你也許不知道的Vuejs - 最佳實踐(3)

yugasun發表於2019-03-05

by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但需要保留原作者和出處。

相信大多數使用 Vue 專案都會面臨國際化的問題,而 vue-i18n 便是國際化的不二之選,它用起來非常簡單,但是同時也會帶來一些問題和挑戰。本篇是個人在專案上國際化時一些經驗的總結,希望能在國際化的道路上幫到你。

基本使用

vue-i18n 官方文件介紹的很清楚,先在 src/lang 目錄下分別建立三個檔案 index.jszh.jsen.js, 然後在 index.js 中建立 i18n 例項並匯出,供 Vue 使用,程式碼如下:

import Vue from 'vue';
import VueI18n from 'vue-i18n';

import zhMsg from './zh';
import enMsg from './en';

Vue.use(VueI18n);

const messages = {
  zh: {
    ...zhMsg,
  },
  en: {
    ...enMsg,
  },
};

const i18n = new VueI18n({
  locale: 'zh',
  messages,
});

export default i18n;
複製程式碼

接著編寫語言包檔案:

// zh.js
export default {
  index: {
    header: {
      title: 'Vue-I18n 示例',
      subtitle: '你將學會如何使用 vue-i18n',
    },
  },
};

// en.js
export default {
  index: {
    header: {
      title: 'Vue-I18n Demo',
      subtitle: 'You will learn how to use vue-i18n',
    },
  },
};
複製程式碼

接下來在入口檔案 src/main.js 檔案中引入:

// ...
import i18n from './lang';

// ...
new Vue({
  i18n,
  router,
  el: '#app',
  template: '<App/>',
  components: { App },
});
複製程式碼

到這裡就配置好了,於是我們就可以在元件中使用了,使用方法如下 src/views/index.vue

<h1>{{ $t('index.header.title') }} </h1>
<h3>{{ $t('index.header.subtitle') }} </h3>
複製程式碼

看起來是不是很簡單,引入 vue-i18n 後,它會在 Vue 例項上掛載它提供的國際化的 api,比如 $t、$tc、$td 等格式化函式。使用時我們只需要在元件中直接呼叫就行,比如:this.$t

$t 接受兩個引數,第一個為我們在語言包中訪問的屬性路徑,各級屬性用 . 符號連結,第二個引數可以動態傳參,比如我們將 index.header.subtitle 的模板修改為 You will learn how to use {name},那麼我們可以像下面這樣使用:

