国际化

国际化(i18n)

应用程序的国际化涉及到开发的很多方面,主要是如何让应用可以被全世界的用户使用而且用起来比较友好。 本页面讲的是 Angular 的国际化i18n)工具,它可以帮助你使用多个语言发布应用。

可以把这个翻译为法语版的 AOT 应用i18n 例子作为一个简单的例子。

Angular 与 i18n

Angular 简化了国际化工作的下列几个方面:

  • 用本地格式显示日期、数字、百分比以及货币。

本文档集中讲解 Angular CLI 项目,Angular CLI 为它生成了写多语言应用时必须的大部分样板代码。

为你的应用设置地区(locale)

地区是一个唯一性标识,它代表的是世界某个区域(比如国家)中共享的一组用户首选项。本文档中提到的“地区”就是指这个地区标识。

Unicode 的地区标识是由 Unicode 语言标识、一个可选的 - 字符,后跟一个地区扩展项组合而成的。(由于历史的原因,还支持用 _ 作为 - 的替代品。)比如,地区标识 fr-CA 中的 fr 代表法语的语言标识,而 CA 代表的是加拿大的语言扩展。

Angular 遵循 Unicode 的 LDML 惯例,它使用基于 BCP47 标准的稳定标识(Unicode 的地区标识)。当你要定义自己的地区时,遵循这个惯例非常重要,因为 Angular 的 i18n 工具会使用这种地区标识来查找相应的本地化数据。

默认情况下,Angular 使用的地区标识是 en-US,它表示美国英语。

要把你的应用的地区改为其它值,可以使用 CLI 参数 --configuration 来传入你要使用的地区标识:

content_copyng serve --configuration=fr

如果要使用 JIT,你就得在你的主模块中定义 LOCALE_ID 这个服务提供商:

src/app/app.module.ts

content_copyimport { LOCALE_ID, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from '../src/app/app.component'; @NgModule{ imports: [ BrowserModule ], declarations: [ AppComponent ], providers: [ { provide: LOCALE_ID, useValue: 'fr' } ], bootstrap: [ AppComponent ] }) export class AppModule { }

要了解关于 Unicode 地区标识的更多信息,参见 CLDR 核心规范

要查看 Angular 支持的地区总表,参见 Angular 源码仓库

CLDR 和 Angular 使用的地区标识基于 BCP47。 这些规范可能会随时间而变化,下表中是本文编写时地区标识的新旧版本对照表:

地区名称旧 ID新 ID
印度尼西亚inid
希伯来iwhe
罗马尼亚摩尔多瓦moro-MD
挪威 Bokmålno, no-NOnb
塞尔维亚拉丁语shsr-Latn
菲律宾tlfil
葡萄牙巴西pt-BRpt
中文简体zh-cn, zh-Hans-CNzh-Hans
中文繁体zh-tw, zh-Hant-TWzh-Hant
中文繁体(香港)zh-hkzh-Hant-HK

i18n 管道

Angular 的管道可以帮助你进行国际化:DatePipeCurrencyPipeDecimalPipePercentPipe 都使用本地化数据来根据 LOCALE_ID 格式化数据。

默认情况下,Angular 只包含 en-US 的本地化数据。如果你要把 LOCALE_ID 的值设置为其它地区,就必须为那个新地区导入本地化数据。 当你使用 ng serveng build--configuration 参数时,CLI 会自动帮你导入相应的本地化数据。

如果还要为其它语言导入本地化数据,你可以手工完成它:

src/app/app.module.ts

