Vue I18n 實作多國語系

不知道為什麼,實作多國語系這部分一直都很吸引我。
一直沒有機會去做做看,卻在這個寫文靈感極度枯竭的時候忽然想起來。
爬文之後發現用 Vue I18n 真的很方便,在此紀錄一下。

官網

Vue I18n

CDN 用法

首先介紹 CDN 的用法。

基本設定

將 vue-i18n.js 引用在 <body> 的最後;也要引用 vue 。

1
2
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-i18n/dist/vue-i18n.js"></script>

接著來設定各國語言的內容,設定值第一層的 key 是語系名稱, value 就是這個語系的內容。
可以依據語意來安排結構,像是「 header 的 title 」。
每個語系都有同樣結構的 key ,如果不相同的話,切換到該語系可能就會找不到詞彙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const messages = {
en: {
header: {
title: "Website"
},
main: {
image: {
text: "fake image",
font: "lobster"
}
}
},
tw: {
header: {
title: "網站"
},
main: {
image: {
text: "假圖",
font: "noto"
}
}
},
jp: {
header: {
title: "サイト"
},
main: {
image: {
text: "フェイク",
font: "noto"
}
}
}
};

再來就將設定值作為 VueI18n 實體的參數。

1
2
3
4
5
6
7
8
9
10
// 如果設定的物件不叫做 messages 就要這樣寫
const i18n = new VueI18n({
locale: 'tw', // 語系可以先指定或之後指定
messages: messages
});
// 不然就可以簡化成這樣
const i18n = new VueI18n({
locale: 'tw',
messages
});

然後將 VueI18n 實體 放入 Vue 實體中。

1
2
3
4
5
6
7
8
9
let app = new Vue({
el: "#app",
i18n: i18n
});
// 同上,也可以寫成這樣
let app = new Vue({
el: "#app",
i18n
});

這樣就設定完成了。

使用

直接使用

在 HTML 中以 $t(“header.title”) 這樣的格式來取得內容。

1
<h1 class="h1">{{ $t("header.title") }}</h1>

在字串裡面的用法也一樣。

1
<img class="img" :src="`https://fakeimg.pl/460x200/?text=${$t('main.image.text')}&font=${$t('main.image.font')}`">

在 JavaScript 中則是例如:

1
this.$t("header.title")

想切換語系的話就直接更改 locale 。

1
this.$i18n.locale = 'jp';

帶入參數

