国际化插件-vuei18n原理

4,664 阅读1分钟

最近开发项目,有国际化的需求,任务派发给了同事,同事一顿操作猛如虎,完成任务,当我拉下代码准备好好观摩大佬的国际化是如何做的时候……

嗯?

惊呆了我!

一个登陆组件写了三遍?所有组件都写了三遍?

没错的,就是三遍,一遍英文,一遍简体,一遍繁体,可把大佬给闲的。

于是我也一顿操作猛如虎,开始大刀改,吐血中……

好了,言归正传,项目是vue框架,理所当然的,选用了vuei18n国际化插件。

vuei18n使用

安装插件:

npm i vue-i18n

我的文件目录结构:

├── src/
│   ├── I18n/                   # 国际化模块
│   │   ├── lang/                   # 语言包
│   │   │   ├── cn.js                  
│   │   │   ├── en.js                  
│   │   │   ├── tw.js                   
│   │   │   ├── index.js                   
│   │   ├── i18n.js                 # 国际化入口
│   ├── main.js                 # 入口
├── static/           

main.js文件中导入国际化入口,并挂载到vue上:

// main.js
import i18n from './I18n/i18n'

const app = new Vue({
    router,
    store,
    i18n,
    render: h => h(App)
}).$mount('#app');

在i18n.js文件中,将插件注册到vue,并初始化语言,同时将当前的语言共享到各个组件。

// i18n.js
import Vue from 'vue'
import locale from 'element-ui/lib/locale'
import VueI18n from 'vue-i18n'
import messages from './lang/index'
import store from '../vuex/index'; //状态管理
import cookies from 'vue-cookies'; //导入cookies
Vue.use(VueI18n)

function initLang() {
    //  初始化语言
    let lang = (navigator.language || navigator.browserLanguage).toLowerCase();
    if (cookies.get('language')||sessionStorage.getItem("language")) {
            // 本地是否有存储
            lang = cookies.get('language')||sessionStorage.getItem("language")
        } else if (lang === 'zh-cn') {
            lang = 'cn';
        } else if (lang === 'zh-tw') {
            lang = 'tw';
        } else if (/en/.test(lang) === true) {
            lang = 'en';
        } else {
            lang = 'cn';
        }
    // lang = 'cn'
    store.dispatch('lang', lang);
    cookies.set('language', lang, {
        expires: 7,
        path: '/',
        domain: '',
        secure: true,
        httponly: true
    })
    return lang
}
// 从localStorage获取语言选择。
const i18n = new VueI18n({
    locale: initLang(), // 初始未选择默认 cn 中文
    messages
})
locale.i18n((key, value) => i18n.t(key, value)) // 兼容element

export default i18n

lang/index.js文件混合多种语言:

值得注意的是,每个语言包中所用到的变量必须要一致,否则报错,只会渲染出字段名。

// lang/index.js
import en from './en'
import cn from './cn'
import tw from './tw'
export default {
	en: en,
	cn: cn,
	tw: tw
}
//cn.js
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'; // 引入element语言包
const cn = {
    hello:'你好',
    apple: '沒有苹果 | 一个苹果 | {count} 个苹果'
    ...zhLocale
}
export default cn;
//en.js
import enLocale from 'element-ui/lib/locale/lang/en' // 引入element语言包 引入element语言包
const en = {
    hello:'hello',
    apple: 'no apples | one apple | {count} apples'
    ...enLocale
}
export default en;
//tw.js
import twLocale from 'element-ui/lib/locale/lang/zh-TW'; // 引入element语言包
const tw = {
    hello:'你好',
    apple: '沒有蘋果 | 一個蘋果 | {count} 個蘋果'
    ...twLocale
}
export default tw;
<p>{{$t('hello')}}</p>
<!-- 复数使用 -->
<p>{{$t('apple', 0)}}</p>
<p>{{$t('apple', 1)}}</p>
<p>{{$t('apple', 10, {count:10})}}</p>
// 切换语言
handleLanuage(e){
    sessionStorage.setItem("language", e);
    cookies.set("language", e, {
        expires: 7,
        path: "/",
        domain: "",
        secure: true,
        httponly: true
      });
    this.$i18n.locale = e;
    this.$store.dispatch("lang", e);
}

vuei18n-原理

vuei18n使用起来非常简单,也容易理解,那么,它的原理是怎样的呢?

i18n是如何注册到vue中的?

首先,引入VueI18n,然后注册到Vue中。

Vue.use(VueI18n)

注册的过程,会执行一段代码:

Object.defineProperty(Vue.prototype, '$i18n', {
      get () { return this._i18n }
})

最开始this._i18n是没有赋值的,上述代码只是简单对数据劫持监听。

beforeCreate: function beforeCreate () {
  var options = this.$options;
  options.i18n = options.i18n || (options.__i18n ? {} : null);

  if (options.i18n) {
    
  } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
    // root i18n  根vue实例
    this._i18n = this.$root.$i18n;
    this._i18n.subscribeDataChanging(this);
    this._subscribing = true;
  }
}

可以看到,以options为判断,最开始取值取的是options.i18n,而这个值并不存在,但是,在根实例this.$root中,i18n是存在的。

因为在创建根实例时,带上了参数i18n

new Vue({
  el: '#app',
  i18n,  // 我在这里
  render: h => h(App)
})

切换语言后,i18n是怎样起作用的?

切换语言时,需要运行代码:

this.$i18n.local = lang

Vuei18n中,有一个监听local的方法。

watchLocale (): ?Function {
    /* istanbul ignore if */
    if (!this._sync || !this._root) { return null }
    const target: any = this._vm // vue 实例
    return this._root.$i18n.vm.$watch('locale', (val) => {
      // 重新对 locale 赋值
      target.$set(target, 'locale', val) 
      // 迫使 Vue 实例重新渲染
      target.$forceUpdate() 
    }, { immediate: true })
  }

可以看到,当local改变的时候,会强制Vue重新渲染页面。

$forceUpdate()

这是vue的一个方法,是强制更新的意思,更新视图和数据,触发updated生命周期。

在vue中,对于复杂的对象数组,数据层次太多,当你改变了其中属性值时,vue无法感知数据的变动,render函数不会自动更新渲染,所以在页面视图中并不会及时更新。

change: function(index) {//增加性别属性
    this.list[index].sex = '男';
},
clear: function() {//清空数组
    this.list.length = 0;
}

当然,如果数据改动较少,我们可以用$set

change: function(index) {//增加性别属性
    this.$set(this.list[index],'sex','男')
},

在数据很多的情况下,总不能一条条的写$set$forceUpdate就派上用场了。

change: function(index) {
    this.list[index].sex = '男';
    this.list[index].name = 'dddd';
    this.list[index].age = '12';
    this.$forceUpdate();
},