Angular 項目 國際化方案

正如angular官網所說,國際化是一件具有挑戰性,需要多方面的努力、持久的奉獻和決心的任務。本文將介紹angular項目的國際化方案,涉及靜態文件(html)和ts文件文案的國際化。

背景

  • Angular: 5.0
  • Angular Cli: 1.6.1(1.5.x也可以)
  • NG-ZORRO: 0.6.8

Angular i18n

i18n模板翻譯流程有四個階段:

1. 在組件模板中標記需要翻譯的靜態文本信息(即打上i18n標籤)。n2. Angular的i18n工具將標記的信息提取到一個行業標準的翻譯源文件(如.xlf文件,使用ng xi18n)。n3. 翻譯人員編輯該文件,翻譯提取出來的文本信息到目標語言,並將該文件還給你(需要翻譯人員接入,本文採用將xlf文件轉為json格式文件輸出,最終將json文件轉換回xlf格式文件)。n4. Angular編譯器導入完成翻譯的文件,使用翻譯的文本替換原始信息,並生成新的目標語言版本的應用程序。n

你可以為每種支持的語言構建和部署單獨的項目版本,僅需替換翻譯後的xlf文件即可。

如何在模板文件中使用?

i18n提供了幾種使用方式,還專門為單複數提供了翻譯方式(個人沒有使用,感覺不太方便)。接下來以一個單獨的html文件來介紹幾種使用方法。

<!DOCTYPE html>n<html>n <head>n <meta charset="utf-8">n <title>Angular i18n</title>n </head>n <body>n <h1 i18n="Site Header|An introduction header for i18n Project@@stTitle">Angular 國際化項目</h1>n <p>n <span i18n="@@agDescription">國際化是一項很具有挑戰性,需要多方面的努力、持久的奉獻和決心的任務。</span>n <span class="delete" i18n-title="@@agDelete" title="刪除"></span>n </p>n <p><ng-container i18n=@@agLetGo>讓我們現在開始吧!</ng-container>朋友!</p>n </body>n</html>n

上述代碼展示了幾種i18n的使用方式:

1、使用i18n屬性標記(可添加上說明性文案,格式如:title|description@@id,title和description可幫助翻譯人員更好地理解文案含義,是否添加取決於自身項目情況)

// 可以在靜態標籤上直接打上i18n的tag,如n<span i18n="@@agDescription"></span>n// 生成的xlf(xml)欄位格式為n<trans-unit id="agDescription" datatype="html">n <source>國際化是一項很具有挑戰性,需要多方面的努力、持久的奉獻和決心的任務。</source>n <context-group purpose="location">n <context context-type="sourcefile">xxx.ts</context>n <context context-type="linenumber">linenum</context>n </context-group>n</trans-unit>n

2、為title添加i18n屬性

* 對於html標籤屬性,同樣可以添加i18n,如n<span class="delete" i18n-title="@@agDelete" title="刪除"></span>n* 生成的xlf(xml)格式同上n

3、翻譯文本,而不必創建元素

// 我們有時候會出現一句話多個斷句情況,如果每次都添加span、label這些元素包裹的話,可能嚴重影響頁面布局,這時候我們可以使用ng-container來包裹需要翻譯的文案。n// ng-container變為了注釋塊,這樣做不會影響頁面布局(尤其是應用了style樣式的情況) n<p>n <ng-container i18n=@@agLetGo>讓我們現在開始吧!</ng-container>朋友!n</p>n// 在頁面顯示為n<p>n <!---->n LETS GO朋友!n</p>n

打上標籤後,我們只要執行ng xi18n即可自動創建出xlf文件,通常為message.xlf,如需自定義,可自行前往 Angular CLI 官網查看。

XLF與JSON轉換

xlf轉json方法

// 我個人是採用xml2js庫進行操作,簡單代碼如下:n const fs = require(fs);n xml2js = require(xml2js);n var parser = new xml2js.Parser();n fs.readFile(fileName, utf8, (err, data) => {n parser.parseString(data, function (err, result) {n // 讀取新文件全部需要翻譯的數據,並對比已翻譯的進行取捨,具體轉換成的格式結構可自行查看n result[xliff][file][0][body][0][trans-unit].forEach((item) => {n var itemFormat = {n "key" : item[$][id],n "value": item[source][0]n };n // 執行相關操作,key-value形式是為了統一翻譯文件結構,可按需定義n })n });n });n

