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
关于载入翻译文件
- Http Loader: load json translation files with http
- Angular Universal Loader: load json translation files with fs instead of http
- Po Loader: load .po files with http
集成步骤
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
21import {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
8export 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命名的Json1
2
3
4
5
6src
├───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
4translate.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
18msgid ""
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
16msgctxt "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.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19getValue(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
41public 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 module1
2
3import { TranslatePoHttpLoader } from './edited-po-loader';
export function createTranslateLoader(http: HttpClient) {
return new TranslatePoHttpLoader(http, 'assets/i18n', '.po');
缺失的翻译 handle missing translations1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import {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指令实现