$t('index.header.subtitle', {name: 'vue-i18n})
複製程式碼

小技巧

獲取鍵值集合

相信大家已經知道 vue-i18n 如何使用了,建議通讀 官方文件,自己動手配置熟悉下。

大多數情況下,我們只需要使用 $t 就可以滿足我們的需求了。但是當我們的的語言包層級越來越深時,你會發現屬性路徑越來越長,而且在某些元件內,需要書寫很多遍,比如上面的 index.header.titleindex.header.subtitle,它們的字首都是 index.header,在這裡寫兩遍我們能接受,如果是100遍呢?作為一名懶惰的程式設計師,怎麼能接受同樣的事情做3遍呢,更何況是100遍,簡直不敢想象!

其實我們可以先獲取字首的物件集合,然後通過這個集合物件來訪問,節省臃長的字首的重複書寫,使用的時候我們需要先在 src/views/index.vue 元件的 computed 中定義國際化集合:

export default {
  name: 'Index',
  computed: {
    indexMsg() {
      return this.$t('index.header')
    }
  },
};
複製程式碼

接下來修改下模板中的書寫方式:

<h1>{{ indexMsg.title }}</h1>
<h3>{{ indexMsg.subtitle }} </h3>
複製程式碼

注意:這裡必須定義為計算屬性,不然在切換語言時,檢視國際化將無法自動更新。

多元化的訊息

曾經在做一個登入錯誤資訊提示的時候,遇到個需求:使用者多次輸入錯誤後,會提示多長時間後重試,但是介面只是返回個剩餘秒數,需要根據這個秒數計算出剩餘的時分秒,當大於1小時時,提示 請 xx 小時後重試,當小於1小時時,提示 請 xx 分 xx 秒後重試。一般碰到這種需求,我們是肯定需要結合模板變數來實現,最簡單的方式就是直接定義兩個國際化鍵值,比如 msg1,msg2,然後通過計算出的小時數來做 if 判斷就行。

但是當專案越來越龐大,專案中類似的需求越來越多的時候,你會發現你的語言包的鍵值對越來越多,到了最後,取個屬性名就要想半天,不知道大家有沒有跟我類似的痛點,所以我對於這種類似需求用 $tc 函式來實現的。

$tc 函式允許我們一個國際化鍵值可以通過管道符 | 來分割多元化的資訊,如下:

export default {
  // ...
  login: {
    tips: '請{minute}分{second}秒後再重試 | 請{hour}小時後再重試',
  },
};
複製程式碼

然後在模板中使用:

<p>{{ $tc('login.tips', 1, {minute: 30, second: 29}) }}</p>
<p>{{ $tc('login.tips', 2, {hour: 1}) }}</p>
複製程式碼

對於上面的需求,我只需要根據介面返回的秒數,計算下 hour 值,然後寫一個三目運算就解決了:

hour >= 1 ? this.$tc('login.tips', 2, {hour: 1}) : this.$tc('login.tips', 1, {minute: 30, second: 29})
複製程式碼

可選陣列訊息

其實對於可選的國際化訊息需求,我們還可以通過陣列來實現,比如我們的某個訂單狀態需要國際化,0-5 分別對應不同的狀態,我們只需要定義簡單的陣列就可以搞定了。

首先定義語言包:

export default {
  //...
  order: {
    status: [
      '待付款',
      '待發貨',
      '已發貨',
      '已簽收',
      '已取消',
    ],
  },
};
複製程式碼

然後我們只需要根據不同狀態,來讀取鍵值就行:

<span>{{ $t(`order.status[orderStatus]`) }}</span>

<script>
export default {
  name: 'Index',
  data() {
    return {
      orderStatus: 3,
    };
  },
  //...
};
</script>
複製程式碼

當然這裡也可以通過多元化的方式來實現,大家可以自行嘗試下。

模組化

隨著專案越來越大,你會發現 src/lang/zh.js 檔案越來越臃腫,每次改個國際化文案,需要在上千行的物件中,找半天對應的鍵值對,而且那深不可測的屬性層級,著實讓人眼花繚亂。

既然 src/lang/zh.jsjs 檔案,我們當然就可以對其進行拆分,然後把不同的模組拆分成獨立的 js 檔案進行維護,是不是找起來會輕鬆好多,比如我們嘗試將例項中的 zh.js 產分成 src/lang/zh-modules/index.jssrc/lang/zh-modules/login.jssrc/lang/zh-modules/order.js 三個模組檔案:

// index.js
export default {
  index: {
    header: {
      title: 'Vue-I18n 示例',
      subtitle: '你將學會如何使用 vue-i18n',
    },
  },
};


// login.js
export default {
  login: {
    tips: '請{minute}分{second}秒後再重試 | 請{hour}小時後再重試',
  },
};

// order.js
export default {
  order: {
    status: [
      '待付款',
      '待發貨',
      '已發貨',
      '已簽收',
      '已取消',
    ],
  },
};
複製程式碼

然後在 src/lang/zh.js 檔案中一次引入:

import index from './zh-modules/index';
import login from './zh-modules/login';
import order from './zh-modules/order';

export default {
  ...index,
  ...login,
  ...order,
};
複製程式碼

這樣就變得清晰很多,下次產品需要你修改登入相關的文案,你只需要修改 login.js 模組就行。

但是按模組拆分後,有人就會有疑問了,我這麼拆分後,語言模組檔案會隨著語言種類成倍數增加,應該怎麼辦?

這裡就說說我的解決辦法,就是將語言檔案按模組合併。所謂 天下大勢,合久必分,分久必合。我們將相同的模組按檔案合併起來就可以了,比如 index 模組:

const zh = {
  index: {
    header: {
      title: 'Vue-I18n 示例',
      subtitle: '你將學會如何使用 vue-i18n',
    },
  },
};
const en = {
  index: {
    header: {
      title: 'Vue-I18n Demo',
      subtitle: 'You will learn how to use vue-i18n',
    },
  },
}
export default {
  zh,
  en,
};
複製程式碼

然後只需要在 src/lang/index.js 引入,稍作修改就行:

import Vue from 'vue';
import VueI18n from 'vue-i18n';

import indexMsg from './modules/index';
import loginMsg from './modules/login';
import orderMsg from './modules/order';

Vue.use(VueI18n);

const messages = {
  zh: {
    ...indexMsg.zh,
    ...loginMsg.zh,
    ...orderMsg.zh,
  },
  en: {
    ...indexMsg.en,
    ...loginMsg.en,
    ...orderMsg.en,
  },
};

const i18n = new VueI18n({
  locale: 'zh',
  messages,
});

export default i18n;
複製程式碼

此時你會發現我們已經不需要 src/lang/zh.jssrc/lang/en.js 檔案了。而且這裡有個好處是,我們在讓翻譯幫忙的事後,只需要在同一個檔案中對我們的相同鍵值進行修改和編輯了,是不是方便很多,趕緊動手嘗試下吧~

總結

本文只是個人在開發過程中,關於國際化方案的經驗總結,我相信大家也有很多提高效率的方案。希望本篇能夠解決你在開發中遇到的一些疑惑和痛點,也非常歡迎評論或來信交流你的優化方案。

原始碼在此

專題目錄

You-May-Not-Know-Vuejs

相關文章