json轉xlf方法

function backToXLF(translatedParams) {n // 文件格式可自行參考angular.cn官網的例子n var xlfFormat = {n "xliff": {n "$" : {n "version": "1.2",n "xmlns" : "urn:oasis:names:tc:xliff:document:1.2"n },n "file": [n {n "$" : {n "source-language": "en",n "datatype" : "plaintext",n "original" : "ng2.template"n },n "body": [n {n "trans-unit": []n }n ]n }n ]n }n };n if (translatedParams instanceof Array) {n // 獲取原始名稱n translatedParams.forEach((data) => {n var tmp = {n "$" : {n "id" : data.key,n "datatype": "html"n },n "source": [i18nItemsOrigin[data.key]], // 這裡的i18nItemsOrigin是json格式,屬性名為key值,表示原始文案n "target": [data.value]n };n // 數組,json項n xlfFormat[xliff][file][0][body][0][trans-unit].push(tmp);n });n }n var builder = new xml2js.Builder();n var xml = builder.buildObject(xlfFormat);n return xml;n }n

這樣提取文案信息和轉換翻譯後的文件就完成了,接下來我們需要把翻譯好的文案應用到項目中去。

部署翻譯文件

JIT模式

src目錄下新建locale文件夾,將翻譯轉換後的demo.en-US.xlf文件存在改目錄下

app文件夾下新建i18n-providers.ts

import {n LOCALE_ID,n MissingTranslationStrategy,n StaticProvider,n TRANSLATIONS,n TRANSLATIONS_FORMATn } from @angular/core;n import { CompilerConfig } from @angular/compiler;n import { Observable } from rxjs/Observable;n import { LOCALE_LANGUAGE } from ./app.config; // 自行定義配置位置n export function getTranslationProviders(): Promise<StaticProvider[]> {n // get the locale string from the documentn const locale = LOCALE_LANGUAGE.toString();n // return no providersn const noProviders: StaticProvider[] = [];n // no locale or zh-CN: no translation providersn if (!locale || locale === zh-CN) {n return Promise.resolve(noProviders);n }n // Ex: locale/demo.zh-MO.xlf`n const translationFile = `./locale/demo.${locale}.xlf`;n return getTranslationsWithSystemJs(translationFile)n .then((translations: string) => [n { provide: TRANSLATIONS, useValue: translations },n { provide: TRANSLATIONS_FORMAT, useValue: xlf },n { provide: LOCALE_ID, useValue: locale },n {n provide: CompilerConfig,n useValue: new CompilerConfig({ missingTranslation: MissingTranslationStrategy.Error })n }n ]).catch(() => noProviders); // ignore if file not foundn }n // 獲取locale文件n function getTranslationsWithSystemJs(file: string) {n let msg = ;n const fileRequest = new XMLHttpRequest();n fileRequest.open(GET, file, false);n fileRequest.onerror = function (err) {n // 自行處理錯誤信息n };n fileRequest.onreadystatechange = function () {n if (fileRequest.readyState === 4) {n if (fileRequest.status === 200 || fileRequest.status === 0) {n msg = fileRequest.responseText;n }n }n };n fileRequest.send();n const observable = Observable.of(text);n const toPromise = observable.toPromise();n return toPromise;n }n

main.ts文件修改為

import { enableProdMode } from @angular/core;n import { platformBrowserDynamic } from @angular/platform-browser-dynamic;n import { AppModule } from ./app/app.module;n import { environment } from ./environments/environment;n import { getTranslationProviders } from ./app/i18n-providers;n if (environment.production) {n enableProdMode();n }n getTranslationProviders().then(providers => {n const options = { providers };n platformBrowserDynamic().bootstrapModule(AppModule, options)n .catch(err => console.log(err));n });n

* 別忘了將locale目錄添加到.angular-cli.json里,來單獨打包。

AOT模式(推薦)

對於AOT模式打包的持續來說,不需要上述複雜的配置,只需要在原有build基礎上,加上相應的i18n文件即可,如

ng build --prod --build-optimizer -bh / --i18n-format=xlf --locale=en --i18n-file=./src/locale/demo.en-US.xlfn

這樣打出的包會自動將翻譯文件應用到項目中。

這樣我們對靜態文案的翻譯工作基本已經完成了,但是有些動態文案如ts文件里的文案或者第三方框架屬性該如何翻譯呢?下面會介紹針對 ts 文件和 NG-ZORRO 框架實現動態文案翻譯的方案。

ts文件文案和NG-ZORRO框架文案翻譯

具體思路

通過Pipe調用Service方法,根據對應的唯一id值匹配json對象里的翻譯結果,進而返回渲染到前端,參考於NG-ZORRO框架的國際化實現方案。n

首先我們定義一下json翻譯對象的格式,全部為三層結構,動態變數需要按%%包裹,這樣做的原因是和項目結構相關聯,也便於後期和i18n方式格式統一。

{n "app": {n "base": {n "hello": "文件文案",n "userCount": "一共%num%人"n }n }n }n

格式已定,我們繼續定義Service處理方式

這裡復用 NG-ZORRO 的國際化方案 ,可以簡化我們的開發,有興趣的可以參看一下其源碼。

// *** TranslateService ***n import { Injectable } from @angular/core;n // 引入語言配置和國際化文件文案對象n import { LOCALE_LANGUAGE } from ../app.config;n import { enUS } from ../locales/demo.en-US;n import { zhCN } from ../locales/stream.zh-CN;n @Injectable()n export class TranslateService {n private _locale = LOCALE_LANGUAGE.toString() === zh-CN ? zhCN : enUS;n constructor() {n }n // path為app.base.hello格式的字元串,這裡按json層級取匹配改變數n translate(path: string, data?: any): string {n let content = this._getObjectPath(this._locale, path) as string;n if (typeof content === string) {n if (data) {n Object.keys(data).forEach((key) => content = content.replace(new RegExp(`%${key}%`, g), data[key]));n }n return content;n }n return path;n }n private _getObjectPath(obj: object, path: string): string | object {n let res = obj;n const paths = path.split(.);n const depth = paths.length;n let index = 0;n while (res && index < depth) {n res = res[paths[index++]];n }n return index === depth ? res : null;n }n }n

這樣,只需要在Pipe中調用Service的translate方法即可

// *** NzTranslateLocalePipe ***n import { Pipe, PipeTransform } from @angular/core;n import { TranslateService } from ../services/translate.service;n @Pipe({n name: nzTranslateLocalen })n export class NzTranslateLocalePipe implements PipeTransform {n constructor(private _locale: TranslateService) {n }n transform(path: string, keyValue?: object): string {n return this._locale.translate(path, keyValue);n }n }n

好了,現在我們處理邏輯已經完全結束了,下面介紹一下如何使用

// *** NG-ZORRO 控制項 ***n <nz-input [nzPlaceHolder]="app.base.hello|nzTranslateLocale"></nz-input> // 無動態參數n <nz-popconfirm [nzTitle]="app.base.userCount|nzTranslateLocale: {num:users.length}" ...>n ... // 有動態參數n </nz-popconfirm>nn // *** ts文件 ***n export class AppComponent implements OnInit {n demoTitle=;n users = [Jack, Johnson, Lucy];n constructor(privete translateService: TranslateService) {n }n ngOnInit() {n this.demoTitle = this.translateService.translate(app.base.hello);n }n }n

以上流程基本上能滿足大部分angular項目的國際化需求,如果需要更加複雜的國際化情況,歡迎討論。

總結

Angular到5.0的國際化已經相對來說簡便了很多,我們只需要在合適的地方打上i18n的tag即可方便快速地提取需要翻譯文案,具體如何處理翻譯後的文件因人而異,多種方法可幫助我們轉換(如本文通過nodejs)。

複雜一點的是無法通過打i18n標籤來翻譯的文本,NG-ZORRO的國際化方案彌補了這方面的不足,結合起來可以很方便地完成項目的國際化。 國際化如果沒有專門的團隊支持,翻譯難度很大,需要考慮的東西很多,比如繁體還有澳門繁體、台灣繁體等,語法也不盡相同。

參考目錄

Angular的國際化(i18n)在線例子

NG-ZORRO Locale 國際化


推薦閱讀:

【ngMiracle】Write Your Own Structural Directive
使用UpgradeAdapter將angular 1 升級到 2 的開發體驗如何?

TAG:Angular? | 国际化 |