docs(i18n): add new features (#2740)

This commit is contained in:
Jesús Rodríguez 2016-12-03 02:25:31 +01:00 committed by Ward Bell
parent 4a536732bc
commit 9fd3fa037d
16 changed files with 466 additions and 109 deletions

View File

@ -1,4 +1,4 @@
'use strict'; // necessary for es6 output in node 'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
@ -8,8 +8,26 @@ describe('i18n E2E Tests', () => {
browser.get(''); browser.get('');
}); });
it('should display i18n translated welcome: Bonjour i18n!', function () { it('should display i18n translated welcome: ¡Hola i18n!', function () {
expect(element(by.css('h1')).getText()).toEqual('Bonjour i18n!'); expect(element(by.css('h1')).getText()).toEqual('¡Hola i18n!');
});
it('should display the node texts without elements', function () {
expect(element(by.css('my-app')).getText()).toContain('No genero ningún elemento');
expect(element(by.css('my-app')).getText()).toContain('Yo tampoco genero ningún elemento');
});
it('should display the translated title attribute', function () {
const title = element(by.css('img')).getAttribute('title');
expect(title).toBe('Logo de Angular 2');
});
it('should display the plural of: a horde of wolves', function () {
expect(element.all(by.css('span')).get(0).getText()).toBe('ningún lobo');
});
it('should display the select of gender', function () {
expect(element.all(by.css('span')).get(1).getText()).toBe('El heroe es mujer');
}); });
}); });

View File

@ -1,6 +1,5 @@
**/*.ngfactory.ts **/*.ngfactory.ts
**/*.metadata.json **/*.metadata.json
**/messages.xlf
dist dist
!app/tsconfig.json !app/tsconfig.json
!rollup.js !rollup.js

View File

@ -9,3 +9,7 @@
<!--#docregion i18n-attribute-desc--> <!--#docregion i18n-attribute-desc-->
<h1 i18n="An introduction header for this sample">Hello i18n!</h1> <h1 i18n="An introduction header for this sample">Hello i18n!</h1>
<!--#enddocregion i18n-attribute-desc--> <!--#enddocregion i18n-attribute-desc-->
<!--#docregion i18n-title-->
<img [src]="logo" title="Angular 2 logo">
<!--#enddocregion i18n-title-->

View File

@ -1,4 +1,34 @@
<!--#docregion--> <!--#docregion-->
<!--#docregion i18n-attribute-meaning--> <!--#docregion i18n-attribute-meaning-->
<h1 i18n="User welcome|An introduction header for this sample">Hello i18n!</h1> <h1 i18n="User welcome|An introduction header for this sample">Hello i18n!</h1>
<!--#enddocregion i18n-attribute-meaning--> <!--#enddocregion i18n-attribute-meaning-->
<!--#docregion i18n-ng-container-->
<ng-container i18n>I don't output any element</ng-container>
<!--#enddocregion i18n-ng-container-->
<br />
<!--#docregion i18n-with-comment-->
<!--i18n: optional meaning|optional description -->
I don't output any element either
<!--/i18n-->
<!--#enddocregion i18n-with-comment-->
<br />
<!--#docregion i18n-title-translate-->
<img [src]="logo" i18n-title title="Angular 2 logo" />
<!--#enddocregion i18n-title-translate-->
<br>
<button (click)="inc(1)">+</button> <button (click)="inc(-1)">-</button>
<!--#docregion i18n-plural-->
<span i18n>{wolves, plural, =0 {no wolves} =1 {one wolf} =2 {two wolves} other {a wolf pack}}</span>
<!--#enddocregion i18n-plural-->
({{wolves}})
<br><br>
<button (click)="male()">&#9794;</button> <button (click)="female()">&#9792;</button>
<!--#docregion i18n-select-->
<span i18n>The hero is {gender, select, m {male} f {female}}</span>
<!--#enddocregion i18n-select-->
<br>

View File

@ -6,5 +6,15 @@ import { Component } from '@angular/core';
selector: 'my-app', selector: 'my-app',
templateUrl: 'app.component.html' templateUrl: 'app.component.html'
}) })
export class AppComponent { } export class AppComponent {
wolves = 0;
gender = 'f';
fly = true;
logo = 'https://angular.io/resources/images/logos/angular2/angular.png';
inc(i: number) {
this.wolves = Math.min(5, Math.max(0, this.wolves + i));
}
male() { this.gender = 'm'; }
female() { this.gender = 'f'; }
}

