摘要:引入是一個(gè)插件,主要作用就是讓項(xiàng)目支持國(guó)際化多語(yǔ)言。所以新建一個(gè)文件夾,存放所有跟多語(yǔ)言相關(guān)的代碼。目前包含三個(gè)文件。全局搜索發(fā)現(xiàn)一共有多個(gè)。
這兩天手頭的一個(gè)任務(wù)是給一個(gè)五六年的老項(xiàng)目添加多語(yǔ)言。這個(gè)項(xiàng)目龐大且復(fù)雜,早期是用jQuery實(shí)現(xiàn)的,兩年前引入Vue并逐漸用組件替換了之前的Mustache風(fēng)格模板。要添加多語(yǔ)言,不可避免存在很多文本替換的工作,這么龐雜的一個(gè)項(xiàng)目,怎么才能使文本替換變得高效且不會(huì)引入bug是這篇文章主要要寫(xiě)的東西。
引入vue-i18nvue-i18n是一個(gè)vue插件,主要作用就是讓項(xiàng)目支持國(guó)際化多語(yǔ)言。首先我們引入這個(gè)插件:
import Vue from "vue" import VueI18n from "vue-i18n" Vue.use(VueI18n)
這里注意的就是vue插件的使用方法,通過(guò)全局方法 Vue.use() 使用插件。
插件通常會(huì)為 Vue 添加全局功能。插件的范圍沒(méi)有限制——一般有下面幾種:添加全局方法或者屬性;添加全局資源:指令/過(guò)濾器/過(guò)渡等;通過(guò)全局 mixin 方法添加一些組件選項(xiàng);添加 Vue 實(shí)例方法,通過(guò)把它們添加到 Vue.prototype 上實(shí)現(xiàn)。
Vue.js 的插件應(yīng)當(dāng)有一個(gè)公開(kāi)方法 install, 通過(guò)代碼可以更直觀的看出插件提供的功能:
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或?qū)傩? Vue.myGlobalMethod = function () { // 邏輯... } // 2. 添加全局資源 Vue.directive("my-directive", { bind (el, binding, vnode, oldVnode) { // 邏輯... } ... }) // 3. 注入組件 Vue.mixin({ created: function () { // 邏輯... } ... }) // 4. 添加實(shí)例方法 Vue.prototype.$myMethod = function (methodOptions) { // 邏輯... } }
了解vue插件的install方法對(duì)我們等會(huì)查看i18n源碼有很大幫助。
使用vue-i18n我們先看官方提供的最簡(jiǎn)單的使用模板:
//HTML//JAVASCRIPT const messages = { en: { message: { hello: "hello world" } }, ja: { message: { hello: "こんにちは、世界" } } } const i18n = new VueI18n({ locale: "ja", // set locale messages, // set locale messages }) new Vue({ i18n }).$mount("#app") //OUTPUT{{ $t("message.hello") }}
こんにちは、世界
可以看到,我們?cè)趯?shí)例化Vue的時(shí)候,將i18n當(dāng)做一個(gè)option傳了進(jìn)去。之后我們就可以在vue的組件里使用i18n了,使用方法主要是兩種:
在組件的template中,調(diào)用$t()方法
在組件的script中,調(diào)用this.$i18n.t()方法
添加locales文件夾上節(jié)的messages是一個(gè)包含了多語(yǔ)言的的對(duì)象,它就像我們的字典。既然是字典,我希望它只有一本。所以我只會(huì)new VueI18n()一次,并將實(shí)例化得到的i18n對(duì)象作為唯一的字典。
所以新建一個(gè)locales文件夾,存放所有跟多語(yǔ)言相關(guān)的代碼。目前包含三個(gè)文件:index.js, en.json, zh.json。
en.json和zh.json就是我們的語(yǔ)言包,是一個(gè)json形式。這里為了對(duì)照方便,我們必須保證語(yǔ)言包的內(nèi)容是一一對(duì)應(yīng)的。然后我們?cè)趇ndex.js中完成設(shè)置。
import Vue from "vue" import VueI18n from "vue-i18n" Vue.use(VueI18n) const DEFAULT_LANG = "zh" const LOCALE_KEY = "localeLanguage" const locales = { zh: require("./zh.json"), en: require("./en.json"), } const i18n = new VueI18n({ locale: DEFAULT_LANG, messages: locales, }) export const setup = lang => { if (lang === undefined) { lang = window.localStorage.getItem(LOCALE_KEY) if (locales[lang] === undefined) { lang = DEFAULT_LANG } } window.localStorage.setItem(LOCALE_KEY, lang) Object.keys(locales).forEach(lang => { document.body.classList.remove(`lang-${lang}`) }) document.body.classList.add(`lang-${lang}`) document.body.setAttribute("lang", lang) Vue.config.lang = lang i18n.locale = lang } setup() export default i18n
我們對(duì)外提供了一個(gè)setup()的方法,給使用者修改當(dāng)前使用語(yǔ)種的能力。同時(shí),我們?cè)趕etup里還做了三件事:
將當(dāng)前語(yǔ)種存到 localStorage中,保存用戶(hù)的使用習(xí)慣;給body添加語(yǔ)種相關(guān)的class,因?yàn)椴煌Z(yǔ)言可能導(dǎo)致排版出現(xiàn)差異,我們需要適配;將當(dāng)前語(yǔ)種存到Vue的全局配置中,以便未來(lái)可能的使用。
最后我們?cè)?b>main.js中引入這個(gè)Index.js即可。
import Vue from "vue" import App from "./app.vue" import store from "./store" import router from "./router" ... import i18n from "@crm/locales" ... new Vue({ i18n, router, store, render: h => h(App), }).$mount("#app")
這樣看起來(lái),我們的國(guó)際化已經(jīng)完成了,然而之后馬上就有新的問(wèn)題出現(xiàn)了!
問(wèn)題一:vue實(shí)例外的js代碼中的文本怎么替換?前面說(shuō)到,vue實(shí)例中我們可以使用this.$i18n.t,這里的this是vue的實(shí)例。那項(xiàng)目中很多js代碼在vue的實(shí)例之外,我們要怎么辦?
最簡(jiǎn)單的解決方法是這樣的,我們的locales/index.js這個(gè)文件已經(jīng)export了i18n這個(gè)對(duì)象,那我們只需要在每次要使用的時(shí)候手動(dòng)將i18n導(dǎo)入進(jìn)來(lái)就可以了。
可是這樣一來(lái),我們之后做諸如上面的文本替換時(shí),就得小心翼翼的區(qū)別是否在vue實(shí)例中。如果是,我們用this.$i18n.t,否則先import然后用i18n.t。這顯然增加了復(fù)雜性!
為了解決這個(gè)問(wèn)題,只直接的解決辦法就是將i18n掛到window下,變成全局變量。我們就不必再I(mǎi)mport進(jìn)來(lái),同時(shí)只使用統(tǒng)一方法:i18n.t。
我們?cè)趍ain.js中添加這行代碼:
import Vue from "vue" import App from "./app.vue" import store from "./store" import router from "./router" ... import i18n from "@crm/locales" ... window.i18n = i18n new Vue({ i18n, router, store, render: h => h(App), }).$mount("#app")
然后我們興高采烈的將組件中的import i18n全去掉,并將this.$i18n.t改為i18n.t。然后項(xiàng)目跑起來(lái)就報(bào)錯(cuò)了:i18n is not defined。
問(wèn)題出在哪里?顯示是組件調(diào)用i18n的時(shí)候,i18n還沒(méi)有掛載到window上,所以是執(zhí)行順序出了問(wèn)題。我們先來(lái)看一下下面代碼的執(zhí)行順序:
//假設(shè)webpack的入口文件是```main.js``` //main.js import moduleA from "moduleA" console.log(1) import moduleB from "moduleB" console.log(2) //moduleA.js console.log(3) //moduleB.js console.log(4) //最終在瀏覽器中打印出的數(shù)字順序是: 3 4 1 2
為什么會(huì)這樣呢?跟ES6 module的機(jī)制有關(guān)系。import命令具有提升效果,會(huì)提升到整個(gè)模塊的頭部,首先執(zhí)行。這種行為的本質(zhì)是,import命令是編譯階段執(zhí)行的,在代碼運(yùn)行之前。
這樣我們就找出之前報(bào)錯(cuò)的原因了,我們先import了App, router這些視圖,然后Import的i18n并掛載到window。所以組件的script中的代碼會(huì)最先執(zhí)行,而此時(shí)i18n并未開(kāi)始。所以我們首先將window.i18n = i18n移到locales/index中,然后調(diào)整main.js中import的順序:
//locales/index ... setup() window.i18n = i18n export default i18n //main.js import Vue from "vue" import i18n from "@crm/locales" import App from "./app.vue" import store from "./store" import router from "./router" ...問(wèn)題二:假如存在很多個(gè)new Vue()怎么辦?
前面我們?cè)趍ain.js的new Vue({i18n, ...})中將i18n作為option放了進(jìn)去,但很快我發(fā)現(xiàn)這個(gè)項(xiàng)目并只有一個(gè)Vue的實(shí)例。全局搜索發(fā)現(xiàn)一共有70多個(gè)。
項(xiàng)目中很的諸如彈窗之類(lèi)的組件,都是直接自己實(shí)例化一個(gè)Vue然后自己$mount()到DOM中。這些組件在實(shí)例化的過(guò)程中并沒(méi)有混入i18n選項(xiàng),所以他們的template上自然找不到$t()方法。
怎么辦?難道給每一個(gè)new Vue()都手動(dòng)添加i18n選項(xiàng)嗎?肯定不行,首先我們要給添加70多次,其次如果未來(lái)又有人寫(xiě)了新的new Vue()忘了添加Ii8n,那又回導(dǎo)致報(bào)錯(cuò)。所以我們要想一個(gè)萬(wàn)全的法子。
官方文檔里找不到解決辦法,看來(lái)我們得hack一下了。首先我們來(lái)查vue-i18n的源碼,找到$t()方法是怎么工作的。
全局搜索$t,找到定義它的地方:
Object.defineProperty(Vue.prototype, "$t", { get: function get () { var this$1 = this; return function (key) { var values = [], len = arguments.length - 1; while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; var i18n = this$1.$i18n; return i18n._t.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this$1 ].concat( values )) } } });
可以看到$t掛載在Vue.prototype上,每當(dāng)我們?cè)趯?shí)例中調(diào)用$t時(shí),其實(shí)我們是在調(diào)用this.$i18n對(duì)象上的_t方法?,F(xiàn)在問(wèn)題變成,實(shí)例上的$i18n是什么是時(shí)候定義的。
全局搜索$i18n,我們找到了前面提到過(guò)的每個(gè)插件必須提供的install方法:
function install (_Vue) { Vue = _Vue; ... Object.defineProperty(Vue.prototype, "$i18n", { get: function get () { return this._i18n } }); extend(Vue); Vue.mixin(mixin); Vue.directive("t", { bind: bind, update: update }); Vue.component(component.name, component); // use object-based merge strategy var strats = Vue.config.optionMergeStrategies; strats.i18n = strats.methods; }
可以看到$i18n一開(kāi)始就被定義在了Vue.prototype上,每次調(diào)用的時(shí)候其實(shí)我們是在調(diào)用this._i18n,所以現(xiàn)在問(wèn)題變成實(shí)例的_i18n在哪里。同時(shí)可以看到在Install中我們還混入了mixin, directive, component,這些在上面都有提過(guò)它的作用。
var mixin = { beforeCreate: function beforeCreate () { var options = this.$options; options.i18n = options.i18n || (options.__i18n ? {} : null); if (options.i18n) { if (options.i18n instanceof VueI18n) { ... this._i18n = options.i18n;
我們?cè)趍ixin中找到了this._i18n的來(lái)源,前面提到mixin會(huì)被注入到組件中。在每個(gè)組件創(chuàng)建前,我們將this.$options的i18n給了this._i18n。
這個(gè)this.$options是什么?它的使用方式是Vue.mixin(mixin),所以我們看一下vue的文檔:全局混入
// 為自定義的選項(xiàng) "myOption" 注入一個(gè)處理器。 Vue.mixin({ created: function () { var myOption = this.$options.myOption if (myOption) { console.log(myOption) } } }) new Vue({ myOption: "hello!" }) // => "hello!"
所以this.$options就是我們new Vue時(shí)提供的選項(xiàng)對(duì)象。
所以問(wèn)題的根源就是除了main.js中的new Vue外,其余70多個(gè)new Vue我們沒(méi)有混入i18n這個(gè)選項(xiàng)。怎樣才可以讓每次new Vue時(shí)自動(dòng)將i18n混入選項(xiàng)呢?看上去我們只能修改Vue的源碼了。
function Vue (options) { if (process.env.NODE_ENV !== "production" && !(this instanceof Vue) ) { warn("Vue is a constructor and should be called with the `new` keyword"); } this._init(options); }
可以看到每次Vue實(shí)例化時(shí),會(huì)調(diào)用_init方法,這個(gè)方法從哪里來(lái)呢?
function initMixin (Vue) { Vue.prototype._init = function (options) { ...
在Vue.prototype上,所以我們只需要修改Vue.prototype就好了。
//locales/index const init = Vue.prototype._init Vue.prototype._init = function(options) { init.call(this, { i18n, ...options, }) }
這樣我們?cè)谌魏螘r(shí)候new Vue()就自動(dòng)添加了i18n選項(xiàng),問(wèn)題解決!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/95241.html
摘要:在中引入調(diào)用第三方插件新建一個(gè)對(duì)象默認(rèn)語(yǔ)言配置語(yǔ)言選項(xiàng)是一個(gè)配置語(yǔ)言包文件和語(yǔ)言選項(xiàng)的文件,文件內(nèi)容大致如下語(yǔ)言包列表合并語(yǔ)言包是一個(gè)函數(shù),判斷當(dāng)前設(shè)置的語(yǔ)言類(lèi)型,如果沒(méi)有設(shè)置語(yǔ)言,則根據(jù)判斷是中文還是非中文,非中文則顯示英語(yǔ)。 vue-i18n官網(wǎng) https://kazupon.github.io/vue... 項(xiàng)目用vue-cli構(gòu)建,用到vue全家桶及webpack、iview...
摘要:如果對(duì)您有幫助請(qǐng)動(dòng)動(dòng)鼠標(biāo)右下方給我來(lái)個(gè)贊,您的支持是我最大的動(dòng)力。安裝 npm install vue-i18n 新建一個(gè)文件夾 i18n ,內(nèi)新建 en.js zh.js index.js 三個(gè)文件 準(zhǔn)備翻譯信息 en.js export default { home: { helloworld: hello workd ! } }; zh.js export d...
摘要:為了滿足很多公司都已經(jīng)向方向發(fā)展顧使用多語(yǔ)言的網(wǎng)站已經(jīng)太普遍了所以是使用和實(shí)現(xiàn)國(guó)際化接下來(lái)我會(huì)盡量寫(xiě)的詳細(xì)一點(diǎn)的內(nèi)容個(gè)人覺(jué)得的應(yīng)該寫(xiě)得清楚一些安裝所需要用到的東西安裝安裝創(chuàng)建目錄編寫(xiě)所需要用到的語(yǔ)言我只寫(xiě)了中文和英文歡迎來(lái)到我 為了滿足很多公司都已經(jīng)向international方向發(fā)展,顧使用多語(yǔ)言的網(wǎng)站已經(jīng)太普遍了, 所以是使用vue-i18n和elementUI實(shí)現(xiàn)國(guó)際化.接下來(lái)我...
摘要:因?yàn)閮纱蔚拈_(kāi)發(fā)維護(hù)體驗(yàn)產(chǎn)生了對(duì)比,使我產(chǎn)生了不小的興趣假設(shè)一個(gè)簡(jiǎn)單的頁(yè)面需要多語(yǔ)言。兩個(gè)簡(jiǎn)單的區(qū)別就是和替換的區(qū)別。樣式模式其實(shí)就是簡(jiǎn)單的切換。當(dāng)修改的某個(gè)值時(shí),會(huì)觸發(fā)對(duì)應(yīng)的,并發(fā)射信號(hào)通知節(jié)點(diǎn)去更新。 i18n是什么?i18n(其來(lái)源是英文單詞internationalization的首末字符i和n,18為中間的字符數(shù))是國(guó)際化的簡(jiǎn)稱(chēng)。 前言 第一次接觸多語(yǔ)言是用野生javascri...
摘要:因?yàn)閮纱蔚拈_(kāi)發(fā)維護(hù)體驗(yàn)產(chǎn)生了對(duì)比,使我產(chǎn)生了不小的興趣假設(shè)一個(gè)簡(jiǎn)單的頁(yè)面需要多語(yǔ)言。兩個(gè)簡(jiǎn)單的區(qū)別就是和替換的區(qū)別。樣式模式其實(shí)就是簡(jiǎn)單的切換。當(dāng)修改的某個(gè)值時(shí),會(huì)觸發(fā)對(duì)應(yīng)的,并發(fā)射信號(hào)通知節(jié)點(diǎn)去更新。 i18n是什么?i18n(其來(lái)源是英文單詞internationalization的首末字符i和n,18為中間的字符數(shù))是國(guó)際化的簡(jiǎn)稱(chēng)。 前言 第一次接觸多語(yǔ)言是用野生javascri...
閱讀 1706·2021-09-22 15:25
閱讀 1624·2021-09-07 10:06
閱讀 3256·2019-08-30 15:53
閱讀 1155·2019-08-29 13:12
閱讀 3459·2019-08-29 13:07
閱讀 805·2019-08-28 18:19
閱讀 2352·2019-08-27 10:57
閱讀 1045·2019-08-26 13:29