
作者:Paian http://mobilesite.github.io
前端的國際化是一個比較常見的需求。但網上關于這一方面的直接可用的方案卻不多。最近剛做了一版基于Vue.js的多語言實作,在此簡單作一小結。
一、通常有哪些内容需要處理
總的來說,一個Web應用中,需要做多語言切換的内容常見的包括如下方面:
1、模闆中的内容,如Vue.js的
<template>
标簽中的文字内容
2、JS代碼中的文字内容
3、圖檔中的文案内容
4、頁面
title
5、第三方元件中的文案(比如,我的項目中用到了Vux的元件)
6、後端接口中需要展示到前端的資料内容
7、後端接口傳回的錯誤提示
二、基本思路
1、首先,需要确定以什麼樣的方式來擷取到目前應該展示何種語言
我采用的是用URL傳遞
?lang=en
或者
?lang=zh-CN
這樣的傳遞參數的形式。這樣做的好處在于可以通過連結指定用哪種語言。但是,隻依賴于位址欄參數也是不友善的。比如,在頁面跳轉的時候,這個位址欄參數可能就丢失了。這會導緻你在頁面跳轉之後就不知道該用哪種語言展示了。而理想的的方式應該是,進入某個頁面的時候帶有這個參數(這個時候就擷取到該使用何種語言了),等再跳轉到其它頁面的時候就不必再帶這個
lang
參數了,因為此時你已經知道該用哪種語言了。是以,應該在一進入第一個頁面的時候就把這個參數存下來,比如,存在localstorage中,存在vuex的state中。
這裡,就引出來一個語言判斷的優先級問題。
因為位址欄裡可能有
lang
參數,localstorage中可能也有相關的存儲字段(因為上次通路過本應用),你可能還想設定預設的降級語言,等等。其優先級應該如何處理呢?
正确的優先級應該是:
先看位址欄參數中有沒有;
再看localstorage中有沒有;
然後再通過
navigator.language
擷取浏覽器預設語言,看是否是你的應用所支援的語言,若是,則采用之;
最後才是使用回退語言(例如,比較通用的英語)。
當然,你可以根據你的需求來做一些簡化。
2、其次,采用什麼工具來解決語言轉換和打包的問題?
(1)i18n相關工具的選擇——由誰來提供多語言轉換函數(通常是$t)?
目前國際化通用方式多數基于i18n,我們也無需再去造輪子了。但就i18n的具體使用上,有很多不同的NPM子產品。比如vuex-i18n、vue-i18n、simplest-i18n等。因為多數複雜一點的項目都會上vuex,是以複雜一點的項目選擇vuex-i18n會比vue-i18n更友善。
而simplest-i18n這個很小衆的子產品,其實也有它的好處。它支援下面這樣的寫法:
在模闆中:
<span>$t('真實姓名', 'Real Name')</span>
或者在JS中:
this.$t('真實姓名', 'Real Name')
即将語言寫在一起,$t函數的每一個參數都是一種語言,一目了然,還是比較友善閱讀的。對小項目來說,不失為一種選擇。
其基本使用如下:
t.js檔案:
import i18n from 'simplest-i18n';
import getLang from '../../getLang';
const t = i18n({
locale: getLang.lang, // 目前語言
locales: getLang.langs // 支援的語言清單
});
export default t;
然後在應用的入口檔案中對Vue.js進行擴充:
import t from './t';
Vue.$t = Vue.prototype.$t = t;
這樣就把
$t
這個方法挂載到了Vue.js的全局。Vue執行個體中也可以通過
this.$t
通路到,使用上還是非常簡單的。
但是,對于大項目來說,把語言包都寫在代碼裡面,對維護并不友好。而且,靠它也解決不了我所用到的Vux元件的多語言化的問題。
是以最終,我選擇了vuex-i18n作為基礎。
(2)組織和處理語言包的工具——語言包怎麼組織,怎麼打包處理?
對于這個問題,我首先需要解決Vux第三方元件的多語言化問題。
首先,在語言包的組織方面,比較常見的是寫成JSON配置檔案。不過,我最終采用了Yaml這種格式,它支援将多語言字段寫在一起。比如:
config.yml
confirm:
zh-CN: 确認
en: confirm
而不是像下面那樣将一個字段的多語言拆成幾處,比如:
confirm: 确認
confirm: confirm
這樣帶來的好處就是,可以友善地對照一個字段的不同語言版本,而且要修改或删除某一個字段時,也可以在一處完成,無需切換。況且,Yaml檔案的文法也更加簡單明了,省去了JSON檔案必須寫雙引号、不可以出現注釋等諸多麻煩。
其次,在語言包的打包方面,我找到了vux-loader。它可以和現有的webpack配置結合,不僅能完成Vux元件多語言配置的打包,還允許在自定義的Vue元件中使用
<i18n>
标簽。比如,在自定義元件中我可以這麼寫:
<i18n>
confirm:
zh-CN: 确認
en: confirm
<i18n>
打包時,vux-loader會将
<i18n>
标簽中的多語言配置資訊導出至我們所配置的一個Yaml檔案中,而把
<i18n>
标簽從我們的自定義元件中移除。
那麼,對于Yaml檔案如何處理呢?可以用json-loader和yaml-loader。它們可以将Yaml檔案轉換成我們所需要的json格式,友善在JS函數中使用,就像這樣:
const componentsLocales = require('json-loader!yaml-loader!../../locales/components.yml'); // 這就得到了一個語言包的json格式
3、如何通知後端接口傳回何種語言的資料?
因為涉及到許多接口都要通知後端采用哪種語言,是以,我選擇了使用header頭的方式。在axios的interceptor中給請求統一添加了header頭:
Accept-Language
, 并把這個值的内容設定成前端所獲得應使用的語言(如,zh-CN 或 en 等)。這樣,就集中在一處把這個問題處理掉了。
三、具體實踐中的一些細節
1、擷取目前應該采用何種語言的getLang子產品的實作
import { getQueryObj } from '../utils/url';
import { setItem, getItem } from '../utils/storage';
const langs = ['zh-CN', 'en']; // 支援哪些語言
const defaultLang = 'en'; // 預設語言,暫時并沒有對外抛出
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, // 擷取到目前語言
langs // 所支援的語言清單
}
2、Vux元件的多語言包的配置
可以從Vux的官方github中找到
src/locales/all.yml
拷貝過來(同一目錄下的
src/locales/zh-CN.yml
、
src/locales/en.yml
分别是其中文部分和英文部分),根據你自己的需要略作修改即可。
然後在你的應用的應用的入口檔案中引入:
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']
就是指定你的應用支援哪幾種語言。
而
extractToFiles: 'src/locales/components.yml'
就是指定你的自定義元件中所用到的那些
<i18n>
标簽中的語言包資訊,應該導出到哪個Yaml檔案中。也就是說,你在各個自定義元件中使用的
<i18n>
标簽中的語言包資訊都會被vux-loader集中抽取到這個檔案中。
然後在應用的入口檔案中引入這個語言封包件:
const componentsLocales = require('json-loader!yaml-loader!../../locales/components.yml');
4、自定義元件内外文案的多語言化
(1)對于自定義元件内部的文案的多語言化資訊,寫在元件的
<i18n>
标簽中即可。同時,為了避免不同的自定義元件中多語言字段的命名沖突,在每個字段的名字前面加上以元件名-式的字首。
(2)對于頁面的标題、一些錯誤提示等文案,它們是出現在元件之外的,是以不适合寫在元件的
<i18n>
标簽中,是以我們單獨建立一個
global.yml
來存放這些全局性的多語言資訊。這些内容直接寫在
global.yml
中即可,并且,為了表面與其它的語言包字段相沖突,我們在每個字段的前面加上
global-
字首。
然後在應用的入口檔案中引入這個語言封包件:
const componentsLocales = require('json-loader!yaml-loader!../../locales/global.yml');
5、vuex-i18n的實作
在src/store/index.js檔案中:
import VuexI18n from 'vuex-i18n';
export default new Vuex.Store
中增加:
i18n: VuexI18n.store
在應用的入口檔案中:
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、圖檔的多語言化
對于圖檔中的文案資訊,多語言化主要有這麼兩種方式:一是根據不同的語言展示不同的圖檔;二是盡将文字從圖檔背景中分離出來,采用文字層加背景圖檔層的方式,這樣文字層就可以作為普通文本來實作多語言化了。都比較簡單,不再贅述。
7、在目前頁面通過按鈕切換目前語言後,如何更新目前頁面的内容?
如果你的應用并不需要在頁面内部切換語言版本,那麼直接通過URL中傳入不同的
lang
參數就可以了,并不涉及到此問題。
第一種方式:重新整理頁面
<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目前頁data中
lang
字段的變化,通過
v-if
局部重新整理某些相關元件:
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;
}
// 在這裡通過改變某個标志位 結合 v-if 來觸發某個局部元件的重新渲染
}
}
第三種方式:結合vuex派發全局的語言狀态,接收到狀态變化時進行更新,或者自己簡單地改寫vuex-i18n的實作。這種方式相對複雜一些。
具體根據自己的業務需求選擇。
8、Yaml中特殊字元的轉義
對于一些包含特殊字元的yaml鍵值,比如
[、]
等,需要進行轉義。轉的方式是給鍵值加上單引号引起來。
str: 'labor''s day'