設定值的字串是可以帶參數的,例如句子中帶有稱呼的時候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const messages = {
en: {
main: {
greeting: "{name}, Hi!",
},
tw: {
main: {
greeting: "{name},你好!",
},
jp: {
main: {
greeting: "{name}さん、こんにちは",
}
}
};

用物件的方式帶入參數。

1
<h2 class="h2">{{$t("main.greeting", {name: "Lynn"})}}</h2>

帶入數量

有些語言的後綴可能會隨著數量不同而改變,這時就可以設定不同數量時的表示法。
參數的帶入方式同上,而不同數量的說法以 | 分開。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const messages = {
en: {
main: {
sheep: "no sheep | 1 sheep | {value} sheeps"
}
},
tw: {
main: {
sheep: "沒有綿羊 | 1 隻綿羊 | {value} 隻綿羊"
}
},
jp: {
main: {
sheep: "羊がいない | 羊 1 匹 | 羊 {value} 匹"
}
}
}

使用方式則如下:

1
2
3
4
// 要小心這邊用的是 $tc
<li>{{ $tc("main.sheep", 0 , { value: 0 }) }}</li>
<li>{{ $tc("main.sheep", 1 , { value: 1 }) }}</li>
<li>{{ $tc("main.sheep", 2 , { value: 2 }) }}</li>

另外如果遇到其它複數規則的語言,可以參考文件:自定義複數

範例

這是用以上的內容寫的範例。

See the Pen Vue I18n CDN by Lynn (@clhuang224) on CodePen.

Vue Cli 用法

Vue Cli 3.x 以上的版本可以直接用指令讓 Cli 將 I18n 設定好。

1
vue add i18n

或者可以自行安裝。

1
npm install vue-i18n

用 Cli 加入模組的話,會有幾個要回答的問題。

1
2
3
4
5
6
7
8
# 專案預設的地區
? The locale of project localization. zh-TW
# 設定當前地區的語言沒有詞彙時,要用什麼地區來代替
? The fallback locale of project localization. zh-TW
# 語系的檔案要放在什麼資料夾
? The directory where store localization messages of project. It`s stored under `src` directory. locales
# 能否在單一的元件設定語系內容
? Enable locale messages in Single file components ? Yes

設定好以後,專案中會產生或更動一些檔案:

1
2
3
4
5
6
7
8
9
10
11
src
├── .env
├── i18n.js
├── main.js
├── locales
| └── zh-TW.json
├── components
| └── HelloI18n.vue
├── vue.config.js
├── package.json
└── package-lock.json

.env

.env 宣告了兩個環境變數給其他檔案使用,如果要更改預設地區跟 fallback 地區就可以來這邊改。

1
2
VUE_APP_I18N_LOCALE=zh-TW
VUE_APP_I18N_FALLBACK_LOCALE=zh-TW

i18n.js

i18n.js 比較長一點,但就是在設定基本的內容,跟 Vue router 的設定檔案類似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import Vue from "vue";
import VueI18n from "vue-i18n";

// 使用 Vue I18n(這行必須被寫在 Vue 實體被宣告前)
Vue.use(VueI18n);

/**
* 將 locales/__.json 中的內容取出來轉成 messages 的結構
* @returns {Object} VueI18n 實體的 messages 內容
*/
function loadLocaleMessages() {
// Webpack 引入一堆檔案的方法
const locales = require.context(
// 開始比對的目錄
"./locales",
// 是否比對子目錄
true,
// 用正則表達式篩選檔案名稱,限定檔名為大小寫英數字、橫線底線逗號及空白的 JSON 檔
/[A-Za-z0-9-_,\s]+\.json$/i
);
const messages = {};
// 用每個檔名去比對並作為 key
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key);
}
});
return messages;
}

// 指定 locale 、 fallbackLocale 和 messages
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || "en",
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en",
messages: loadLocaleMessages()
});

話說中間比對檔名的部分我有點不懂為什麼是這樣子的規則,所以做了一個小實驗。
首先在 src/locales 新增幾個檔案:

  • jp .json
  • tw .json
  • tw,.json
  • tw,copy.json
  • tw,tw.json
  • tw.json
    再加上 console.log() 。
1
2
3
4
5
6
7
8
9
10
locales.keys().forEach(key => {
console.log("Filename: ", key);
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
console.log("matched: ", matched);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key);
console.log("messages: ", messages);
}
});

最後跑出來結果是這樣:

實驗結果

可以觀察到的規則是:

  • 不接受空白鍵或逗號結尾的檔名
  • 有空白鍵或逗號隔開時,會選擇符號前的文字作為 key
  • key 重複時不會被覆蓋
  • 所以基本上取名只要用大小寫英數字、橫線及底線就可以

main.js

main.js 的部分就是把 VueI18n 放入 Vue 實體中。

1
2
3
4
5
6
7
8
9
10
import Vue from "vue";
import App from "./App.vue";
import i18n from "./i18n"; // 多了這行

Vue.config.productionTip = false;

new Vue({
i18n, // 和這行
render: h => h(App)
}).$mount("#app");

locales/__.json

預設的語系檔案。

1
2
3
{
"message": "hello i18n !!"
}

components/HelloI18n.vue

示範元件內使用 標籤的檔案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<p>{{ $t("hello") }}</p>
</template>

<script>
export default {
name: "HelloI18n"
};
</script>

<i18n>
{
"en": {
"hello": "Hello i18n in SFC!"
}
}
</i18n>

vue.config.js

i18n 相關的專案設定。

1
2
3
4
5
6
7
8
9
10
module.exports = {
pluginOptions: {
i18n: {
locale: "zh-TW",
fallbackLocale: "zh-TW",
localeDir: "locales",
enableInSFC: true
}
}
};

結語

以上大概就是有關 Vue I18n 的內容,感謝閱讀到這裡:)

參考資料