Angular?項(xiàng)目實(shí)現(xiàn)國(guó)際化的方法
正如angular官網(wǎng)所說(shuō),項(xiàng)目國(guó)際化是一件具有挑戰(zhàn)性,需要多方面的努力、持久的奉獻(xiàn)和決心的任務(wù)。
本文將介紹angular項(xiàng)目的國(guó)際化方案,涉及靜態(tài)文件(html)和ts文件文案的國(guó)際化。
背景
- Angular: 5.0
- Angular Cli: 1.6.1(1.5.x也可以)
- NG-ZORRO: 0.6.8
Angular i18n
i18n模板翻譯流程有四個(gè)階段:
- 在組件模板中標(biāo)記需要翻譯的靜態(tài)文本信息(即打上i18n標(biāo)簽)。
- Angular的i18n工具將標(biāo)記的信息提取到一個(gè)行業(yè)標(biāo)準(zhǔn)的翻譯源文件(如.xlf文件,使用ng xi18n)。
- 翻譯人員編輯該文件,翻譯提取出來(lái)的文本信息到目標(biāo)語(yǔ)言,并將該文件還給你(需要翻譯人員接入,本文采用將xlf文件轉(zhuǎn)為json格式文件輸出,最終將json文件轉(zhuǎn)換回xlf格式文件)。
- Angular編譯器導(dǎo)入完成翻譯的文件,使用翻譯的文本替換原始信息,并生成新的目標(biāo)語(yǔ)言版本的應(yīng)用程序。
你可以為每種支持的語(yǔ)言構(gòu)建和部署單獨(dú)的項(xiàng)目版本,僅需替換翻譯后的xlf文件即可。
如何在模板文件中使用?
i18n提供了幾種使用方式,還專(zhuān)門(mén)為單復(fù)數(shù)提供了翻譯方式(個(gè)人沒(méi)有使用,感覺(jué)不太方便)。接下來(lái)以一個(gè)單獨(dú)的html文件來(lái)介紹幾種使用方法。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Angular i18n</title> </head> <body> <h1 i18n="Site Header|An introduction header for i18n Project@@stTitle">Angular 國(guó)際化項(xiàng)目</h1> <p> <span i18n="@@agDescription">國(guó)際化是一項(xiàng)很具有挑戰(zhàn)性,需要多方面的努力、持久的奉獻(xiàn)和決心的任務(wù)。</span> <span class="delete" i18n-title="@@agDelete" title="刪除"></span> </p> <p><ng-container i18n=@@agLetGo>讓我們現(xiàn)在開(kāi)始吧!</ng-container>朋友!</p> </body> </html>
上述代碼展示了幾種i18n的使用方式:
1、使用i18n屬性標(biāo)記(可添加上說(shuō)明性文案,格式如:title|description@@id,title和description可幫助翻譯人員更好地理解文案含義,是否添加取決于自身項(xiàng)目情況)
可以在靜態(tài)標(biāo)簽上直接打上i18n的tag,如
<span i18n="@@agDescription"></span>
生成的xlf(xml)字段格式為
<trans-unit id="agDescription" datatype="html"> <source>國(guó)際化是一項(xiàng)很具有挑戰(zhàn)性,需要多方面的努力、持久的奉獻(xiàn)和決心的任務(wù)。</source> <context-group purpose="location"> <context context-type="sourcefile">xxx.ts</context> <context context-type="linenumber">linenum</context> </context-group> </trans-unit>
2、為title添加i18n屬性
對(duì)于html標(biāo)簽屬性,同樣可以添加i18n,如
<span class="delete" i18n-title="@@agDelete" title="刪除"></span>
生成的xlf(xml)格式同上
3、翻譯文本,而不必創(chuàng)建元素
我們有時(shí)候會(huì)出現(xiàn)一句話(huà)多個(gè)斷句情況,如果每次都添加span、label這些元素包裹的話(huà),可能?chē)?yán)重影響頁(yè)面布局,這時(shí)候我們可以使用ng-container來(lái)包裹需要翻譯的文案。
<p> <ng-container i18n=@@agLetGo>讓我們現(xiàn)在開(kāi)始吧!</ng-container>朋友! </p>
在頁(yè)面顯示為
<p> <!----> LET'S GO朋友! </p>
* ng-container變?yōu)榱俗⑨寜K,這樣做不會(huì)影響頁(yè)面布局(尤其是應(yīng)用了style樣式的情況)
打上標(biāo)簽后,我們只要執(zhí)行ng xi18n即可自動(dòng)創(chuàng)建出xlf文件,通常為message.xlf,如需自定義,可自行前往 Angular CLI 官網(wǎng)查看。
XLF與JSON轉(zhuǎn)換
xlf轉(zhuǎn)json方法
我個(gè)人是采用xml2js庫(kù)進(jìn)行操作,簡(jiǎn)單代碼如下:
const fs = require('fs');
xml2js = require('xml2js');
var parser = new xml2js.Parser();
fs.readFile(fileName, 'utf8', (err, data) => {
parser.parseString(data, function (err, result) {
// 讀取新文件全部需要翻譯的數(shù)據(jù),并對(duì)比已翻譯的進(jìn)行取舍,具體轉(zhuǎn)換成的格式結(jié)構(gòu)可自行查看
result['xliff']['file'][0]['body'][0]['trans-unit'].forEach((item) => {
var itemFormat = {
"key" : item['$']['id'],
"value": item['source'][0]
};
// 執(zhí)行相關(guān)操作,key-value形式是為了統(tǒng)一翻譯文件結(jié)構(gòu),可按需定義
})
});
});
json轉(zhuǎn)xlf方法
function backToXLF(translatedParams) {
// 文件格式可自行參考angular.cn官網(wǎng)的例子
var xlfFormat = {
"xliff": {
"$" : {
"version": "1.2",
"xmlns" : "urn:oasis:names:tc:xliff:document:1.2"
},
"file": [
{
"$" : {
"source-language": "en",
"datatype" : "plaintext",
"original" : "ng2.template"
},
"body": [
{
"trans-unit": []
}
]
}
]
}
};
if (translatedParams instanceof Array) {
// 獲取原始名稱(chēng)
translatedParams.forEach((data) => {
var tmp = {
"$" : {
"id" : data.key,
"datatype": "html"
},
"source": [i18nItemsOrigin[data.key]], // 這里的i18nItemsOrigin是json格式,屬性名為key值,表示原始文案
"target": [data.value]
};
// 數(shù)組,json項(xiàng)
xlfFormat['xliff']['file'][0]['body'][0]['trans-unit'].push(tmp);
});
}
var builder = new xml2js.Builder();
var xml = builder.buildObject(xlfFormat);
return xml;
}
這樣提取文案信息和轉(zhuǎn)換翻譯后的文件就完成了,接下來(lái)我們需要把翻譯好的文案應(yīng)用到項(xiàng)目中去。
部署翻譯文件
src目錄下新建locale文件夾,將翻譯轉(zhuǎn)換后的demo.en-US.xlf文件存在改目錄下
app文件夾下新建i18n-providers.ts
import {
LOCALE_ID,
MissingTranslationStrategy,
StaticProvider,
TRANSLATIONS,
TRANSLATIONS_FORMAT
} from '@angular/core';
import { CompilerConfig } from '@angular/compiler';
import { Observable } from 'rxjs/Observable';
import { LOCALE_LANGUAGE } from './app.config'; // 自行定義配置位置
export function getTranslationProviders(): Promise<StaticProvider[]> {
// get the locale string from the document
const locale = LOCALE_LANGUAGE.toString();
// return no providers
const noProviders: StaticProvider[] = [];
// no locale or zh-CN: no translation providers
if (!locale || locale === 'zh-CN') {
return Promise.resolve(noProviders);
}
// Ex: 'locale/demo.zh-MO.xlf`
const translationFile = `./locale/demo.${locale}.xlf`;
return getTranslationsWithSystemJs(translationFile)
.then((translations: string) => [
{ provide: TRANSLATIONS, useValue: translations },
{ provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
{ provide: LOCALE_ID, useValue: locale },
{
provide: CompilerConfig,
useValue: new CompilerConfig({ missingTranslation: MissingTranslationStrategy.Error })
}
]).catch(() => noProviders); // ignore if file not found
}
declare var System: any;
// 獲取locale文件
function getTranslationsWithSystemJs(file: string) {
let text = '';
const fileRequest = new XMLHttpRequest();
fileRequest.open('GET', file, false);
fileRequest.onerror = function (err) {
console.log(err);
};
fileRequest.onreadystatechange = function () {
if (fileRequest.readyState === 4) {
if (fileRequest.status === 200 || fileRequest.status === 0) {
text = fileRequest.responseText;
}
}
};
fileRequest.send();
const observable = Observable.of(text);
const prom = observable.toPromise();
return prom;
}
main.ts文件修改為
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { getTranslationProviders } from './app/i18n-providers';
if (environment.production) {
enableProdMode();
}
getTranslationProviders().then(providers => {
const options = { providers };
platformBrowserDynamic().bootstrapModule(AppModule, options)
.catch(err => console.log(err));
});
別忘了將locale目錄添加到.angular-cli.json里,來(lái)單獨(dú)打包。
這樣我們對(duì)靜態(tài)文案的翻譯工作基本已經(jīng)完成了,但是有些動(dòng)態(tài)文案如ts文件里的文案或者第三方框架屬性該如何翻譯呢?下面會(huì)介紹針對(duì) ts 文件和 NG-ZORRO 框架實(shí)現(xiàn)動(dòng)態(tài)文案翻譯的方案。
ts文件文案和NG-ZORRO框架文案翻譯
具體思路
通過(guò)Pipe調(diào)用Service方法,根據(jù)對(duì)應(yīng)的唯一id值匹配json對(duì)象里的翻譯結(jié)果,進(jìn)而返回渲染到前端,參考于NG-ZORRO框架的國(guó)際化實(shí)現(xiàn)方案。
首先我們定義一下json翻譯對(duì)象的格式,全部為三層結(jié)構(gòu),動(dòng)態(tài)變量需要按%%包裹,這樣做的原因是和項(xiàng)目結(jié)構(gòu)相關(guān)聯(lián),也便于后期和i18n方式格式統(tǒng)一。
{
"app": {
"base": {
"hello": "文件文案",
"userCount": "一共%num%人"
}
}
}格式已定,我們繼續(xù)定義Service處理方式
這里復(fù)用NG-ZORRO的國(guó)際化方案 ,可以簡(jiǎn)化我們的開(kāi)發(fā),有興趣的可以參看一下其源碼。
*** TranslateService ***
import { Injectable } from '@angular/core';
// 引入語(yǔ)言配置和國(guó)際化文件文案對(duì)象
import { LOCALE_LANGUAGE } from '../app.config';
import { enUS } from '../locales/demo.en-US';
import { zhCN } from '../locales/stream.zh-CN';
@Injectable()
export class TranslateService {
private _locale = LOCALE_LANGUAGE.toString() === 'zh-CN' ? zhCN : enUS;
constructor() {
}
// path為app.base.hello格式的字符串,這里按json層級(jí)取匹配改變量
translate(path: string, data?: any): string {
let content = this._getObjectPath(this._locale, path) as string;
if (typeof content === 'string') {
if (data) {
Object.keys(data).forEach((key) => content = content.replace(new RegExp(`%${key}%`, 'g'), data[key]));
}
return content;
}
return path;
}
private _getObjectPath(obj: object, path: string): string | object {
let res = obj;
const paths = path.split('.');
const depth = paths.length;
let index = 0;
while (res && index < depth) {
res = res[paths[index++]];
}
return index === depth ? res : null;
}
}
這樣,只需要在Pipe中調(diào)用Service的translate方法即可
*** NzTranslateLocalePipe ***
import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '../services/translate.service';
@Pipe({
name: 'nzTranslateLocale'
})
export class NzTranslateLocalePipe implements PipeTransform {
constructor(private _locale: TranslateService) {
}
transform(path: string, keyValue?: object): string {
return this._locale.translate(path, keyValue);
}
}
好了,現(xiàn)在我們處理邏輯已經(jīng)完全結(jié)束了,下面介紹一下如何使用
*** NG-ZORRO 控件 ***
<nz-input [nzPlaceHolder]="'app.base.hello'|nzTranslateLocale"></nz-input> // 無(wú)動(dòng)態(tài)參數(shù)
<nz-popconfirm [nzTitle]="'app.base.userCount'|nzTranslateLocale: {num:users.length}" ...>
... // 有動(dòng)態(tài)參數(shù)
</nz-popconfirm>
*** ts文件 ***
export class AppComponent implements OnInit {
demoTitle='';
users = ['Jack', 'Johnson', 'Lucy'];
constructor(privete translateService: TranslateService) {
}
ngOnInit() {
this.demoTitle = this.translateService.translate('app.base.hello');
}
}
以上流程基本上能滿(mǎn)足大部分angular項(xiàng)目的國(guó)際化需求,如果需要更加復(fù)雜的國(guó)際化情況,歡迎討論。
總結(jié)
Angular到5.0的國(guó)際化已經(jīng)相對(duì)來(lái)說(shuō)簡(jiǎn)便了很多,我們只需要在合適的地方打上i18n的tag即可方便快速地提取需要翻譯文案,具體如何處理翻譯后的文件因人而異,多種方法可幫助我們轉(zhuǎn)換(如本文通過(guò)nodejs)。
復(fù)雜一點(diǎn)的是無(wú)法通過(guò)打i18n標(biāo)簽來(lái)翻譯的文本,NG-ZORRO的國(guó)際化方案彌補(bǔ)了這方面的不足,結(jié)合起來(lái)可以很方便地完成項(xiàng)目的國(guó)際化。 國(guó)際化如果沒(méi)有專(zhuān)門(mén)的團(tuán)隊(duì)支持,翻譯難度很大,需要考慮的東西很多,比如繁體還有澳門(mén)繁體、臺(tái)灣繁體等,語(yǔ)法也不盡相同。
參考目錄
Angular的國(guó)際化(i18n)在線(xiàn)例子
NG-ZORRO Locale 國(guó)際化
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決angular雙向綁定無(wú)效果,ng-model不能正常顯示的問(wèn)題
今天小編就為大家分享一篇解決angular雙向綁定無(wú)效果,ng-model不能正常顯示的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
詳解AngularJS Filter(過(guò)濾器)用法
這篇文章主要介紹了AngularJS的filter,中文名“過(guò)濾器”是用來(lái)過(guò)濾變量的值,或者格式化輸出,得到自己所期望的結(jié)果或格式的東東,的相關(guān)資料,需要的朋友可以參考下2015-12-12
Angular+ionic實(shí)現(xiàn)折疊展開(kāi)效果的示例代碼
這篇文章主要介紹了Angular+ionic實(shí)現(xiàn)折疊展開(kāi)效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
AngularJS 入門(mén)教程之HTML DOM實(shí)例詳解
本文主要介紹AngularJS HTML DOM,這里幫大家整理了詳細(xì)的資料,并附實(shí)例代碼詳細(xì)講解,有需要的小伙伴可以參考下2016-07-07
angular強(qiáng)制更新ui視圖的實(shí)現(xiàn)方法
這篇文章主要介紹了angular強(qiáng)制更新ui視圖的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
AngularJS 中的指令實(shí)踐開(kāi)發(fā)指南(一)
指令(Directives)是所有AngularJS應(yīng)用最重要的部分。盡管AngularJS已經(jīng)提供了非常豐富的指令,但還是經(jīng)常需要?jiǎng)?chuàng)建應(yīng)用特定的指令。這篇教程會(huì)為你講述如何自定義指令,以及介紹如何在實(shí)際項(xiàng)目中使用2016-03-03
Angular8升級(jí)至Angular13遇到的問(wèn)題解決
這幾天升級(jí)公司的一個(gè)Angular項(xiàng)目遇到了一些問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于A(yíng)ngular8升級(jí)至Angular13遇到的問(wèn)題解決,文中介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01
AngularJS實(shí)現(xiàn)網(wǎng)站換膚實(shí)例
這篇文章主要為大家詳細(xì)介紹了AngularJS實(shí)現(xiàn)網(wǎng)站換膚的簡(jiǎn)單實(shí)例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
詳解AngularJS用Interceptors來(lái)統(tǒng)一處理HTTP請(qǐng)求和響應(yīng)
本篇文章主要介紹了AngularJS用Interceptors來(lái)統(tǒng)一處理HTTP請(qǐng)求和響應(yīng) ,具有一定的參考價(jià)值,有興趣的可以了解一下2017-06-06
AngularJs 最新驗(yàn)證手機(jī)號(hào)碼的實(shí)例,成功測(cè)試通過(guò)
下面小編就為大家分享一篇AngularJs 最新驗(yàn)證手機(jī)號(hào)碼的實(shí)例,成功測(cè)試通過(guò),具有很好的參考價(jià)值。希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11

