Vue 項目前端多語言方案

前端的國際化是一個比較常見的需求。但網上關於這一方面的直接可用的方案卻不多。最近剛做了一版基於 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.ymlsrc/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'
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6v8D13hUzegkIHdc3AobCA