國際化是什麼?
國際化 對應的英文單詞為 Internationalization,又稱 i18n
:
i
為單詞的 【第一個】 字母18
為 【i
和n
之間】 單詞的個數n
代表這個單詞的 【最後一個】 字母
如果你的專案是 Vue
,那麼相信你在實現國際化功能時,也必不可少的會使用到 vue-i18n
這個庫,接下來本文也是透過這個庫搭配 Vue
實現最基本的國際化功能,但關注點並不是如何使用這個庫,而是在實現的過程中思考 可最佳化的點。
實現基本國際化功能
這裡就不再多餘演示 demo
專案的建立過程了,並且文中只演示最基本的 中英文 切換,好了現在直奔核心吧!
整合 vue-i18n
安裝依賴
熟悉的命令:npm install vue-i18n -S
配置 vue-i18n
- 在
src
目錄下建立language
目錄用於保持和語言切換相關的內容 - 在
language
目錄下建立lang
目錄用於儲存不同語言的對映關係,如中文對應zh.js
、英文對應en.js
等 在
language
目錄下建立index.js
作為預設匯出,並在其中建立i18n
物件import { createI18n } from "vue-i18n"; import zh from './lang/zh'; import en from './lang/en'; const i18n = createI18n({ legacy: false, locale: "zh", // 初始化配置語言 messages: { zh, en, }, }); export default i18n;
main.js 註冊 i18n
內容非常簡單,直接上程式碼:
import { createApp } from "vue"; import i18n from "./language"; import store from "./store"; import App from "./App.vue"; createApp(App) .use(store) .use(i18n) .mount("#app");
實際上在透過
use(i18n)
時,會呼叫i18n.install()
方法,大概內容如下:- 透過
app.provide(app.__VUE_I18N_SYMBOL__, i18n)
將i18n
物件提供給應用中的所有後代元件可透過inject
注入 透過
app.config.globalProperties.xxx = xxx
的方式為應用新增 全域性屬性/方法,實際上是對Vue2
中Vue.prototype
使用方式的一種替代- 常見全域性屬性,如
$i18n
透過app.config.globalProperties.$i18n = i18n
新增到全域性 - 常見全域性方法,如:
$t, $rt, $d, $n, $tm
透過Object.defineProperty(app.config.globalProperties, `$${method}`, desc)
新增到全域性
- 常見全域性屬性,如
- 透過
aplly(...)
方法註冊常用的 全域性指令v-t
和 全域性元件i18n
等 在根元件解除安裝時移除/釋放
i18n
相關內容const unmountApp = app.unmount; app.unmount = () => { i18n.dispose(); unmountApp(); };
註冊
vue-devtools
的相關外掛根據資料資訊填充國際化內容
頁面渲染
假設需要渲染如下資料對應的列表,並且要實現國際化:
const data = [ { url: vueImg, title: 'Vue', describe: '漸進式 JavaScript 框架' }, { url: reactImg, title: 'React', describe: '用於構建使用者介面的 JavaScript 庫' }, { url: angularImg, title: 'Angular', describe: '現代 Web 開發平臺' }, { url: nodeImg, title: 'Node', describe: 'Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行時' }, { url: webpackImg, title: 'Webpack', describe: 'webpack 是一個用於現代 JavaScript 應用程式的靜態模組打包工具' }, ];
對應
App.vue
元件模板內容如下:<template> <button class="btn" @click="changeLang">{{ $t("中/英") }}</button> <List :data="data" /> </template>
對應的頁面效果如下:
填充 lang 目錄下對映關係
在 lang/zh.js
檔案中:
export default {
"漸進式 JavaScript 框架": "漸進式 JavaScript 框架",
"用於構建使用者介面的 JavaScript 庫": "用於構建使用者介面的 JavaScript 庫",
"現代 Web 開發平臺": "現代 Web 開發平臺",
"Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行時":
"Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行時",
"webpack 是一個用於現代 JavaScript 應用程式的靜態模組打包工具":
"webpack 是一個用於現代 JavaScript 應用程式的靜態模組打包工具",
"中/英": "中/英",
};
在 lang/zh.js
檔案中:
export default {
"漸進式 JavaScript 框架": "Progressive JavaScript framework",
"用於構建使用者介面的 JavaScript 庫":
"JavaScript library for building user interface",
"現代 Web 開發平臺": "Modern web development platform",
"Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行時":
"Node.js is a JavaScript runtime based on the chrome V8 engine",
"webpack 是一個用於現代 JavaScript 應用程式的靜態模組打包工具":
"Webpack is a static module packaging tool for modern JavaScript applications",
"中/英": "Chinese / English",
};
在 <List />
元件中進行翻譯處理
翻譯處理可透過如下方式處理:
- 使用
$t(...)
方法 - 使用
v-t
指令 - 使用
<i18n-t></i18n-t>
元件
這裡選擇第一種,因為它更靈活,能使用的範圍也更廣,指令和元件形式限定在了 template
中:
效果演示
最佳化 i18n 配置
基於以上簡單的例子,已經能夠實現了國際化切換功能,但其中需要考慮的最佳化點還不少,下面的內容僅屬於 拋轉引玉,不一定全面。
最佳化翻譯檔案中的 key
現在很明顯的一點,就是 zh.js、en.js
檔案中用於對映的 key
太長了,導致整個檔案看起來會很多、很亂,因此我們可以將對應的 key
進行精簡,如下:
// zh.js
export default {
"Vue 簡介": "漸進式 JavaScript 框架",
"React 簡介": "用於構建使用者介面的 JavaScript 庫",
"Angular 簡介": "現代 Web 開發平臺",
"Node 簡介": "Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行時",
"Webpack 簡介":
"webpack 是一個用於現代 JavaScript 應用程式的靜態模組打包工具",
"中/英": "中/英",
};
// en.js
export default {
"Vue 簡介": "Progressive JavaScript framework",
"React 簡介": "JavaScript library for building user interface",
"Angular 簡介": "Modern web development platform",
"Node 簡介": "Node.js is a JavaScript runtime based on the chrome V8 engine",
"Webpack 簡介":
"Webpack is a static module packaging tool for modern JavaScript applications",
"中/英": "Chinese / English",
};
那麼對應到外部傳入需要渲染的資料來源 data
就可以簡寫為:
const data = [
{
url: vueImg,
title: "Vue",
describe: "Vue 簡介",
},
{
url: reactImg,
title: "React",
describe: "React 簡介",
},
{
url: angularImg,
title: "Angular",
describe: "Angular 簡介",
},
{
url: nodeImg,
title: "Node",
describe: "Node 簡介",
},
{
url: webpackImg,
title: "Webpack",
describe: "Webpack 簡介",
},
];
另一種精簡方式就是將key
用對應的類似於 變數命名 的方式來定義,但我個人不是很喜歡這種方式,首先在語義上很難直接讀出相關資訊,而且很難用一兩個英文單詞去概括文字內容,而且在後期需要排查對應問題並需要定位tmeplate
時是極其不方便.
轉換翻譯檔案型別
.js 轉 .json
上述的翻譯檔案是 .js
檔案,因此,為了能夠讓其能被其他檔案匯入,我們不得不在檔案中使用 export defualt
或 export
將對應檔案內容向外匯出,但其實我們可以將 .js
檔案轉換為 .json
檔案直接使用,如下:
excel 轉 .json
在實際專案中翻譯的內容通常是由業務專門找對應的翻譯人員提供的,而真正到了開發者手中往往就是一個 excel
型別的表格檔案,如果我們使用的是前面的純 json
方式,免不了要自己一個一個從表格中複製對應的內容到我們對應的 .json
檔案中,而且是分別填充到 zh.json
和 en.json
中,值得注意的是現在才是僅支援兩個國家的語言,如果後續支援的國家變多,那麼手動複製的方式豈不是要
因此,最好的做法是我們根據業務方提供的表格自動轉成 json
格式的資料,避免不必要的手動操作,用命令幫我們處理這個內容:
- 安裝依賴
npm install xlsx-to-json
將對應的轉換操作封裝
excel2json.js
檔案中,基於第三方庫簡單封裝即可const xlsx2json = require("xlsx-to-json"); const path = require("path"); xlsx2json( { input: path.join(__dirname, "./i18n.xlsx"), output: path.join(__dirname, "./i18n.json"), }, function (err, result) { if (err) { console.error(err); } else { console.log(result); } } );
修改
i18n
配置的入口檔案src\language\index.js
import { createI18n } from "vue-i18n"; import i18njson from "./i18n.json"; // 動態獲取 message 資訊 function getMessage() { const messages = { zh: {}, en: {} }; i18njson.forEach(({Short, Chinese, English}) => { messages.zh[Short] = Chinese; messages.en[Short] = English; }); return messages; } const i18n = createI18n({ legacy: false, locale: "zh", // 初始化配置語言 messages: getMessage(), }); export default i18n;
提供
i18n.xlsx
檔案作為資料來源在
package.json
問檔案中新增對應轉換命令"scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", "i18n": "node ./src/language/excel2json.js" }
具體效果如下:
升級為 i18n 系統
上面已經將對應的翻譯包從 *.xlsx
轉成了 *.json
的形式,這樣一定程度上能減少重複勞動力,但在 複用/協作 方面還是不夠理想,因此可以從更高的維度將這整個內容升級到 i18n
系統,並提供對應的翻譯包上傳、自動解析、去重、新增名稱空間等功能,再加上對應的列表管理功能,重點是可供多人員、多系統進行 複用/協作,對前端來講就可以透過 介面 獲取對應的翻譯包資料,也能減少前端最終構建產物的體積。
處理翻譯檔案中重複的內容
什麼叫重複的內容呢?其實很簡單,比如有個文字內容為 確認 按鈕,它在 A
頁面需要翻譯為 Confirm
,它在 B
頁面需要翻譯為 OK
,而它的中文內容就是 確認
,意味著它對應的資料內容為:
[
{ "Short": "確認", "Chinese": "確認", "English": "Confirm" },
{ "Short": "確認", "Chinese": "確認", "English": "OK" }
]
但這樣其實是不行的,這樣最終會被後面的內容覆蓋掉,即 A、B
頁面最終的翻譯內容都為 OK
,因為資料中的 Short
其實就是最終的不同語言對映的 key
,如下:
const messages = {
zh: {
"確認": "確認",
"確認": "確認"
},
en: {
"確認": "Confirm",
"確認": "OK"
}
};
既然這樣,那麼其實我們只要為不同的翻譯內容設定不同的 Short
值即可,如下:
[
{ "Short": "確認1", "Chinese": "確認", "English": "Confirm" },
{ "Short": "確認2", "Chinese": "確認", "English": "OK" }
]
這樣變動小,並且也沒有丟失掉原本的語義。
考慮不同語言的樣式
由於不同語言的表現形式不同,內容長度也不一致,因此在前端進行展示時,就必須要考慮到最終的顯式問題,否則一旦切換語言環境那麼一定會導致頁面的佈局展示出現問題,處理方式無非幾種:
- 允許文字內容換行展示,在文字發生換行時,要透過
CSS
設定按完整詞換行 - 不允許換行的,就要控制固定寬度,超出部分打點展示,滑鼠移入展示全部內容等
- 單獨為不同語言環境設定樣式,具體還是得看展示需求,如需要考慮不同語言環境下文字的對齊方式、文字間距等
針對難以處理的翻譯內容,可以透過和業務溝通是否可以替換翻譯內容、縮減文字長度等等
考慮後端介面語言環境變更
一個專案的國際化不可能都是前端來實現的,一些介面動態返回的內容也是需要後端去處理的,通常介面的請求頭中會儲存一個用於標識當前頁面語言環境的欄位,然後再決定返回給前端頁面的具體內容。
基於前面處理的國家化切換功能,本身是會基於 vue
的響應式來切換翻譯內容的,即不會重新整理頁面,因此在切換對應翻譯內容後,同樣需要修改後續介面請求頭中的語言環境。
但這樣還是有問題的,已經透過介面返回的資料內容,此時沒有辦法切換成對應的翻譯內容,因為當前的國際化切換是基於頁面的變動,但基於介面變動的部分還沒重新請求獲取新的內容,那怎麼處理呢?
- 前端切換語言環境後,重新重新整理頁面,讓介面也重新獲取新的內容
- 後端在返回資料時,將對應的不同語言環境的翻譯內容一起返回,由前端根據語言環境決定如何渲染
將所有的翻譯內容全部交由前端管理,一開始就初始化好各個語言環境對應的翻譯內容
最後
以上內容是基於國際化功能的一點思考,文中對應的思考點是筆者自己在專案中遇到的點,並不一定適合所有專案,當然也期待評論區給出更多、更好的方案。
本文參與了SegmentFault 思否寫作挑戰賽,歡迎正在閱讀的你也加入。