i18n 都是怎麼做的
Category: Web
i18n (Internationalization) 是一項工程,目的是讓 app 能支援不同語系這件事。除了語言之外,常常還要考慮不同國家間的差異,包括單位(貨幣、溫度、距離等)、時間與日期表示法、寫作系統(例如文法、斷句、阿拉伯文的 rtl)。支援的語言多,能接觸到的使用者就多了(或者也可能是反過來的順序)
從底層就要支援 Unicode
不只在前端顯示,包含資料傳到後端、資料庫、CMS、response 或錯誤訊息等都要能處理 Unicode,不能有其中一環爆炸
Locale
Locale (地區) 是由 Language Code(ISO 639 定義,例如 en、zh)加上可選的 Country Code(ISO 3166 定義,例如 uk、us、tw)組成
產品的 prototype 可以先做英文或習慣的語言,再根據預計要打的市場、使用者的反饋資料等,決定加上其他語言、地區,甚至貨幣、日期、地址、稅率、電話、法律條款等。重要的原則是先做需要的,預留未來可能擴充的,不過度設計不知道需不需要的
誰應該進來做 i18n?
幾乎所有人,包含前後端、QA、PM、Designer
- 前後端:建好 UI 用的翻譯檔、locale 的 metadata、溝通 API 格式(header 帶 Accept-Language 或 request 帶 locale 等)
- PM:決定產品第一版要支援哪些語言或國家、日期貨幣等要不要支援、哪些功能要在地化、維護某個需求的成本估算
- Designer(UI/UX):Component 或 Layout 能否容納不同翻譯的長度
- QA:文字是否有被截斷、特殊字元是否正常、有沒有亂碼、翻譯有沒有漏掉等
如果是再大一點的東西,甚至 Legal、Data、Marketing、CSM 什麼的都要加入,最終會發現 i18n 是一個跨團隊的產品能力,要解決的是一個工程問題,並不是單一個團隊的孤立工作
前端通常怎麼做 i18n?
建立翻譯檔
最簡單的架構就是把 app 裡面的文字抽出來,變成 key/string 的 pair,再根據 Locale 選對應文字
通常不是寫 <button>Submit</button>,而是 <button>{t("button.submit")}</button>,並建立翻譯檔像是
{
"button.submit": "Submit",
"button.cancel": "Cancel",
...
}
之類的
決定 key 叫什麼會是個問題,跟寫程式時決定變數名字一樣,通常就是用結構化/巢狀路徑命名。至於是誰應該決定 key 的名字?一樣,大家一起開會決定吧
以某個語言作為 Single Source of Truth
當多個翻譯檔已經建好了,後續的維護可以用這個原則
假設以英文為準好了,新增翻譯時僅須加上英文翻譯檔的 key 與翻譯字串。其他語言的翻譯檔則不用動,或是僅需新增 key
這樣做的原因,是如果 CI/CD 之類的流程有翻譯管理平台 / 自動翻譯機器人之類的,會有以下情境
- 新增按鈕,翻譯平台或機器人會看到「哎呀,
zh-TW.json裡面這個 key 的內容居然跟en.json長得一樣(或它是空的)!看來這東西還沒翻譯過,得送去翻譯」 - 改動英文翻譯,翻譯平台或機器人會看到「哎呀,
en.json裡面這個 key 的內容更新了,但中文還是舊的對應,看來需要重新翻譯」
這樣做可以確保幾件事:
- 缺翻譯的 fallback
- 人工翻譯不被翻譯平台或機器人蓋掉
- 工程師僅需維護一個翻譯檔案
i18n Library
有許多現成 Library 把前端的 i18n 工作處理好,常見的有 i18next、react-i18next、next-translate 之類的,選擇的話就是看你的需求、App 框架等來選
Library 處理的功能:
- 文字翻譯:提供一個像
t('key')的函式,輸入 key 就會根據目前選擇的語系吐出對應的文字 - 語系切換:提供動態切換語系的 API(例如
i18n.changeLanguage('zh-TW')),切換後畫面通常能自動或手動更新,讓 i18n 根據 locale 決定 UI 應該顯示什麼 - 變數代換:
t('welcome', { name: 'Alex' })-> 「歡迎回來,Alex!」 - 複數處理:根據數字決定要用單數還複數形式,並且不同語言間的複數可以很複雜
- 格式化:處理不同國家地區的數字、日期、時間、貨幣格式
- Fallback:缺 key 時不至於使程式崩潰
Testing and Performance
當 i18n 的架構和維護流程都上軌道後,支援的語系跟檔案越來越多就會需要
- Unit Test:針對 Pluralization、日期、時間、貨幣格式化等邏輯進行測試
- Static Analysis:在 CI/CD 階段自動比對:檔案間 key 有沒有完全對齊?有沒有 JSON 裡找不到的 Key?有沒有沒用到的冗 key?
- Lazy Loading:初次載入或切換語系時才透過非同步去下載對應語系的 JSON 檔案
- 快取與分割:將共用或高頻使用的語系先載入,較少用到的語系或大型頁面的翻譯字典則採用動態載入與快取策略,在「省流量」與「流暢度」之間取得平衡