View File

@ -14,7 +14,7 @@ export function getTranslationProviders(): Promise<Object[]> {
return Promise.resolve(noProviders); return Promise.resolve(noProviders);
} }
// Ex: 'locale/messages.fr.xlf` // Ex: 'locale/messages.es.xlf`
const translationFile = `./locale/messages.${locale}.xlf`; const translationFile = `./locale/messages.${locale}.xlf`;
return getTranslationsWithSystemJs(translationFile) return getTranslationsWithSystemJs(translationFile)

View File

@ -18,7 +18,7 @@
<!-- #docregion i18n --> <!-- #docregion i18n -->
<script> <script>
// Get the locale id somehow // Get the locale id somehow
document.locale = 'fr'; document.locale = 'es';
// Map to the text plugin // Map to the text plugin
System.config({ System.config({

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<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 id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
<source>Hello i18n!</source>
<target>¡Hola i18n!</target>
<note priority="1" from="description">An introduction header for this sample</note>
<note priority="1" from="meaning">User welcome</note>
</trans-unit>
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
<source>I don&apos;t output any element</source>
<target>No genero ningún elemento</target>
</trans-unit>
<trans-unit id="df3cf8b55cb528cf8c00167e0b119bfb828538b5" datatype="html">
<source>
I don&apos;t output any element either
</source>
<target>Yo tampoco genero ningún elemento</target>
<note priority="1" from="description">optional description</note>
<note priority="1" from="meaning">optional meaning</note>
</trans-unit>
<trans-unit id="35ab5d6121ecbe0decdda638571a5a55ac77d5c4" datatype="html">
<source>Angular 2 logo</source>
<target>Logo de Angular 2</target>
</trans-unit>
<trans-unit id="6e22e74e8cbd3095560cfe08993c4fdfa3c50eb0" datatype="html">
<source/>
<target>{wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}}</target>
</trans-unit>
<trans-unit id="61cafedb85466ab789b3ae817bba1a545468ee1c" datatype="html">
<source>The hero is <x id="ICU"/></source>
<target>El heroe es <x id="ICU"/></target>
</trans-unit>
<trans-unit id="14c7055d67771a3b7b6888d282ac092896be06b6" datatype="html">
<source/>
<target>{gender, select, m {hombre} f {mujer}}</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -0,0 +1,54 @@
<!-- The `messages.es.xlf` after translation for documentation purposes -->
<!-- #docregion -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<!-- #docregion translated-hello -->
<trans-unit id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
<source>Hello i18n!</source>
<target>¡Hola i18n!</target>
<note priority="1" from="description">An introduction header for this sample</note>
<note priority="1" from="meaning">User welcome</note>
</trans-unit>
<!-- #enddocregion translated-hello -->
<!-- #docregion translated-other-nodes -->
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
<source>I don&apos;t output any element</source>
<target>No genero ningún elemento</target>
</trans-unit>
<trans-unit id="df3cf8b55cb528cf8c00167e0b119bfb828538b5" datatype="html">
<source>I don&apos;t output any element either</source>
<target>Yo tampoco genero ningún elemento</target>
<note priority="1" from="description">optional description</note>
<note priority="1" from="meaning">optional meaning</note>
</trans-unit>
<trans-unit id="35ab5d6121ecbe0decdda638571a5a55ac77d5c4" datatype="html">
<source>Angular 2 logo</source>
<target>Logo de Angular 2</target>
</trans-unit>
<!-- #enddocregion translated-other-nodes -->
<!-- #docregion translated-plural -->
<trans-unit id="6e22e74e8cbd3095560cfe08993c4fdfa3c50eb0" datatype="html">
<source/>
<target>{wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}}</target>
</trans-unit>
<!-- #enddocregion translated-plural -->
<!-- #docregion translated-select -->
<!-- #docregion translate-select-1 -->
<trans-unit id="61cafedb85466ab789b3ae817bba1a545468ee1c" datatype="html">
<source>The hero is <x id="ICU"/></source>
<target>El heroe es <x id="ICU"/></target>
</trans-unit>
<!-- #enddocregion translate-select-1 -->
<!-- #docregion translate-select-2 -->
<trans-unit id="14c7055d67771a3b7b6888d282ac092896be06b6" datatype="html">
<source/>
<target>{gender, select, m {hombre} f {mujer}}</target>
</trans-unit>
<!-- #enddocregion translate-select-2 -->
<!-- #enddocregion translated-select -->
</body>
</file>
</xliff>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<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 id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" 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>
</body>
</file>
</xliff>

View File

@ -1,17 +0,0 @@
<!-- The `messages.fr.xlf` after translation for documentation purposes -->
<!-- #docregion -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<!-- #docregion trans-unit -->
<trans-unit id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" 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>
<!-- #enddocregion trans-unit -->
</body>
</file>
</xliff>

View File

@ -1,7 +0,0 @@
<!-- #docregion -->
<trans-unit id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
<source>Hello i18n!</source>
<target/>
<note priority="1" from="description">An introduction header for this sample</note>
<note priority="1" from="meaning">User welcome</note>
</trans-unit>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<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 id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
<source>Hello i18n!</source>
<target/>
<note priority="1" from="description">An introduction header for this sample</note>
<note priority="1" from="meaning">User welcome</note>
</trans-unit>
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
<source>I don&apos;t output any element</source>
<target/>
</trans-unit>
<trans-unit id="df3cf8b55cb528cf8c00167e0b119bfb828538b5" datatype="html">
<source>
I don&apos;t output any element either
</source>
<target/>
<note priority="1" from="description">optional description</note>
<note priority="1" from="meaning">optional meaning</note>
</trans-unit>
<trans-unit id="35ab5d6121ecbe0decdda638571a5a55ac77d5c4" datatype="html">
<source>Angular 2 logo</source>
<target/>
</trans-unit>
<trans-unit id="6e22e74e8cbd3095560cfe08993c4fdfa3c50eb0" datatype="html">
<source/>
<target/>
</trans-unit>
<trans-unit id="61cafedb85466ab789b3ae817bba1a545468ee1c" datatype="html">
<source>The hero is <x id="ICU"/></source>
<target/>
</trans-unit>
<trans-unit id="14c7055d67771a3b7b6888d282ac092896be06b6" datatype="html">
<source/>
<target/>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -4,8 +4,8 @@
"app/**/*.css", "app/**/*.css",
"app/**/*.html", "app/**/*.html",
"app/**/*.ts", "app/**/*.ts",
"locale/messages.xlf", "messages.xlf",
"locale/messages.fr.xlf", "locale/messages.*.xlf",
"!**/*.[1].*", "!**/*.[1].*",

View File

@ -1,21 +1,26 @@
include ../_util-fns include ../_util-fns
a#top
:marked :marked
Angular's _internationalization_ ("_i18n_") tools help make your app available in multiple languages. Angular's _internationalization_ ("_i18n_") tools help make your app available in multiple languages.
<a id="top"></a>
## Table of contents ## Table of contents
* [Angular and i18n template translation](#angular-i18n) * [Angular and i18n template translation](#angular-i18n)
* [Mark text with the _i18n_ attribute](#i18n-attribute) * [Mark text with the _i18n_ attribute](#i18n-attribute)
* [Create a translation source file with the _ng-xi18n_ tool](#ng-xi18n) * [Add _i18n-..._ translation attributes](#translate-attributes)
* [Handle singular and plural](#cardinality)
* [Select among alternative texts](#select)
* [Use the **_ng-xi18n_ extraction tool** to create a translation source file](#ng-xi18n)
* [Translate](#translate) * [Translate](#translate)
* [Merge the completed translation file into the app](#merge) * [Merge the completed translation file into the app](#merge)
* [JiT configuration](#jit) * [JiT configuration](#jit)
* [AoT configuration](#aot) * [AoT configuration](#aot)
* [Translation file maintenance and _id_ changes](#maintenance)
:marked :marked
**Try this** <live-example name="cb-i18n">live example</live-example> of a JiT-compiled app, translated into French. **Try this** <live-example name="cb-i18n" title="i18n Example in Spanish">live example</live-example>
of a JiT-compiled app, translated into Spanish.
a#angular-i18n a#angular-i18n
@ -67,7 +72,7 @@ a#i18n-attribute
It will be removed by the compiler _after_ translation. It will be removed by the compiler _after_ translation.
:marked :marked
In the accompanying sample, an `<h1>` tag displays a simple English language greeting which you will translate to French: In the accompanying sample, an `<h1>` tag displays a simple English language greeting which you will translate to Spanish:
+makeExample('cb-i18n/ts/app/app.component.1.html', 'greeting', 'app/app.component.html')(format=".") +makeExample('cb-i18n/ts/app/app.component.1.html', 'greeting', 'app/app.component.html')(format=".")
:marked :marked
Add the `i18n` attribute to the tag to mark it for translation. Add the `i18n` attribute to the tag to mark it for translation.
@ -75,29 +80,132 @@ a#i18n-attribute
+makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-attribute', 'app/app.component.html')(format=".") +makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-attribute', 'app/app.component.html')(format=".")
:marked :marked
### Help the translator with a _description_ and _intent_
The translator may need a description of the message to translate it accurately. The translator may need a description of the message to translate it accurately.
Assign a description to the i18n attribute: Assign a description to the i18n attribute:
+makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-attribute-desc', 'app/app.component.html')(format=".") +makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-attribute-desc', 'app/app.component.html')(format=".")
:marked :marked
The true _meaning_ of the text may require some application context. The translator may need to know your _intent_ &mdash;
Add a contextual meaning to the assigned string, separating it from the description with the `|` character: the true _meaning_ of the text within _this particular_ application context &mdash;
in order to deliver a correct translation.
Add some contextual meaning to the assigned string, in front of the description,
separating it from the description with the `|` character (`<meaning>|<description>`):
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-attribute-meaning', 'app/app.component.html')(format=".") +makeExample('cb-i18n/ts/app/app.component.html', 'i18n-attribute-meaning', 'app/app.component.html')(format=".")
:marked :marked
While all appearance of a message with the _same_ meaning should have the _same_ translation, While all appearances of a message with the _same_ meaning have the _same_ translation,
a message with *different meanings* could have different translations. a message with *different meanings* could have different translations.
The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
to facilitiate contextually-specific translations. to facilitiate contextually-specific translations.
### Translate text without creating an element
Suppose there is a stretch of text that you'd like to translate.
You could wrap it in a `<span>` but for some reason (CSS comes to mind)
you don't want to create a new DOM element merely to facilitate translation.
Here are two techniques to try.
(1) Wrap the text in an `<ng-container>` element. The `<ng-container>` is never renderered:
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-ng-container', 'app/app.component.html')(format=".")
:marked
(2) Wrap the text in a pair of HTML comments:
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-with-comment', 'app/app.component.html')(format=".")
.l-main-section
a#translate-attributes
:marked
## Add _i18n-..._ translation attributes
You've added an image to your template. You care about accessibility too so you add a `title` tag:
+makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-title', 'app/app.component.html')(format=".")
:marked
The `title` attribute needs to be translated.
Angular i18n support has more translation attributes in the form,`i18n-x`, where `x` is the
name of the attribute to translate.
To translate the `title` on the `img` tag from the previous example, write:
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-title-translate', 'app/app.component.html')(format=".")
:marked
You can also assign a meaning and a description with the `i18n-x="<meaning>|<description>"` syntax.
.l-main-section
a#cardinality
:marked
## Handle singular and plural
Different languages have different pluralization rules.
Suppose your application says something about a collection of wolves.
In English, depending upon the number of wolves, you could display "no wolves", "one wolf", "two wolves", or "a wolf pack".
Other languages might express the _cardinality_ differently.
Here's how you could markup the component template to display the phrase appropriate to the number of wolves:
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-plural', 'app/app.component.html')(format=".")
:marked
* The first parameter is the key. It will be bound to the component property (`wolves`) that determines the number of wolves.
* The second parameter indentifies this as a `plural` translation type.
* The third parameter defines a pluralization pattern consisting of pluralization categories and their matching values.
Pluralization categories include:
* =0
* =1
* =5
* few
* other
Put the default _English_ translation in braces (`{}`) next to the pluralization category.
* When you're talking about one wolf, you could write `=1 {one wolf}`.
* For zero wolves, you could write `=0 {no wolves}`.
* For two wolves, you could write `=2 {two wolves}`.
You could keep this up for three, four, and every other number of wolves.
Or you could specify the **`other`** category as a catch-all for any unmatched cardinality
and write something like: `other {a wolf pack}`.
.l-sub-section
:marked
This syntax conforms to the
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU Message Format</a>
which derives from the
<a href="http://cldr.unicode.org/" target="_blank" title="CLDR">Common Locale Data Repository (CLDR)</a>
which specifies the
<a href="http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules" target="_blank" title="Pluralization Rules">pluralization rules</a>.
a#select
:marked
## Select
The application displays different text depending upon whether the hero is male or female.
These text alternatives require translation too.
You can handle this with a `select` translation.
A `select` also follows the
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU message syntax</a>.
You choose among alternative translation based on a string value instead of a number.
The following format message in the component template binds to the component's `gender` property which outputs either an 'm' or an 'f'.
The message maps those values to the appropriate translation:
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-select', 'app/app.component.html')(format=".")
a#ng-xi18n a#ng-xi18n
.l-main-section .l-main-section
:marked :marked
## Create a translation source file with the _ng-xi18n_ tool ## Create a translation source file with the _ng-xi18n_ tool
Use the `ng-xi18n` extraction tool to extract the `i18n`-marked texts Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
into a translation source file in an industry standard format. into a translation source file in an industry standard format.
This is an Angular CLI tool in the `@angular/compiler-cli` npm package. This is an Angular CLI tool in the `@angular/compiler-cli` npm package.
@ -112,48 +220,68 @@ code-example(language="sh" class="code-shell").
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
./node_modules/.bin/ng-xi18n ./node_modules/.bin/ng-xi18n
.l-sub-section
:marked
Windows users may have to quote the command: like this `"./node_modules/.bin/ng-xi18n"`
:marked :marked
By default the tool generates a translation file named **`messages.xlf`** in the By default the tool generates a translation file named **`messages.xlf`** in the
<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML Localisation Interchange File Format (XLIFF, version 1.2)</a>. <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML Localisation Interchange File Format (XLIFF, version 1.2)</a>.
code-example(language="sh" class="code-shell"). a#other-formats
./node_modules/.bin/ng-xi18n --i18nFormat=xmb
.l-sub-section
:marked
Windows users may have to quote the command:
code-example(language="sh").
"./node_modules/.bin/ng-xi18n"
:marked
Consider adding a convenience shortcut to the `scripts` section of the `package.json`
to make the command easier to remember and run:
code-example(format='.').
"scripts": {
"i18n": "ng-xi18n",
...
}
:marked
Now you can enter:
code-example(language="sh" class="code-shell").
npm run i18n
:marked :marked
### Other translation formats ### Other translation formats
You can generate a file named **`messages.xmb`** in the You can generate a file named **`messages.xmb`** in the
<a href="http://cldr.unicode.org/development/development-process/design-proposals/xmb" target="_blank">XML Message Bundle (XMB)</a> format <a href="http://cldr.unicode.org/development/development-process/design-proposals/xmb" target="_blank">XML Message Bundle (XMB)</a> format
by adding the `--i18nFormat=xmb` switch. by adding the `--i18nFormat=xmb` flag.
code-example(language="sh" class="code-shell").
./node_modules/.bin/ng-xi18n --i18nFormat=xmb
:marked
This sample sticks with the _XLIFF_ format. This sample sticks with the _XLIFF_ format.
a#ng-xi18n-options
:marked
### Other options
You may have to specify additional options.
For example, if the `tsconfig.json` TypeScript configuration file is located other than in the root folder,
you must identify the path to it with the `-p` option:
code-example(language="sh" class="code-shell").
./node_modules/.bin/ng-xi18n -p path/to/tsconfig.json
./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p path/to/tsconfig.json
a#npm-i18n-script
:marked
### Add an _npm_ script for convenience
Consider adding a convenience shortcut to the `scripts` section of the `package.json`
to make the command easier to remember and run:
code-example(format='.' language='sh' ).
"scripts": {
"i18n": "ng-xi18n",
...
}
:marked
Now you can issue command variations such as these:
code-example(language="sh" class="code-shell").
npm run i18n
npm run i18n -- -p path/to/tsconfig.json
npm run i18n -- --i18nFormat=xmb -p path/to/tsconfig.json
:marked
Note the `--` flag before the options.
It tells _npm_ that every flag thereafter should be passed through to `ng-xi18n`.
a#translate a#translate
.l-main-section .l-main-section
:marked :marked
## Translate _le message textuel_ ## Translate _los mensajes de texto_
The `ng-xi18n` command generated a translation source file in the project root folder named `messages.xlf`. The `ng-xi18n` command generated a translation source file in the project root folder named `messages.xlf`.
The next step is to translate the English language template text into the specific language translation The next step is to translate the English language template text into the specific language translation
files. The cookbook sample creates a French translation file. files. The cookbook sample creates a Spanish translation file.
a#localization-folder a#localization-folder
:marked :marked
@ -169,39 +297,88 @@ a#localization-folder
Localization and internationalization are Localization and internationalization are
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>. <a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>.
:marked :marked
This sample follows that suggestion. It has `locale` folder immediately under the project root. This sample follows that suggestion. It has a `locale` folder under the project root.
Assets within the folder carry a filename extension that matches a language-culture code from a Assets within the folder carry a filename extension that matches a language-culture code from a
<a href="https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx" target="_blank">well-known codeset</a>. <a href="https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx" target="_blank">well-known codeset</a>.
Move `messages.xlf` into the `locale` folder where it will become the source for all language-specific translations. Make a copy of the `messages.xlf` file in the `locale` folder and
Then make a copy for the French language named `messages.fr.xlf` . rename it `messages.es.xlf`for the Spanish language translation.
Do the same for each target language.
Follow the same convention for each target language. ### Translate text nodes
In the real world, you send the `messages.es.xlf` file to a Spanish translator who fills in the translations
### Translate using one of the
In the real world, you send the `messages.fr.xlf` file to a French translator who would fill in the translations
using one of the
<a href="https://en.wikipedia.org/wiki/XLIFF#Editors" target="_blank">many XLIFF file editors</a>. <a href="https://en.wikipedia.org/wiki/XLIFF#Editors" target="_blank">many XLIFF file editors</a>.
This sample file is easy to translate without a special editor or knowledge of French. This sample file is easy to translate without a special editor or knowledge of Spanish.
Open `messages.fr.xlf` and find the `<trans-unit>` section: Open `messages.es.xlf` and find the first `<trans-unit>` section:
+makeExample('cb-i18n/ts/locale/trans-unit.html', '', 'locale/messages.fr.xlf (<trans-unit>)')(format=".") +makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translated-hello', 'locale/messages.es.xlf (<trans-unit>)')(format=".")
:marked :marked
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute. This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
Using the _source_, _description_, and _meaning_ elements to guide your translation, Using the _source_, _description_, and _meaning_ elements to guide your translation,
replace the `<target/>` tag with the French greeting: replace the `<target/>` tag with the Spanish greeting:
+makeExample('cb-i18n/ts/locale/messages.fr.xlf.html', 'trans-unit', 'locale/messages.fr.xlf (<trans-unit>, after translation)')(format=".") +makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translated-hello', 'locale/messages.es.xlf (<trans-unit>, after translation)')(format=".")
:marked
Note that the `id` is generated by the tool. Don't touch it. .alert.is-important
Its value depends on the content of the message and its assigned meaning.
Change either factor and the `id` changes as well.
.alert.is-helpful
:marked :marked
Repeat the extraction process whenever you add new app messages or edit existing ones. Note that the `id` is generated by the tool. **Don't touch it.**
Be careful not to lose the previous translations. Its value depends on the content of the message and its assigned meaning.
Specialized software can help manage the change process. Change either factor and the `id` changes as well.
See the **[translation file maintenance discussion](#maintenance)** below.
:marked
Translate the other text nodes the same way:
+makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translated-other-nodes', 'locale/messages.es.xlf (<trans-unit>)')(format=".")
a#translate-plural-select
:marked
## Translate _plural_ and _select_
Translating _plural_ and _select_ messages is a little tricky.
The `<source>` tag is empty for `plural` and `select` translation units which makes them hard to correlate with the original template.
The ICU rules are not yet supported in the `XLIFF` format; they soon will be.
The ICU rules _are_ supported in the `XMB` format.
You'll just have to look for them in relation to other translation units that you recognize from elsewhere in the source template.
In this example, you know the translation unit for the `select` must be just below the translation unit for the logo.
:marked
### Translate _plural_
To translate a `plural`, translate its ICU format match values:
+makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translated-plural', 'locale/messages.es.xlf (<trans-unit>)')(format=".")
:marked
### Translate _select_
The `select` behaves a little differently. Here again is the ICU format message in the component template:
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-select', 'app/app.component.html')(format=".")
:marked
The extraction tool broke that into _two_ translation units.
The first unit contains the text that was _outside_ the `select`.
In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `select` message.
Translate the text and leave the placeholder where it is.
+makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translate-select-1', 'locale/messages.es.xlf (<trans-unit>)')(format=".")
:marked
The second translation unit, immediately below the first one, contains the `select` message. Translate that.
+makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translate-select-2', 'locale/messages.es.xlf (<trans-unit>)')(format=".")
:marked
Here they are together, after translation:
+makeExample('cb-i18n/ts/locale/messages.es.xlf.html', 'translated-select', 'locale/messages.es.xlf (<trans-unit>)')(format=".")
.l-main-content
:marked
The entire template translation is complete. It's time to incorporate that translation in the application.
#app-pre-translation #app-pre-translation
:marked :marked
@ -214,13 +391,13 @@ a#localization-folder
cb-i18n/ts/app/app.component.ts, cb-i18n/ts/app/app.component.ts,
cb-i18n/ts/app/app.module.ts, cb-i18n/ts/app/app.module.ts,
cb-i18n/ts/app/main.1.ts, cb-i18n/ts/app/main.1.ts,
cb-i18n/ts/locale/messages.fr.xlf.html cb-i18n/ts/locale/messages.es.xlf.html
`, '', ` `, '', `
app/app.component.html, app/app.component.html,
app/app.component.ts, app/app.component.ts,
app/app.module.ts, app/app.module.ts,
app/main.ts, app/main.ts,
locale/messages.fr.xlf locale/messages.es.xlf
`) `)
a#merge a#merge
@ -237,7 +414,7 @@ a#merge
* the translation file * the translation file
* the translation file format * the translation file format
* the <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a> * the <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a>
(`fr` or `en-US` for instance) (`es` or `en-US` for instance)
_How_ you provide this information depends upon whether you compile with _How_ you provide this information depends upon whether you compile with
the JiT (_Just-in-Time_) compiler or the AoT (_Ahead-of-Time_) compiler. the JiT (_Just-in-Time_) compiler or the AoT (_Ahead-of-Time_) compiler.
@ -320,7 +497,7 @@ a#text-plugin
Notice that it waits for the `getTranslationProviders` promise to resolve before Notice that it waits for the `getTranslationProviders` promise to resolve before
bootstrapping the app. bootstrapping the app.
The app is now _internationalized_ for English and French and there is a clear path for adding The app is now _internationalized_ for English and Spanish and there is a clear path for adding
more languages. more languages.
a#aot a#aot
@ -352,12 +529,27 @@ a#aot
* `--locale`: the name of the locale * `--locale`: the name of the locale
* `--i18nFormat`: the format of the localization file * `--i18nFormat`: the format of the localization file
For this sample, the French language command would be For this sample, the Spanish language command would be
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
./node_modules/.bin/ngc --i18nFile=./locale/messages.fr.xlf --locale=fr --i18nFormat=xlf ./node_modules/.bin/ngc --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf
.l-sub-section .l-sub-section
:marked :marked
Windows users may have to quote the command: Windows users may have to quote the command:
code-example(language="sh" class="code-shell"). code-example(language="sh" class="code-shell").
"./node_modules/.bin/ngc" --i18nFile=./locale/messages.fr.xlf --locale=fr --i18nFormat=xlf "./node_modules/.bin/ngc" --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf
a#maintenance
:marked
## Translation file maintenance and _id_ changes
As the application evolves, you will change the _i18n_ markup and re-run the `ng-xi18n` extraction tool many times.
The _new_ markup that you add is not a problem.
But _most_ changes to _existing_ markup will trigger generation of _new_ `id`s for the affected translation units.
After an `id` changes, the translation files are no longer in-sync.
**All translated versions of the application will fail** during re-compilation.
The error messages identify the old `id`s that are no longer valid but they don't tell you what the new `id`s should be.
**Commit all translation message files to source control**, especially the English source `messages.xlf`.
The difference between the old and the new `messages.xlf` file will help you find and update `id` changes across your translation files.

View File

@ -5,6 +5,11 @@ block includes
The Angular documentation is a living document with continuous improvements. The Angular documentation is a living document with continuous improvements.
This log calls attention to recent significant changes. This log calls attention to recent significant changes.
## Internationalization: pluralization and _select_ (2016-11-30)
The [Internationalization (i18n)](i18n.html) guide explains how to handle pluralization and
translation of alternative texts with `select`.
The sample demonstrates these features too.
## Testing: karma file updates (2016-11-30) ## Testing: karma file updates (2016-11-30)
* karma.config + karma-test-shim can handle multiple spec source paths; * karma.config + karma-test-shim can handle multiple spec source paths;
see quickstart issue: [angular/quickstart#294](https://github.com/angular/quickstart/issues/294) see quickstart issue: [angular/quickstart#294](https://github.com/angular/quickstart/issues/294)