0%

Ngx-translate

NGX-Translate is an internationalization library for Angular. NGX-Translate is also extremely modular. It is written in a way that makes it really easy to replace any part with a custom implementation in case the existing one doesn’t fit your needs.

Why ngx-translate exists if we already have built-in Angular i18n

关于载入翻译文件

集成步骤

1
npm install @ngx-translate/core @ngx-translate/http-loader --save

在模块中引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {TranslateModule} from '@ngx-translate/core';

export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient]
},
defaultLanguage: 'en'
})
],
bootstrap: [AppComponent]
})
export class AppModule { }

组件注入及初始化
1
2
3
4
5
6
7
8
export class AppComponent {
constructor(translate: TranslateService) {
// this language will be used as a fallback when a translation isn't found in the current language
translate.setDefaultLang('en');
// the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use('en');
}
}

在模板中使用管道标记待翻译标记
1
2
3
4
5
<p>{{"sayHi" | translate}}</p>

<p>{{'sayHiWithParameter' | translate:userObj}}</p>

<p>{{ 'ROLES.' + role | uppercase | translate }}</p>

翻译文件,对于HTTP Loader是以Locale_ID命名的Json
1
2
3
4
5
6
src
├───assets
│ ├───i18n
│ │ en.json
│ │ fr.json
│ │ zh.json

翻译格式
1
2
3
4
5
6
7
8
{
"introductionHeader":"你好",
"ROLE":{
"ADMIN": "管理员",
"USER": "用户"
},
"sayHiWithParameter":"你好,{{pride}}的{{lastname}}}先生"
}

在ts代码中引用翻译
1
2
3
4
translate.get('HELLO', {value: 'world'}).subscribe((res: string) => {
console.log(res);
//=> 'hello world'
});

使用 PO Loader

官方提供给的 @biesbjerg/ngx-translate-po-http-loader,很遗憾,并不好使,或许是因为新版本的gettext-parser将msgctxt(信息上下文)作为一级父节点,而ngx-translate-po-http-loader一直没有更新使支持msgctxt

部分zh_CN.po文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Language: zh_CN\n"
"X-Qt-Contexts: true\n"

#: ../scanflow/res/qml/Dialog/AboutDialog.qml:25
msgctxt "AboutDialog|"
msgid "About"
msgstr "关于"

#: ../scanflow/res/qml/Dialog/AboutDialog.qml:52
msgctxt "AboutDialog|"
msgid "Product Version"
msgstr "产品版本"

通过上文“翻译格式”可知,ngx-translate允许多级属性mapping,如
1
<h1>{{ "AboutDialog|.About" |translate}}</h1>

另外,gettext-parser会将分段的msgid和msgstr进行合并,即
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
msgctxt "ErrorMessage|"
msgid ""
"Application preferences have been damaged. Reinstall the application to "
"solve the problem."
msgstr ""
"Předvolby aplikace byly poškozeny. Problém vyřešíte opětovnou instalací "
"aplikace."
====>
{
...
"ErrorMessage|":{
msgctxt:"ErrorMessage|",
msgid:"Application preferences have been damaged. Reinstall the application to solve the problem.",
msgstr:["Předvolby aplikace byly poškozeny. Problém vyřešíte opětovnou instalací aplikace."]
}
}

原理见@ngx-translate/core/fesm2015/ngx-translate-core.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getValue(target, key) {
/** @type {?} */
let keys = typeof key === 'string' ? key.split('.') : [key];
key = '';
do {
key += keys.shift();
if (isDefined(target) && isDefined(target[key]) && (typeof target[key] === 'object' || !keys.length)) {
target = target[key];
key = '';
}
else if (!keys.length) {
target = undefined;
}
else {
key += '.';
}
} while (keys.length);
return target;
}

结合ngx-translate解析翻译时的逻辑,在实现PO Loader的getTranslation方法(关于自定义loader的继承和实现,ngx-translate有指引 -> link)时,应将msgctxt,msgid转为父子属性形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public getTranslation(lang: string): Observable<any> {
return this._http
.get(`${this._prefix}/${lang}${this._suffix}`, { responseType: 'text' })
.pipe(
map((contents: string) => this.parse(contents)));
}

/**
* Parse po file
* @param contents
* @returns {any}
*/
public parse(contents: string): any {
let translations: { [key: string]: object | string } = {};

const po = gettext.po.parse(contents, 'utf-8');
if (!po.translations.hasOwnProperty(this.domain)) {
return translations;
}

Object.keys(po.translations)
.forEach(domain => {
if (domain.length === 0) { return; }
if (po.translations[domain].msgstr) { // there is no msgctxt
translations[domain] = po.translations[domain].msgstr;
} else {
// context
translations[domain] = {};
Object.keys(po.translations[domain]).forEach(key => {
let translation: string | Array<string> = po.translations[domain][key].msgstr.pop();
if (translation instanceof Array && translation.length > 0) {
translation = translation[0];
}
if (key.length > 0 && translation.length > 0) {
translations[domain][key] = translation;
}
});
}
});
return translations;
}

引入自己的 loader module
1
2
3
import { TranslatePoHttpLoader } from './edited-po-loader';
export function createTranslateLoader(http: HttpClient) {
return new TranslatePoHttpLoader(http, 'assets/i18n', '.po');

缺失的翻译 handle missing translations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core';

export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) {
return 'some value';
}
}
@NgModule({
imports: [
BrowserModule,
TranslateModule.forRoot({
missingTranslationHandler: {provide: MissingTranslationHandler, useClass: MyMissingTranslationHandler},
useDefaultLang: false
})
],
providers: [

],
bootstrap: [AppComponent]
})
export class AppModule { }

plural and select

这个库没有相应的api与i18n的 plural / select 模式相对应,对于这些场景需要使用ngIf或ngSwitch指令实现