content_copyimport { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; // the second parameter 'fr' is optional registerLocaleData(localeFr, 'fr'

第一个参数是一个包含从 @angular/common/locales 中导入的本地化数据的对象。 默认情况下,导入 Angular 自带的本地化数据时会使用数据中自带的一个地区标识进行注册。 如果你要使用其它地区标识来注册这个导入的本地化数据,可以在第二个参数中指定一个自定义的地区标识。 比如,Angular 为法语定义的地区标识是 “fr”,你可以通过第二个参数为这些导入的法语本地化数据指定一个自定义的地区标识 “fr-FR”。

@angular/common/locales 中的文件包含你需要的大多数本地化数据, 不过有些高级的格式选项只存在于从 @angular/common/locales/extra 中导入的扩展数据集中。遇到这种情况,你就会收到一条错误信息。

src/app/app.module.ts

content_copyimport { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; registerLocaleData(localeFr, 'fr-FR', localeFrExtra

Angular 中使用的所有本地化数据都是从 Unicode 联盟的常用本地化数据仓库 (CLDR) 中提取的。

模板翻译

本文档中会把翻译文本的最小单元称为“文本”、“消息”或“文本消息”。

i18n 模板的翻译过程分为四个阶段

  • 在组件模板中标记需要翻译的静态文本信息。

你可以为每种支持的语言构建和部署单独的应用程序版本。

使用 i18n 属性标记文本

Angular的i18n属性是可翻译内容的标记。 将它放到每个固定文本需要翻译的元素标签中。

在下面的例子中,<h1>标签显示了一句简单的英文问候语,“Hello i18n!”

src/app/app.component.html

content_copy<h1>Hello i18n!</h1>

要想把它标记为需要翻译的文本,就给 <h1> 标签添加上 i18n 属性。

src/app/app.component.html

content_copy<h1 i18n>Hello i18n!</h1>

i18n 是一个自定义属性,会被 Angular 工具和编译器识别。 翻译之后,编译器就会移除它。它不是 Angular 指令。

用描述和意图来帮助翻译人员

要想翻译的更准确,翻译人员可能需要待翻译文本的额外信息或场景说明。

你可以用 i18n 属性的值来添加这些文本信息的描述,例子如下:

src/app/app.component.html

content_copy<h1 i18n="An introduction header for this sample">Hello i18n!</h1>

为了给出正确的翻译,翻译者需要知道你这段文本在特定情境下的含义真实意图

在描述的前面,你可以用 i18n 开头的属性值来为指定的字符串添加一些上下文含义,用 | 将其与描述文字隔开(<意图>|<描述>)。

src/app/app.component.html

content_copy<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>

如果所有地方出现的文本具有相同含义时,它们应该有相同的翻译, 但是如果在某些地方它具有不同含义,那么它应该有不同的翻译。

Angular 的提取工具会在翻译源文件中保留含义描述,以支持符合特定上下文的翻译。但它只会使用含义和文本消息的组合来为待翻译文本生成明确的 id。如果你有两个相同的文本消息,但是含义不同,它们就会被分别提取。如果你有两个相同的文本消息,但是描述不同(但含义相同),它们就只会提取一次。

设置一个自定义的 id 来提升可搜索性和可维护性

Angular 的 i18n 提取工具会为模板中每个带有 i18n 属性的元素生成一个翻译单元(translation unit)条目,并保存到一个文件中。默认情况下,它为每个翻译单元指定一个唯一的 id,就像这样:

content_copy<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">

当你修改这段可翻译的文字时,提取工具会为那个翻译单元生成一个新的 id。 你就要使用这个新的 id 来修改这个翻译文件。

另一种方案是,你可以使用 @@ 前缀在 i18n 属性中指定一个自定义的 id。 下面这个例子就定义了一个自定义 id introductionHeader

app/app.component.html

content_copy<h1 i18n="@@introductionHeader">Hello i18n!</h1>

一旦你指定了自定义 id,提取工具和编译器就会用*你的自定义 id` 生成一个翻译单元,而不会再改变它。

content_copy<trans-unit id="introductionHeader" datatype="html">

自定义 id 是永久性的,翻译工具待翻译文本发生变化时不会修改它。 因此,你不必修改翻译结果。这种方式可以让维护变得更简单。

在描述中使用自定义 id

你可以在 i18n 属性的值中使用自定义 id 与描述信息的组合。 下面的例子中,i18n 的属性中中就包含了一条跟在自定义 id 后面的描述信息:

app/app.component.html

content_copy<h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1>

你还可以添加含义,例子如下:

app/app.component.html

content_copy<h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1>

定义唯一的自定义 ID

要确保自定义 id 是唯一的。如果你对两个不同的文本块使用了同一个 id,那么就只有一个会被提取出来,然后其翻译结果会被用于全部原始文本消息。

在下面这个例子中,自定义的 id myId 就被用在了两个不同的消息上:

content_copy<h3 i18n="@@myId">Hello</h3> <!-- ... --> <p i18n="@@myId">Good bye</p>

考虑下列法文翻译:

content_copy<trans-unit id="myId" datatype="html"> <source>Hello</source> <target state="new">Bonjour</target> </trans-unit>

由于自定义 id 都是一样的,所以翻译结果中所有的元素都包含同样的文本 Bonjour

content_copy<h3>Bonjour</h3> <!-- ... --> <p>Bonjour</p>

翻译文本,而不必创建元素

如果要翻译一段纯文本,你就可以把它用 <span> 标签包裹起来。 但如果由于某些原因(比如 CSS 结构方面的考虑),你可能不希望仅仅为了翻译而创建一个新的 DOM 元素,那么也可以把这段文本包裹进一个 <ng-container> 元素中。<ng-container> 将被转换成一个 HTML 注释:

src/app/app.component.html

content_copy<ng-container i18n>I don't output any element</ng-container>

添加 i18n 翻译属性

你还可以翻译属性。 比如,假设你的模板中有一个带 title 属性的图片:

src/app/app.component.html

content_copy<img [src]="logo" title="Angular logo">

这个 title 属性也需要翻译。

要把一个属性标记为需要翻译的,就添加一个形如 i18n-x 的属性,其中的 x 是要翻译的属性的名字。 下面的例子中演示了如何通过给 img 标签添加 i18n-title 属性来把 title 属性标记为待翻译的。

src/app/app.component.html

content_copy<img [src]="logo" i18n-title title="Angular logo" />

这个技巧适用于任何元素上的任何属性。

你也同样可以使用 i18n-x="<meaning>|<description>@@<id>" 语法来指定一个含义和描述。

翻译单数与复数

不同的语言有不同的单复数规则。

假设你要说某些东西“updated x minutes ago(在 x 分钟前修改了)”。 在英语中,根据分钟数,可能要显示为 "just now"、"one minute ago" 或 "x minutes ago"(这里的 x 是实际的数量)。 而在其它语言中则可能会有不同的基数规则。

下面这个例子示范了如何使用 ICU 表达式 plural 来根据这次修改发生的时间显示这三个选项之一:

src/app/app.component.html

content_copy<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>

  • 第一个参数是 key。它绑定到了组件中表示分钟数的 minutes 属性。

这种语法遵守 CLDR 复数规则 中指定的 ICU 消息格式

复数类别包括(取决于语言):

  • =0 (或其它数字)

复数类别之后的括号({})中是默认的英语文本。

在上面的例子中,这三个选项都是根据复数模式来指定的。要说零分钟,就用 =0 {just now}。一分钟就用 =1 {one minute}。 无法匹配的数量就用 other {{{minutes}} minutes ago}。如果复数规则与此不同,你还可以为两个、三个火任意数量添加更多的模式。对于这个 “minute” 的例子,英语中只要这三种模式就够了。

你可以在翻译结果中使用插值表达式和 HTML 标记。

在候选文本中选择

如果你的模板中需要根据某个变量的值显示出不同的文本消息,你还需要对所有这些候选文本进行翻译。

你可以使用 ICU 表达式 select 来翻译这些。它与 ICU 表达式 plural 类似,只是你要根据一个字符串值而不是数字来选择这些候选翻译文本,而这些字符串值是你自己定义的。

组件模板中的下列消息格式绑定到了组件的 gender 属性,这个属性的取值是 "m" 或 "f" 或 "o"。 这个消息会把那些值映射到适当的翻译文本:

src/app/app.component.html

content_copy<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>

把"复数"与"选择"表达式嵌套在一起

你也可以把不同的 ICU 表达式嵌套在一起,比如:

src/app/app.component.html

content_copy<span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}} </span>

使用 ng-xi18n 工具创建翻译源文件

使用 CLI 提供的 ng xi18n 命令来将带 i18n 标记的文本消息提取到一个翻译源文件中。

在应用的项目根目录打开一个终端窗口,并输入 ng xi18n 命令:

content_copyng xi18n

本工具默认会生成一个名叫 messages.xlf 的翻译文件,格式为XML本土化互换文件格式(XLIFF, version 1.2)。

如果你不使用 CLI,那么你有两个选择:

  • 你可以直接使用来自 @angular/compiler-cli 包中的 ng-xi18n 工具。更多信息,参见 CLI 文档中的 i18n 部分。

其它翻译格式

Angular 的 i18n 工具支持三种翻译格式:

  • XLIFF 1.2 (默认)

你可以使用 --i18nFormat 来明确指定想用的格式,范例如下:

content_copyng xi18n --i18n-format=xlf ng xi18n --i18n-format=xlf2 ng xi18n --i18n-format=xmb

本章的范例使用默认的 XLIFF 1.2 格式。

XLIFF 文件带有 .xlf 扩展名。XMB 格式会生成 .xmb 格式的源文件,但使用 .xtb(XML 翻译包)格式的翻译文件。

其它选项

你还可以使用 --output-path 参数在 CLI 提取翻译源文件时指定输出路径:

content_copyng xi18n --output-path locale

你还可以使用 --out-file 参数来为提取工具生成的翻译源文件改名:

content_copyng xi18n --out-file source.xlf

你还可以使用 --i18n-locale 参数来指定应用的基本地区:

content_copyng xi18n --i18n-locale fr

该提取工具会用这个地区标识把本地化信息添加到你的翻译源文件中。这个信息对 Angular 来说没用,不过外部翻译工具可能会需要它。

翻译文本信息

ng xi18n 命令会在项目根目录生成一个名为 messages.xlf 的翻译源文件。

下一步是将英文模板文本翻译到指定语言的翻译文件。 这个例子中创建了一个法语翻译文件。

新建一个本土化目录

大多数应用都要被翻译成多种其它语言,因此,为全部国际化工作做适当的调整项目目录结构是一种标准实践。

方法之一是为本土化和相关资源(比如国际化文件)创建一个专门的目录。

本土化和国际化是不同但是很相近的概念

本指南遵循了这种方式。在src/目录下,有一个专门的locale目录,该目录中的文件都带一个与相关地区匹配的扩展名。

创建翻译文件

对每个翻译文件来说,都必须至少有一个语言的翻译文件作为翻译结果。

对于这个例子:

  • 复制一份 messages.xlf 文件。

如果你要翻译为其他语言,那就为每一个目标语种重复上述步骤。

翻译文本节点

在现实世界中,messages.fr.xlf 文件会被发给法语翻译,他们会使用某种 XLIFF 文件编辑器来翻译它。

你不需要任何编辑器或者法语知识就可以轻易的翻译本例子文件。

  • 打开 messages.fr.xlf 并找到第一个 <trans-unit> 区:

src/locale/messages.fr.xlf (<trans-unit>)content_copy<trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit>这个 XML 元素表示前面你加过 i18n 属性的那个打招呼用的 <h1> 标签。 注意这个 id=introductionHeader 的翻译单元是来自你以前设置过的自定义 id的。但是并没有源 HTML 所需的 @@ 前缀。

  • 复制 <source/> 标记,把它改名为 target,并把它的内容改为法语版的 “greeting”。 如果你要做的是更复杂的翻译,可能会使用由源文本、描述信息和含义等提供的信息和上下文来给出更恰当的法语翻译。

src/locale/messages.fr.xlf (<trans-unit>, after translation)content_copy<trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <target>Bonjour i18n !</target> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit>

  • 用同样的方式翻译其它文本节点:

src/locale/messages.fr.xlf (<trans-unit>)content_copy<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don&apos;t output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit>

Angular 的 i18n 工具会为这些翻译单元生成一些 id,不要修改它们。 每个 id 都是根据模板文本的内容及其含义来生成的。 无论你修改了文本还是含义,它们的 id 都会改变。 要了解更多信息,参见 关于翻译文件可维护性的讨论

翻译复数(plural)和选择(select)表达式

复数选择的 ICU 表达式都是分别提取的,所以在准备翻译时,它们需要格外注意。

你要从原始模板中其它地方识别出来的翻译单元来找到这些表达式之间的联系。 比如在这个例子中,你知道 select 一定会出现在 logo 的翻译单元的紧下方。

翻译复数

要翻译一个复数,就要翻译它的 ICU 格式中匹配的值:

src/locale/messages.fr.xlf (<trans-unit>)

content_copy<trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit>

你可以添加或删除复数的情况,每种语言都有自己的基数规则。(参见 CLDR 复数规则)。

翻译选择(select)

下面是组件模板中的 ICU 表达式 select 的例子:

src/app/app.component.html

content_copy<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>

提取工具会把它拆成两个翻译单元,因为 ICU 表达式是分别提取的。

第一个单元包含 select 之外的文本。 这里的 select 是一个占位符 <x id="ICU">,用来表示 select 中的消息。 翻译这段文本,如果需要就把占位符放在那里,但不要删除它。 如果删除了占位符,ICU 表达式就不会出现在翻译后的应用中。

src/locale/messages.fr.xlf (<trans-unit>)

content_copy</trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit>

第一个翻译单元的紧下方就是第二个翻译单元,包含 select 中的消息。照样翻译它。

src/locale/messages.fr.xlf (<trans-unit>)

content_copy<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit>

在翻译之后,它们会放在一起:

src/locale/messages.fr.xlf (<trans-unit>)

content_copy</trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit>

翻译嵌套的表达式

嵌套的表达式和前一节没有什么不同。就像上一个例子中那样,这里有两个翻译单元。 第一个包含嵌套表达式之外的文本:

src/locale/messages.fr.xlf (<trans-unit>)

content_copy<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit>

第二个包含完整的嵌套表达式:

src/locale/messages.fr.xlf (<trans-unit>)

content_copy<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit>

放在一起时:

src/locale/messages.fr.xlf (<trans-unit>)

content_copy<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit>

整个模板的翻译就完成了。下一节会讲如何翻译结果加载到应用程序中。

应用及其翻译文件

下面是例子应用及其翻译文件:

src/app/app.component.html

src/app/app.component.ts

src/app/app.module.ts

src/main.ts

src/locale/messages.fr.xlf

content_copy<h1 i18n="User welcome|An introduction header for this sample@@introductionHeader"> Hello i18n!</h1> <ng-container i18n>I don't output any element</ng-container> <br /> <img [src]="logo" i18n-title title="Angular logo" /><br><button (click)="inc(1)">+</button> <button (click)="inc(-1)">-</button><span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>{{minutes}})<br><br><button (click)="male()">&#9794;</button> <button (click)="female()">&#9792;</button> <button (click)="other()">&#9895;</button><span i18n>The author is {gender, select, male {male} female {female} other {other}}</span><br><br><span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}</span>

合并已经翻译的文件

要把已经翻译的文件合并到组件模板,就要用翻译过的文件编译应用。 为 Angular 编译器提供三种与翻译有关的信息:

  • 翻译文件

无论翻译文件是 .xlf 还是 Angular 支持的其它格式(比如 .xtb),其编译过程都是一样的。

你如何提供这些信息取决于你使用的是JIT(即时)编译器还是AOT(预先)编译器。

  • 使用AOT时,用配置项传入这些信息。

使用 AOT 编译器合并

AOT(预先)编译器是构建过程的一部分,它可以生成又小又快,直接可用的应用包。

当你使用 AOT 编译器进行国际化时,你必须为每种语言预先编译一个独立的应用包,并且依靠服务端语言检测或 URL 参数来找出合适的包。

你还要指示 AOT 编译器使用你的翻译配置,要这么做,你就要在 angular.json 文件中使用三个选项来配置翻译信息。

  • i18nFile: 翻译文件的路径。

content_copy"build": { ... "configurations": { ... "fr": { "aot": true, "outputPath": "dist/my-project-fr/", "i18nFile": "src/locale/messages.fr.xlf", "i18nFormat": "xlf", "i18nLocale": "fr", ... } }},"serve": { ... "configurations": { ... "fr": { "browserTarget": "*project-name*:build:fr" } }}

你可以通过 ng serveng build 命令来传入这些配置项。 下面的例子演示了如何使用前面部分创建的法语文件来启动开发服务器:

content_copyng serve --configuration=fr

For production builds, you define a separate production-fr build configuration in your angular.json.

content_copy"configurations": { ... "production-fr": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "outputPath": "dist/my-project-fr/", "i18nFile": "src/locale/messages.fr.xlf", "i18nFormat": "xlf", "i18nLocale": "fr", "i18nMissingTranslation": "error" }, ...}

The same configuration options can also be provided through the CLI with your existing production configuration.

content_copyng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr

用 JIT 编译器合并

JIT(即时)编译器在应用程序加载时,在浏览器中编译应用。 在使用 JIT 编译器的环境中翻译是一个动态的流程,包括:

  • 把合适的语言翻译文件导入成一个字符串常量

三种提供商帮助 JIT 编译在编译应用时,将模板文本翻译到某种语言:

  • TRANSLATIONS 是含有翻译文件内容的字符串。

在下面的 src/app/i18n-providers.ts 文件的 getTranslationProviders() 函数中,根据用户的语言环境和对应的翻译文件构建这些提供商:

src/main.ts

content_copyimport { enableProdMode, TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular/core';import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module';import { environment } from './environments/environment'; if (environment.production) { enableProdMode(} // use the require method provided by webpackdeclare const require;// we use the webpack raw-loader to return the content as a stringconst translations = require(`raw-loader!./locale/messages.fr.xlf` platformBrowserDynamic().bootstrapModule(AppModule, { providers: [ {provide: TRANSLATIONS, useValue: translations}, {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'} ]}

然后在主文件包中提供 LOCALE_ID

src/app/app.module.ts

content_copyimport { LOCALE_ID, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from '../src/app/app.component'; @NgModule{ imports: [ BrowserModule ], declarations: [ AppComponent ], providers: [ { provide: LOCALE_ID, useValue: 'fr' } ], bootstrap: [ AppComponent ] }) export class AppModule { }

汇报缺失的翻译

默认情况下,如果缺少了某个翻译文件,构建工具会成功但给出警告(如Missing translation for message "foo")。你可以配置 Angular 编译器生成的警告级别:

  • Error(错误):抛出错误,如果你使用的是 AOT 编译器,构建就会失败。如果使用的是 JIT 编译器,应用的加载就会失败。

你可以在 Angular CLI 构建配置的 configurations 区指定警告级别。下面这个例子展示了如何把警告级别设置为 error

content_copy"configurations": { ... "fr": { ... "i18nMissingTranslation": "error" } }

如果你要使用 JIT 编译器,就在启动时往编译器的配置中添加一个 MissingTranslationStrategy 属性来指定警告级别。下面的例子示范了如何把警告级别设置为 error

src/main.ts

content_copyimport { MissingTranslationStrategy } from '@angular/core';import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module'; // ... platformBrowserDynamic().bootstrapModule(AppModule, { missingTranslation: MissingTranslationStrategy.Error, providers: [ // ... ]}