Vue 項目前端多語言方案實踐
點擊上方?前端Q,關(guān)注公眾號
回復(fù)加群,加入前端Q技術(shù)交流群
一、通常有哪些內(nèi)容需要處理
總的來說,一個Web應(yīng)用中,需要做多語言切換的內(nèi)容常見的包括如下方面:
1、模板中的內(nèi)容,如Vue.js的標(biāo)簽中的文字內(nèi)容
2、JS代碼中的文字內(nèi)容
3、圖片中的文案內(nèi)容
4、頁面title
5、第三方組件中的文案(比如,我的項目中用到了Vux的組件)
6、后端接口中需要展示到前端的數(shù)據(jù)內(nèi)容
7、后端接口返回的錯誤提示
二、基本思路
1、首先,需要確定以什么樣的方式來獲取到當(dāng)前應(yīng)該展示何種語言
我采用的是用URL傳遞?lang=en或者?lang=zh-CN這樣的傳遞參數(shù)的形式。這樣做的好處在于可以通過鏈接指定用哪種語言。但是,只依賴于地址欄參數(shù)也是不方便的。比如,在頁面跳轉(zhuǎn)的時候,這個地址欄參數(shù)可能就丟失了。這會導(dǎo)致你在頁面跳轉(zhuǎn)之后就不知道該用哪種語言展示了。而理想的的方式應(yīng)該是,進(jìn)入某個頁面的時候帶有這個參數(shù)(這個時候就獲取到該使用何種語言了),等再跳轉(zhuǎn)到其它頁面的時候就不必再帶這個lang參數(shù)了,因為此時你已經(jīng)知道該用哪種語言了。所以,應(yīng)該在一進(jìn)入第一個頁面的時候就把這個參數(shù)存下來,比如,存在localstorage中,存在vuex的state中。
這里,就引出來一個語言判斷的優(yōu)先級問題。
因為地址欄里可能有lang參數(shù),localstorage中可能也有相關(guān)的存儲字段(因為上次訪問過本應(yīng)用),你可能還想設(shè)置默認(rèn)的降級語言,等等。其優(yōu)先級應(yīng)該如何處理呢?
正確的優(yōu)先級應(yīng)該是:
先看地址欄參數(shù)中有沒有;
再看localstorage中有沒有;
然后再通過navigator.language獲取瀏覽器默認(rèn)語言,看是否是你的應(yīng)用所支持的語言,若是,則采用之;
最后才是使用回退語言(例如,比較通用的英語)。
當(dāng)然,你可以根據(jù)你的需求來做一些簡化。
2、其次,采用什么工具來解決語言轉(zhuǎn)換和打包的問題?
(1)i18n相關(guān)工具的選擇——由誰來提供多語言轉(zhuǎn)換函數(shù)(通常是$t)?
目前國際化通用方式多數(shù)基于i18n,我們也無需再去造輪子了。但就i18n的具體使用上,有很多不同的NPM模塊。比如vuex-i18n、vue-i18n、simplest-i18n等。因為多數(shù)復(fù)雜一點的項目都會上vuex,所以復(fù)雜一點的項目選擇vuex-i18n會比vue-i18n更方便。
而simplest-i18n這個很小眾的模塊,其實也有它的好處。它支持下面這樣的寫法:
在模板中:
<span>$t('真實姓名',?'Real?Name')span>
或者在JS中:
this.$t('真實姓名',?'Real?Name')
即將語言寫在一起,$t函數(shù)的每一個參數(shù)都是一種語言,一目了然,還是比較方便閱讀的。對小項目來說,不失為一種選擇。
其基本使用如下:
t.js文件:
import?i18n?from?'simplest-i18n';
import?getLang?from?'../../getLang';
const?t?=?i18n({
??locale:?getLang.lang,?//?當(dāng)前語言
??locales:?getLang.langs?//?支持的語言列表
});
export?default?t;
然后在應(yīng)用的入口文件中對Vue.js進(jìn)行擴(kuò)展:
import?t?from?'./t';
Vue.$t?=?Vue.prototype.$t?=?t;
這樣就把$t這個方法掛載到了Vue.js的全局。Vue實例中也可以通過this.$t訪問到,使用上還是非常簡單的。
但是,對于大項目來說,把語言包都寫在代碼里面,對維護(hù)并不友好。而且,靠它也解決不了我所用到的Vux組件的多語言化的問題。
所以最終,我選擇了vuex-i18n作為基礎(chǔ)。
(2)組織和處理語言包的工具——語言包怎么組織,怎么打包處理?
對于這個問題,我首先需要解決Vux第三方組件的多語言化問題。
首先,在語言包的組織方面,比較常見的是寫成JSON配置文件。不過,我最終采用了Yaml這種格式,它支持將多語言字段寫在一起。比如:
config.yml
confirm:
??zh-CN:?確認(rèn)
??en:?confirm
而不是像下面那樣將一個字段的多語言拆成幾處,比如:
confirm:?確認(rèn)
confirm:?confirm
這樣帶來的好處就是,可以方便地對照一個字段的不同語言版本,而且要修改或刪除某一個字段時,也可以在一處完成,無需切換。況且,Yaml文件的語法也更加簡單明了,省去了JSON文件必須寫雙引號、不可以出現(xiàn)注釋等諸多麻煩。
其次,在語言包的打包方面,我找到了vux-loader。它可以和現(xiàn)有的webpack配置結(jié)合,不僅能完成Vux組件多語言配置的打包,還允許在自定義的Vue組件中使用標(biāo)簽。比如,在自定義組件中我可以這么寫:
<i18n>
confirm:
??zh-CN:?確認(rèn)
??en:?confirm
<i18n>
打包時,vux-loader會將標(biāo)簽中的多語言配置信息導(dǎo)出至我們所配置的一個Yaml文件中,而把標(biāo)簽從我們的自定義組件中移除。
那么,對于Yaml文件如何處理呢?可以用json-loader和yaml-loader。它們可以將Yaml文件轉(zhuǎn)換成我們所需要的json格式,方便在JS函數(shù)中使用,就像這樣:
const?componentsLocales?=?require('json-loader!yaml-loader!../../locales/components.yml');?//?這就得到了一個語言包的json格式
3、如何通知后端接口返回何種語言的數(shù)據(jù)?
因為涉及到許多接口都要通知后端采用哪種語言,所以,我選擇了使用header頭的方式。在axios的interceptor中給請求統(tǒng)一添加了header頭:Accept-Language, 并把這個值的內(nèi)容設(shè)置成前端所獲得應(yīng)使用的語言(如,zh-CN 或 en 等)。這樣,就集中在一處把這個問題處理掉了。
三、具體實踐中的一些細(xì)節(jié)
1、獲取當(dāng)前應(yīng)該采用何種語言的getLang模塊的實現(xiàn)
import?{?getQueryObj?}?from?'../utils/url';
import?{?setItem,?getItem?}?from?'../utils/storage';
const?langs?=?['zh-CN',?'en'];?//?支持哪些語言
const?defaultLang?=?'en';?//?默認(rèn)語言,暫時并沒有對外拋出
function?getLang()?{
??let?queries?=?getQueryObj();
??let?storeLang?=?getItem('lang');
??let?rawLang;
??let?flag?=?false;
??if?(queries?&&?queries['lang'])?{
????rawLang?=?queries['lang'];
????setItem('lang',?rawLang);
??}?else?{
????rawLang?=?storeLang?||?navigator.language;
??}
??langs.map(item?=>?{
????if?(item?===?rawLang)?{
??????flag?=?true;
????}
??});
??return?flag???rawLang?:?defaultLang;
}
const?lang?=?getLang(langs,?defaultLang);
export?default?{
????lang,?//?獲取到當(dāng)前語言
????langs?//?所支持的語言列表
}
2、Vux組件的多語言包的配置
可以從Vux的官方github中找到src/locales/all.yml拷貝過來(同一目錄下的src/locales/zh-CN.yml、src/locales/en.yml分別是其中文部分和英文部分),根據(jù)你自己的需要略作修改即可。
然后在你的應(yīng)用的應(yīng)用的入口文件中引入:
const?vuxLocales?=?require('json-loader!yaml-loader!../../locales/all.yml');
3、vux-loader的配置
webpack.dev.conf.js中:
resolve(vuxLoader.merge(devWebpackConfig,?{
????plugins_dir:?[
????????'vux-ui',
????????{
????????????name:?'i18n',
????????????vuxStaticReplace:?false,
????????????staticReplace:?false,
????????????extractToFiles:?'src/locales/components.yml',
????????????localeList:?['en','zh-CN']
????????}
????]
}))
webpack.prod.conf.js中:
resolve(vuxLoader.merge(buildWebpackConfig,?{
????plugins_dir:?[
????????'vux-ui',
????????{
????????????name:?'i18n',
????????????vuxStaticReplace:?false,
????????????staticReplace:?false,
????????????extractToFiles:?'src/locales/components.yml',
????????????localeList:?['en','zh-CN']
????????}
????]
}))
其中的localeList: ['en','zh-CN']就是指定你的應(yīng)用支持哪幾種語言。
而extractToFiles: 'src/locales/components.yml'就是指定你的自定義組件中所用到的那些標(biāo)簽中的語言包信息,應(yīng)該導(dǎo)出到哪個Yaml文件中。也就是說,你在各個自定義組件中使用的標(biāo)簽中的語言包信息都會被vux-loader集中抽取到這個文件中。
然后在應(yīng)用的入口文件中引入這個語言包文件:
const?componentsLocales?=?require('json-loader!yaml-loader!../../locales/components.yml');
4、自定義組件內(nèi)外文案的多語言化
(1)對于自定義組件內(nèi)部的文案的多語言化信息,寫在組件的標(biāo)簽中即可。同時,為了避免不同的自定義組件中多語言字段的命名沖突,在每個字段的名字前面加上以組件名-式的前綴。
(2)對于頁面的標(biāo)題、一些錯誤提示等文案,它們是出現(xiàn)在組件之外的,因此不適合寫在組件的標(biāo)簽中,所以我們單獨新建一個global.yml來存放這些全局性的多語言信息。這些內(nèi)容直接寫在global.yml中即可,并且,為了表面與其它的語言包字段相沖突,我們在每個字段的前面加上global-前綴。
然后在應(yīng)用的入口文件中引入這個語言包文件:
const?componentsLocales?=?require('json-loader!yaml-loader!../../locales/global.yml');
5、vuex-i18n的實現(xiàn)
在src/store/index.js文件中:
import?VuexI18n?from?'vuex-i18n';
export default new Vuex.Store中增加:
????i18n:?VuexI18n.store
在應(yīng)用的入口文件中:
import?VuexI18n?from?'vuex-i18n';
import?getLang?from?'../../getLang';
Vue.use(VuexI18n.plugin,?store);
const?vuxLocales?=?require('json-loader!yaml-loader!../../locales/all.yml');
const?componentsLocales?=?require('json-loader!yaml-loader!../../locales/components.yml');
const?finalLocales?=?{
??'en':?Object.assign(vuxLocales['en'],?componentsLocales['en']),
??'zh-CN':?Object.assign(vuxLocales['zh-CN'],?componentsLocales['zh-CN'])
}
for?(let?i?in?finalLocales)?{
??Vue.i18n.add(i,?finalLocales[i])
}
Vue.i18n.set(globalVars.lang);
6、圖片的多語言化
對于圖片中的文案信息,多語言化主要有這么兩種方式:一是根據(jù)不同的語言展示不同的圖片;二是盡將文字從圖片背景中分離出來,采用文字層加背景圖片層的方式,這樣文字層就可以作為普通文本來實現(xiàn)多語言化了。都比較簡單,不再贅述。
7、在當(dāng)前頁面通過按鈕切換當(dāng)前語言后,如何更新當(dāng)前頁面的內(nèi)容?
如果你的應(yīng)用并不需要在頁面內(nèi)部切換語言版本,那么直接通過URL中傳入不同的lang參數(shù)就可以了,并不涉及到此問題。
第一種方式:刷新頁面
<button?@click="changeLang('zh-CN')">中文button>
<button?@click="changeLang('en')">英文button>
changeLang(lang){
????location.href?=?this.$utils.url.replaceParam(this.$router.history.current.path,?'lang',?lang);
},
第二種方式:watch當(dāng)前頁data中lang字段的變化,通過v-if局部刷新某些相關(guān)組件:
data(){
????return?{
????????lang:?this.$i18n.locale()
????}
}
changeLang(lang){
????this.$i18n.set(lang);
????this.lang?=?this.$i18n.locale();
},
watch:?{
????lang(newVal,?oldVal)?{
????????if(newVal?===?oldVal)?{
????????????return;
????????}
????????//?在這里通過改變某個標(biāo)志位?結(jié)合?v-if?來觸發(fā)某個局部組件的重新渲染
????}
}
第三種方式:結(jié)合vuex派發(fā)全局的語言狀態(tài),接收到狀態(tài)變化時進(jìn)行更新,或者自己簡單地改寫vuex-i18n的實現(xiàn)。這種方式相對復(fù)雜一些。
具體根據(jù)自己的業(yè)務(wù)需求選擇。
8、Yaml中特殊字符的轉(zhuǎn)義
對于一些包含特殊字符的yaml鍵值,比如[、]等,需要進(jìn)行轉(zhuǎn)義。轉(zhuǎn)的方式是給鍵值加上單引號引起來。
如果你的語言包信息中有單引號,則必須連續(xù)使用兩個單引號轉(zhuǎn)義。例如:
str:?'labor''s?day'作者:Paian
http://mobilesite.github.io

往期推薦



最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個專業(yè)的技術(shù)人...


