Note: This article is based on [email protected], see element for source code. Common internationalization schemes are:
ECMAscript Intl: See Front-end internationalization and Intl
angular-translate
react-intl
vue-i18n
Before looking at elementUI’s internationalization scheme, let’s talk about VUE-i18n.
A,vue-i18n
Vue-i18n is a common internationalization solution. Here are a few key points. 1.1 code demo // step1: install vue-i18 plug-in in project
cnpm install vue-i18n --save-dev
Copy the code
// step2: introduce vue-i18n in the project entry file main.js
import Vue from 'vue'
import router from './router'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'zh', // messages: {'zh': require('./assets/lang/zh'),
'en': require('./assets/lang/en'}}) /* eslint-disable no-new */ new vue ({el:'#app',
i18n,
router,
template: '<Layout/>',
components: {
Layout
},
})
Copy the code
// step3: use in the page
// zh.js
module.exports = {
menu : {
home:"Home page"
},
content:{
main:"Here's the content."
}
}
// en.js
module.exports = {
menu : {
home:"home"
},
content:{
main:"this is content"}} // business code <div class="title"> {{$t('menu.home')}}</div>
<input :placeholder="$t('content.main')" type="text"> // Render the result (apply en.js) <div class="title"< div> <input placeholder="Here's the content." type="text">
Copy the code
1.2 Features support complex, date-time localization, digital localization, linking, fallback (default language), component-based localization, custom instruction localization, component interpolation, single-file component, hot overloading, language change and lazy loading. A wide range of functions, this is mainly about a single file component, component-based localization, custom instructions and lazy loading three pieces.
- 1.2.1
$i18n
and$t
vue-i18n
An instance of vUE is generated internally by the initialization method of_vm
, such as1.1 Code demonstration - Step2
Locale and messages from the VueI18n instance are injected into the vue instance, as shown in:
_initVM (data: {
locale: Locale,
fallbackLocale: Locale,
messages: LocaleMessages,
dateTimeFormats: DateTimeFormats,
numberFormats: NumberFormats
}): void {
const silent = Vue.config.silent
Vue.config.silent = true
this._vm = new Vue({ data })
Vue.config.silent = silent
}
this._initVM({
locale,
fallbackLocale,
messages,
dateTimeFormats,
numberFormats
})
Copy the code
1.2.1.1 VuE-i8N install method
export functioninstall (_Vue) { ...... Extend (Vue) // Attach some common methods or properties to Vue. Prototype, for example$i18n,$t,$tcand$dVue. Mixin (mixin) // Inject i18n attributes into each Vue example, etc. Vue.directive('t', { bind, update, unbind}) / / global directives, called v - t Vue.com ponent (interpolationComponent. Name, interpolationComponent) / / global components, I18n Vue.component(numberComponent. Name, numberComponent) // global component, I18n-n // Use simple mergeStrategies to prevent I18n instance lose'__proto__'Const strats = Vue. Config. OptionMergeStrategies / / define a merge strategy strats i18n =function (parentVal, childVal) {
return childVal === undefined
? parentVal
: childVal
}
}
Copy the code
1.2.1.2 extend(Vue) : Mount some common methods/attributes like $i18n, $t, $TC and $d on vue. prototype
export default function extend (Vue: any): void {
if(! Vue.prototype.hasOwnProperty('$i18n')) {
Object.defineProperty(Vue.prototype, '$i18n', {
get () { return this._i18n }
})
}
Vue.prototype.$t = function(key: Path, ... values: any): TranslateResult { const i18n = this.$i18n
returni18n._t(key, i18n.locale, i18n._getMessages(), this, ... values) } ......Copy the code
1.2.1.3 vue. mixin(mixin) : Globally mix beforeCreate, beforeMount and beforeDestroy methods, make each Vue sample inject i18N attributes, add _I18N attributes to each Vue component
beforeCreate (){
const options = this.$options
options.i18n = options.i18n || (options.__i18n ? {} : null)
if (options.i18n) {
if (options.i18n instanceof VueI18n) {
// init locale messages via custom blocks
if (options.__i18n) {
try {
letLocaleMessages = {} // options.__i18n is the contents of the < i18N ></i18n> tag in the vue component of a single file. Options. __i18n.forEach(resource => {localeMessages = merge(localeMessages, JSON.parse(resource)) }) Object.keys(localeMessages).forEach((locale: Locale) = > {/ * mergeLocaleMessage, is i18n tag in the component data merged into _vm instance messages enclosing _vm.$set(this._vm.messages, locale, merge({}, this._vm.messages[locale] || {}, message)) */ options.i18n.mergeLocaleMessage(locale, localeMessages[locale]) }) } catch (e) {...... } this._i18nwatcher = this._i18n.watchi18nData ()} this._i18nwatcher = this._i18n.watchi18ndata ()}else if(isPlainObject(options.i18n)) {// i18n is a normal object, not an instance of VueI18n // componentlocalI18n // inject Vue. Prototype at extend(Vue)$i18n
if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
options.i18n.root = this.$root
options.i18n.formatter = this.$root.$i18n.formatter
......
options.i18n.preserveDirectiveContent = this.$root.$i18n.preserveDirectiveContent
}
// init locale messages via custom blocks
if(options.__i18n) { ...... }}}else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
// root i18n
this._i18n = this.$root.$i18n
} else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {
// parent i18n
this._i18n = options.parent.$i18n
}
},
beforeMount (): void {
const options: any = this.$options
options.i18n = options.i18n || (options.__i18n ? {} : null)
if(options.i18n) { ...... // Add the current vue instances to the global _dataListeners array, and when the Watch method is notified, loop over these instances and call$forceUpdateMethod to update this. _i18n. SubscribeDataChanging (this) enclosing _subscribing =true
} else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
this._i18n.subscribeDataChanging(this)
this._subscribing = true
} else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {
this._i18n.subscribeDataChanging(this)
this._subscribing = true}},Copy the code
1.2.1.4 Update Mechanism: In Vue. Mixin (mixin), there is this._i18nwatcher = this._i18N.watchi18ndata (), which notifies Vue instances of updates, as well as the watchLocale method.
WatchI18nData (): Function {const self = this //$i18nand$tSection ', the global _VM instance's data property, which holds locale and messages informationreturn this._vm.$watch('$data', () = > {letI = self._datalisteners. Length // _dataListeners saves each vUE instancewhile (i--) {
Vue.nextTick(() => {
self._dataListeners[i] && self._dataListeners[i].$forceUpdate() // force update})}}, {deep:true})}Copy the code
$i18n: $i18n: $i18n: $i18n: $i18n: $i18n: $i18n: $i18n
- 1.2.2 Single-file Components
The following example code allows you to manage internationalization within a component.
<i18n>
{
"en": {
"hello": "hello world!"
},
"ja": {
"hello": "Kohiko: The world!"
}
}
</i18n>
<template>
<div id="app">
<label for="locale">locale</label>
<select v-model="locale">
<option>en</option>
<option>ja</option>
</select>
<p>message: {{ $t('hello') }}</p>
</div>
</template>
<script>
export default {
name: 'app'.data () { return { locale: 'en' } },
watch: {
locale (val) {
this.$i18n.locale = val
}
}
}
</script>
Copy the code
Webpack configuration (for VUe-Loader V15 or later) :
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
resourceQuery: /blockType=i18n/,
type: 'javascript/auto',
loader: '@kazupon/vue-i18n-loader'} / /... ] } / /... }Copy the code
Vue i18n-loader is used to parse the custom tag
i18n
The contents of the tag can beyaml
Format, can also be JSON (5) or general text format, this is mainly throughconvert
Method processing;- GenerateCode is mainly used to parse vUE single file components
i18n
Tags (refer toVue custom block, the tag content is saved in__i18n
Array) and some special characters (such as \u2028, \u2029, and \u0027, seeSpecial characters commonly encountered in JSON).
import webpack from 'webpack'
import { ParsedUrlQuery, parse } from 'querystring'
import { RawSourceMap } from 'source-map'
import JSON5 from 'json5'
import yaml from 'js-yaml'
const loader: webpack.loader.Loader = function (
source: string | Buffer,
sourceMap: RawSourceMap | undefined
): void {
if (this.version && Number(this.version) >= 2) {
try {
......
this.callback(
null,
`export default ${generateCode(source, parse(this.resourceQuery))}`,
sourceMap ) } catch (err) { ...... }}else{... }}function generateCode(source: string | Buffer, query: ParsedUrlQuery): string {
const data = convert(source, query.lang as string)
let value = JSON.parse(data)
if (query.locale && typeof query.locale === 'string') {
value = Object.assign({}, { [query.locale]: value })
}
value = JSON.stringify(value)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
.replace(/\\/g, '\ \ \ \')
let code = ' '
code += `function (Component) {
Component.__i18n = Component.__i18n || []
Component.__i18n.push('${value.replace(/\u0027/g, '\\u0027')} ')
}\n`
return code
}
function convert(source: string | Buffer, lang: string): string {
const value = Buffer.isBuffer(source)? source.toString() :source
switch (lang) {
case 'yaml':
case 'yml':
const data = yaml.safeLoad(value)
return JSON.stringify(data, undefined, '\t')
case 'json5':
return JSON.stringify(JSON5.parse(value))
default:
return value
}
}
export default loader
Copy the code
- 1.2.3
Lazy loading
referenceLazy loading translationThe following is the original text.
Loading all translation files at once is excessive and unnecessary.
With Webpack, lazy loading or asynchronous loading of transformation files is very simple.
Let’s assume we have a project directory similar to the following
our-cool-project -dist -src --routes --store --setup ---i18n-setup.js --lang ---en.js ---it.js Copy the code
The lang folder is where all our translation files are located. The setup folder is our arbitrary Settings > file, such as I18N-setup, global component inits, plug-in inits and other locations.
//i18n-setup.js import Vue from 'vue' import VueI18n from 'vue-i18n' import messages from '@/lang/en' import axios from 'axios' Vue.use(VueI18n) export const i18n = new VueI18n({ locale: 'en'// Set the locale to fallbackLocale:'en'Const loadedLanguages = [const loadedLanguages = ['en'] // Our pre-installed default languagefunction setI18nLanguage (lang) { i18n.locale = lang axios.defaults.headers.common['Accept-Language'] = lang document.querySelector('html').setAttribute('lang', lang) return lang } export function loadLanguageAsync (lang) { if(i18n.locale ! == lang) {if(! loadedLanguages.includes(lang)) {return import(/* webpackChunkName: "lang-[request]" */ `@/lang/${lang}`).then(msgs => { i18n.setLocaleMessage(lang, msgs.default) loadedLanguages.push(lang) return setI18nLanguage(lang) }) } return Promise.resolve(setI18nLanguage(lang)) } return Promise.resolve(lang) } Copy the code
In short, we are creating a new VueI18n instance. Then we create a loadedLanguages array that will keep track of the language we loaded. Next comes the setI18nLanguage function, which actually changes the vueI18n instance, AXIOS, and whatever else needs to be localized.
LoadLanguageAsync is the actual function used to change the language. Loading a new file is done through the import feature, generously provided by Webpack, which allows us to load the file dynamically, and because it uses Promise, we can easily wait for the load to complete.
You can learn more about the import functionality in the Webpack documentation.
Using the loadLanguageAsync function is simple. A common use case is in the VUe-router beforeEach hook.
router.beforeEach((to, from, next) => { const lang = to.params.lang loadLanguageAsync(lang).then(() => next()) }) Copy the code
We can improve on this by checking whether lang actually supports reject, calling Reject so that we can capture routing transformations in beforeEach.
The core method is loadLanguageAsync, and the core method of loadLanguageAsync is import method. The principle of import dynamic loading can be refer to the implementation process of Import in Webpack. Essentially, script tags are dynamically generated in HTML.
Element – UI default internationalization scheme
el-select
packages/select/src/select.vue
emptyText() {
if (this.loading) {
......
} else{...if (this.filterable && this.query && this.options.length > 0 && this.filteredOptionsCount === 0) {
return this.noMatchText || this.t('el.select.noMatch'); }... }Copy the code
The locale/lang directory
locale/lang/zh-CN.js
t
t
this.t('el.select.noMatch')
import { t } from 'element-ui/src/locale';
exportdefault { methods: { t(... args) {returnt.apply(this, args); }}};Copy the code
Elemental-ui/SRC /locale/index.js is introduced in SRC /mixins/locale.js. The logic of the file is as follows: a. Use and i18n are exposed to SRC /index.js (see ElementUI for structure and source code). It is used to set the language category and the handling method globally (the default is to call its own supplied i18nHandler); b. use export const use = function(l) { lang = l || lang; // Default is Chinese}; Methods used in the project:
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'// Set the language locale.use(lang)Copy the code
C. I18n and i18nHandler, with vuei18n and $t, are obviously compatible with vuE-i18n internationalization schemes, as shown in the first part of this article;
let i18nHandler = function() {
const vuei18n = Object.getPrototypeOf(this || Vue).$t;
if (typeof vuei18n === 'function'&&!!!!! Vue.locale) {if(! merged) { merged =true;
Vue.locale(
Vue.config.lang,
deepmerge(lang, Vue.locale(Vue.config.lang) || {}, { clone: true})); }returnvuei18n.apply(this, arguments); }};Copy the code
Which method
export const t = function(path, options) {// If the 'vuei18n' scheme is used in the project, then internationalization is taken over by itlet value = i18nHandler.apply(this, arguments);
if(value ! == null && value ! == undefined)returnvalue; // self-processing logic const array = path.split('. ');
let current = lang;
for (let i = 0, j = array.length; i < j; i++) {
const property = array[i];
value = current[property];
if (i === j - 1) return format(value, options);
if(! value)return ' ';
current = value;
}
return ' ';
};
Copy the code
As above, if the VUEI18N scheme is used in a project, internationalization is taken over directly by it; Denial goes into the logic behind it. T (‘ el.select.nomatch ‘); t(‘ select.nomatch ‘); Press “for the string el.select.nomatch.” Select and curren.select.noMatch from zh-cn. js. B. Format is supported. Taking the El-Pagination component as an example, the total number of entries can be displayed
this.t('el.pagination.total', { total: this.$parent.total })
{
el: {
pagination: {
total: 'Total {total}'}}}Copy the code
For this case, method T is simplified as follows:
var RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function format() {
return function template(string, args) {
return string.replace(RE_NARGS, (match, prefix, i, index) => {
let result;
if (string[index - 1] === '{' &&
string[index + match.length] === '} ') {
return i;
} else {
result = hasOwn(args, i) ? args[i] : null;
if (result === null || result === undefined) {
return ' ';
}
returnresult; }}}})function t(string, args) {
return format()(string, args)
}
var test = t('Total {total}', { total: 1000 })
console.log(test)
Copy the code
So if I do that, I’m going to end up with 1000 entries.
recommended
ElementUI structure and source code research
ElementUI — mixins
ElementUI — Cache: mousewheel & repeat-click
ElementU – transitions
Theme elementUI –