命運多舛,在當了幾個月待業廢物後,總算找到風氣跟待遇都不錯的新工作,如果寫扣寫到懷疑人生就離職換工作吧。也為了新工作開始學習 Angular,解開使用三大前端框架的成就,這次來記錄 Angular i18n 套件 ngx-translate 上所碰到的問題與解決方法。
TL;DR
主要是實務上需要使用到
1
| setTranslation(lang: string, translations: Object, shouldMerge: boolean = false)
|
這個 method 時,所歸納的要點:
setTranslation()
第三個參數 shouldMerge
結果不符預期,使用 Lodash 的 _.merge()
自幹解決
setTranslation()
一定要在 use()
與 setDefaultLang()
之前完成,否則會吃到舊的翻譯檔,也就是不會熱更新
- 由於
setTranslation()
沒有 return Observerble,因此用 setTimeout(0)
來實現第二點的非同步。
心路歷程
基本設置
用 Angular-cli 新建一個 project,照著 ngx-translate 文件 npm install --save
安裝 @ngx-translate/core
與 @ngx-translate/http-loader
,在 app,module.ts 裡接好 loader
app.module.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { HttpClientModule, HttpClient} from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http); }
imports: [ HttpClientModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [HttpClient] } }) ],
|
寫兩份翻譯檔,放在 src/assets/i18n/
資料夾底下
en.json1 2 3 4
| { "WELCOME_TO": "Welcome to", "SELECT_LANGUAGE": "Select a language" }
|
zh-TW.json1 2 3 4
| { "WELCOME_TO": "歡迎使用", "SELECT_LANGUAGE": "選擇語言" }
|
在 template 中寫個簡單的語言切換選單
app.component.html1 2 3 4 5 6 7 8 9 10 11 12
| <div style="text-align:center"> <h1> {{ 'WELCOME_TO' | translate }} {{ title }}! </h1> <label for="lang"> {{ 'SELECT_LANGUAGE' | translate }}: <select #langSelect name="lang" (change)="translate.use(langSelect.value)"> <option *ngFor="let lang of translate.getLangs()" [value]="lang" [selected]="lang === translate.currentLang">{{ lang }}</option> </select> </label> </div>
|
在 constructor 中使用 TranslateService
app.component.ts1 2 3 4 5 6 7 8 9 10
| import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
title = 'ngx-translate'; constructor( public translate: TranslateService ) { translate.addLangs(['en', 'zh-TW']); translate.setDefaultLang('en'); }
|
ng serve
跑起來,就能看到我們簡單的 i18n 網頁
語系判別 & 儲存設定
我們也能將選取的語言值儲存到 localStorage
,下次開啟網頁時就能沿用,並使用 getBrowserCultureLang()
做預設 fallback,改完的 constructor 長這樣:
app.component.ts1 2 3 4 5 6 7 8 9 10 11 12 13
| translate.addLangs(['en', 'zh-TW']); translate.onLangChange.subscribe((event: LangChangeEvent) => { const {lang} = event; localStorage.setItem('useLang', lang); }); translate.setDefaultLang('en'); const useLang = localStorage.getItem('useLang'); if (useLang) { translate.use(useLang); } else { const browserLang = translate.getBrowserCultureLang(); translate.use( translate.langs.includes(browserLang) ? browserLang : 'en'); }
|
Merge 額外的翻譯
真正的需求來了,如果想將選單裡的「en」、「zh-TW」也翻成「English」與「正體中文」:
app.component.html1 2
| <option *ngFor="let lang of translate.getLangs()" [value]="lang" [selected]="lang === translate.currentLang">{{ lang | translate }}</option>
|
不想把翻譯重複寫在 json 裡,另外 merge key-value 進去可以嗎?看文件似乎使用 setTranslate()
並設定第三個參數為 true
就能達到我們要的結果
app.component.ts1 2 3 4 5 6 7 8 9 10 11
| translate.onLangChange.subscribe((event: LangChangeEvent) => { const {lang} = event; localStorage.setItem('useLang', lang); }); for (const lang of translate.getLangs() ) { translate.setTranslation(lang, { 'en': 'English', 'zh-TW': '正體中文' }, true); }
|
實際上卻不是這樣…
原有的翻譯不見了,只剩下選單的翻譯,這行為看起來是 replace 而非 merge,文件居然陰我!? 查了 Github 的 issue 發現一堆人發了類似問題,也都沒有回應根本被放生WTF…
山不轉路轉,只要直接給它 merge 好的翻譯就可以了吧?因此把 Lodash 拉進來改寫…
app.component.ts1 2 3 4 5 6 7 8 9 10
| import { merge } from 'lodash';
for (const lang of translate.getLangs() ) { const json = require(`../assets/i18n/${lang}.json`); const mergedLocale = merge(json, { 'en': 'English', 'zh-TW': '正體中文' }); translate.setTranslation(lang, mergedLocale); }
|
先後順序
merge 後的翻譯 log 出來是正確的,結果只有中文頁 OK 但英文頁沒翻譯到選單?撞牆了一段時間才發現順序的重要性: **必須先執行完 setTranslation()
,再執行 setDefaultLang()
與 use()
** 。由於 setTranslation()
並沒有回傳值或是 Observerble,只好利用 JS 本身的 Queue:將 setDefaultLang()
與 use()
包在 setTimeout(0)
裡面,就能保證非同步順序:
app.component.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| translate.addLangs(['en', 'zh-TW']); translate.onLangChange.subscribe((event: LangChangeEvent) => { const {lang} = event; localStorage.setItem('useLang', lang); }); for (const lang of translate.getLangs() ) { const json = require(`../assets/i18n/${lang}.json`); const mergedLocale = merge(json, { 'en': 'English', 'zh-TW': '正體中文' }); translate.setTranslation(lang, mergedLocale); } setTimeout(() => { translate.setDefaultLang('en'); const useLang = localStorage.getItem('useLang'); if (useLang) { translate.use(useLang); } else { const browserLang = translate.getBrowserCultureLang(); translate.use( translate.langs.includes(browserLang) ? browserLang : 'en'); } }, 0);
|
總算,整頁與選單都能正確翻譯了
小結
這麼長一串有點像幫第三方擦屁股,不確定 setTranslation()
裡面是不是有讓 TranslateService
subscribe 觸發翻譯更新,文件也沒提到多少細節,還是呼籲大家慎選第三方 library 。
Reference