diff --git a/aio/content/examples/i18n/src/app/app.component.1.html b/aio/content/examples/i18n/doc-files/app.component.html
similarity index 96%
rename from aio/content/examples/i18n/src/app/app.component.1.html
rename to aio/content/examples/i18n/doc-files/app.component.html
index ef8c968d31..98daded79e 100644
--- a/aio/content/examples/i18n/src/app/app.component.1.html
+++ b/aio/content/examples/i18n/doc-files/app.component.html
@@ -29,4 +29,3 @@
-Contact GitHub API Training Shop Blog About
diff --git a/aio/content/examples/i18n/src/app/app.locale_data.ts b/aio/content/examples/i18n/doc-files/app.locale_data.ts
similarity index 100%
rename from aio/content/examples/i18n/src/app/app.locale_data.ts
rename to aio/content/examples/i18n/doc-files/app.locale_data.ts
diff --git a/aio/content/examples/i18n/doc-files/app.locale_data_extra.ts b/aio/content/examples/i18n/doc-files/app.locale_data_extra.ts
new file mode 100644
index 0000000000..69e637d6a7
--- /dev/null
+++ b/aio/content/examples/i18n/doc-files/app.locale_data_extra.ts
@@ -0,0 +1,7 @@
+// #docregion import-locale-extra
+import { registerLocaleData } from '@angular/common';
+import localeFrCa from '@angular/common/locales/fr-CA';
+import localeFrCaExtra from '@angular/common/locales/extra/fr-CA';
+
+registerLocaleData(localeFrCa, localeFrCaExtra);
+// #enddocregion import-locale-extra
diff --git a/aio/content/examples/i18n/doc-files/app.module.ts b/aio/content/examples/i18n/doc-files/app.module.ts
new file mode 100644
index 0000000000..0f5d2d7e9f
--- /dev/null
+++ b/aio/content/examples/i18n/doc-files/app.module.ts
@@ -0,0 +1,13 @@
+// #docregion
+import { 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 { }
diff --git a/aio/content/examples/i18n/src/main.1.ts b/aio/content/examples/i18n/doc-files/main.1.ts
similarity index 54%
rename from aio/content/examples/i18n/src/main.1.ts
rename to aio/content/examples/i18n/doc-files/main.1.ts
index f332d1d245..0740658908 100644
--- a/aio/content/examples/i18n/src/main.1.ts
+++ b/aio/content/examples/i18n/doc-files/main.1.ts
@@ -1,6 +1,12 @@
// #docregion
+import { enableProdMode } 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();
+}
platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/aio/content/examples/i18n/doc-files/main.2.ts b/aio/content/examples/i18n/doc-files/main.2.ts
new file mode 100644
index 0000000000..0b68bc10fe
--- /dev/null
+++ b/aio/content/examples/i18n/doc-files/main.2.ts
@@ -0,0 +1,22 @@
+// #docregion
+import { 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 webpack
+declare const require;
+// we use the webpack raw-loader to return the content as a string
+const translations = require(`raw-loader!./locale/messages.fr.xlf`);
+
+platformBrowserDynamic().bootstrapModule(AppModule, {
+ providers: [
+ {provide: TRANSLATIONS, useValue: translations},
+ {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'}
+ ]
+});
diff --git a/aio/content/examples/i18n/doc-files/main.3.ts b/aio/content/examples/i18n/doc-files/main.3.ts
new file mode 100644
index 0000000000..dc5f919e1f
--- /dev/null
+++ b/aio/content/examples/i18n/doc-files/main.3.ts
@@ -0,0 +1,13 @@
+// #docregion
+import { MissingTranslationStrategy } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { AppModule } from './app/app.module';
+
+// ...
+
+platformBrowserDynamic().bootstrapModule(AppModule, {
+ missingTranslation: MissingTranslationStrategy.Error,
+ providers: [
+ // ...
+ ]
+});
diff --git a/aio/content/examples/i18n/doc-files/messages.fr.xlf.html b/aio/content/examples/i18n/doc-files/messages.fr.xlf.html
new file mode 100644
index 0000000000..c0c0b89a4e
--- /dev/null
+++ b/aio/content/examples/i18n/doc-files/messages.fr.xlf.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+ An introduction header for this sample
+ User welcome
+
+
+
+
+
+
+
+ Bonjour i18n !
+ An introduction header for this sample
+ User welcome
+
+
+
+
+
+
+
+ Je n'affiche aucun élément
+
+
+
+ Logo d'Angular
+
+
+
+
+
+ {VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a minutes} }
+
+
+
+
+
+
+ L'auteur est
+
+
+
+
+
+ {VAR_SELECT, select, m {un homme} f {une femme} o {autre} }
+
+
+
+
+
+
+
+ Mis à jour:
+
+
+
+
+
+ {VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a minutes par {VAR_SELECT, select, m {un homme} f {une femme} o {autre} }} }
+
+
+
+
+
+
+
diff --git a/aio/content/examples/i18n/e2e-spec.ts b/aio/content/examples/i18n/e2e-spec.ts
deleted file mode 100644
index 03811d058f..0000000000
--- a/aio/content/examples/i18n/e2e-spec.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict'; // necessary for es6 output in node
-
-import { browser, element, by } from 'protractor';
-
-describe('i18n E2E Tests', () => {
-
- beforeEach(function () {
- browser.get('');
- });
-
- it('should display i18n translated welcome: ¡Hola i18n!', function () {
- 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');
- });
-
- it('should display the translated title attribute', function () {
- const title = element(by.css('img')).getAttribute('title');
- expect(title).toBe('Logo de Angular');
- });
-
- 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');
- });
-
- it('should display the nested expression', function() {
- expect(element.all(by.css('span')).get(2).getText()).toBe('Aquí tenemos: 3 mujeres');
- });
-
-});
diff --git a/aio/content/examples/i18n/e2e/app.e2e-spec.ts b/aio/content/examples/i18n/e2e/app.e2e-spec.ts
new file mode 100644
index 0000000000..ebafb55f96
--- /dev/null
+++ b/aio/content/examples/i18n/e2e/app.e2e-spec.ts
@@ -0,0 +1,45 @@
+import { browser, element, by } from 'protractor';
+
+describe('i18n E2E Tests', () => {
+
+ beforeEach(function () {
+ browser.get('');
+ });
+
+ it('should display i18n translated welcome: Bonjour !', function () {
+ expect(element(by.css('h1')).getText()).toEqual('Bonjour i18n !');
+ });
+
+ it('should display the node texts without elements', function () {
+ expect(element(by.css('app-root')).getText()).toContain(`Je n'affiche aucun élément`);
+ });
+
+ it('should display the translated title attribute', function () {
+ const title = element(by.css('img')).getAttribute('title');
+ expect(title).toBe(`Logo d'Angular`);
+ });
+
+ it('should display the ICU plural expression', function () {
+ expect(element.all(by.css('span')).get(0).getText()).toBe(`Mis à jour à l'instant`);
+ });
+
+ it('should display the ICU select expression', function () {
+ const selectIcuExp = element.all(by.css('span')).get(1);
+ expect(selectIcuExp.getText()).toBe(`L'auteur est une femme`);
+ element.all(by.css('button')).get(2).click();
+ expect(selectIcuExp.getText()).toBe(`L'auteur est un homme`);
+ });
+
+ it('should display the nested expression', function() {
+ const nestedExp = element.all(by.css('span')).get(2);
+ const incBtn = element.all(by.css('button')).get(0);
+ expect(nestedExp.getText()).toBe(`Mis à jour: à l'instant`);
+ incBtn.click();
+ expect(nestedExp.getText()).toBe(`Mis à jour: il y a une minute`);
+ incBtn.click();
+ incBtn.click();
+ element.all(by.css('button')).get(4).click();
+ expect(nestedExp.getText()).toBe(`Mis à jour: il y a 3 minutes par autre`);
+ });
+
+});
diff --git a/aio/content/examples/i18n/example-config.json b/aio/content/examples/i18n/example-config.json
index e69de29bb2..1188a704a3 100644
--- a/aio/content/examples/i18n/example-config.json
+++ b/aio/content/examples/i18n/example-config.json
@@ -0,0 +1,3 @@
+{
+ "projectType": "i18n"
+}
diff --git a/aio/content/examples/i18n/messages.xlf b/aio/content/examples/i18n/messages.xlf
deleted file mode 100644
index a7f8ca199f..0000000000
--- a/aio/content/examples/i18n/messages.xlf
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
- An introduction header for this sample
- User welcome
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/aio/content/examples/i18n/plnkr.json b/aio/content/examples/i18n/plnkr.json
deleted file mode 100644
index 36f2685129..0000000000
--- a/aio/content/examples/i18n/plnkr.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "description": "i18n",
- "basePath": "src/",
- "files": [
- "app/**/*.css",
- "app/**/*.html",
- "app/**/*.ts",
- "messages.xlf",
- "locale/messages.*.xlf",
-
- "!**/*.[1].*",
-
- "main.ts",
- "styles.css",
- "systemjs-text-plugin.js",
- "index.html"
- ],
- "tags": ["i18n"]
-}
diff --git a/aio/content/examples/i18n/src/app/app.component.html b/aio/content/examples/i18n/src/app/app.component.html
index ec46ed8095..f15133d2ff 100644
--- a/aio/content/examples/i18n/src/app/app.component.html
+++ b/aio/content/examples/i18n/src/app/app.component.html
@@ -17,19 +17,19 @@
-{wolves, plural, =0 {no wolves} =1 {one wolf} =2 {two wolves} other {a wolf pack}}
+Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}
-({{wolves}})
+({{minutes}})
-
+
-The hero is {gender, select, m {male} f {female}}
+The author is {gender, select, m {male} f {female} o {other}}
-Here we have: {count, plural,
- =0 {no one}
- =1 {one {gender, select, male {man} female {woman}}}
- other {{{heroes.length}} {gender, select, male {men} female {women}}}
-}
+Updated: {minutes, plural,
+ =0 {just now}
+ =1 {one minute ago}
+ other {{{minutes}} minutes ago by {gender, select, m {male} f {female} o {other}}}}
+
diff --git a/aio/content/examples/i18n/src/app/app.component.ts b/aio/content/examples/i18n/src/app/app.component.ts
index 60106263a0..a39745714f 100644
--- a/aio/content/examples/i18n/src/app/app.component.ts
+++ b/aio/content/examples/i18n/src/app/app.component.ts
@@ -2,20 +2,20 @@
import { Component } from '@angular/core';
@Component({
- selector: 'my-app',
+ selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
- wolves = 0;
+ minutes = 0;
gender = 'f';
fly = true;
logo = 'https://angular.io/assets/images/logos/angular/angular.png';
- count = 3;
heroes: string[] = ['Magneta', 'Celeritas', 'Dynama'];
inc(i: number) {
- this.wolves = Math.min(5, Math.max(0, this.wolves + i));
+ this.minutes = Math.min(5, Math.max(0, this.minutes + i));
}
- male() { this.gender = 'm'; }
+ male() { this.gender = 'm'; }
female() { this.gender = 'f'; }
+ other() { this.gender = 'o'; }
}
diff --git a/aio/content/examples/i18n/src/app/app.locale_data_extra.ts b/aio/content/examples/i18n/src/app/app.locale_data_extra.ts
deleted file mode 100644
index 6630f02efc..0000000000
--- a/aio/content/examples/i18n/src/app/app.locale_data_extra.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// #docregion import-locale-extra
-import { registerLocaleData } from '@angular/common';
-import localeEnGB from '@angular/common/locales/en-GB';
-import localeEnGBExtra from '@angular/common/locales/extra/en-GB';
-
-registerLocaleData(localeEnGB, localeEnGBExtra);
-// #enddocregion import-locale-extra
diff --git a/aio/content/examples/i18n/src/app/app.module.ts b/aio/content/examples/i18n/src/app/app.module.ts
index 64ad44075b..530cd295e7 100644
--- a/aio/content/examples/i18n/src/app/app.module.ts
+++ b/aio/content/examples/i18n/src/app/app.module.ts
@@ -1,13 +1,12 @@
// #docregion
-import { NgModule } from '@angular/core';
+import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
-import { AppComponent } from './app.component';
+import { AppComponent } from './app.component';
@NgModule({
- imports: [ BrowserModule ],
+ imports: [ BrowserModule ],
declarations: [ AppComponent ],
- bootstrap: [ AppComponent ]
+ bootstrap: [ AppComponent ]
})
-
export class AppModule { }
diff --git a/aio/content/examples/i18n/src/app/i18n-providers.ts b/aio/content/examples/i18n/src/app/i18n-providers.ts
deleted file mode 100644
index 3c74d0e8e0..0000000000
--- a/aio/content/examples/i18n/src/app/i18n-providers.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// #docplaster
-// #docregion without-missing-translation
-import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID, MissingTranslationStrategy, StaticProvider } from '@angular/core';
-import { CompilerConfig } from '@angular/compiler';
-
-export function getTranslationProviders(): Promise {
-
- // Get the locale id from the global
- const locale = document['locale'] as string;
-
- // return no providers if fail to get translation file for locale
- const noProviders: StaticProvider[] = [];
-
- // No locale or U.S. English: no translation providers
- if (!locale || locale === 'en-US') {
- return Promise.resolve(noProviders);
- }
-
- // Ex: 'locale/messages.es.xlf`
- const translationFile = `./locale/messages.${locale}.xlf`;
-
- // #docregion missing-translation
- return getTranslationsWithSystemJs(translationFile)
- .then( (translations: string ) => [
- { provide: TRANSLATIONS, useValue: translations },
- { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
- { provide: LOCALE_ID, useValue: locale },
- // #enddocregion without-missing-translation
- { provide: CompilerConfig, useValue: new CompilerConfig({ missingTranslation: MissingTranslationStrategy.Error }) }
- // #docregion without-missing-translation
- ])
- .catch(() => noProviders); // ignore if file not found
- // #enddocregion missing-translation
-}
-
-declare var System: any;
-
-function getTranslationsWithSystemJs(file: string) {
- return System.import(file + '!text'); // relies on text plugin
-}
-// #enddocregion without-missing-translation
diff --git a/aio/content/examples/i18n/src/index.html b/aio/content/examples/i18n/src/index.html
index ce90cd24c4..6b26810c50 100644
--- a/aio/content/examples/i18n/src/index.html
+++ b/aio/content/examples/i18n/src/index.html
@@ -1,39 +1,14 @@
-
+
- Angular i18n example
+ Angular i18n example
-
-
-
-
-
-
-
-
-
-
-
-
-
- Loading...
+ Loading...
+
diff --git a/aio/content/examples/i18n/src/locale/messages.es.xlf b/aio/content/examples/i18n/src/locale/messages.es.xlf
deleted file mode 100644
index 13dff26fc0..0000000000
--- a/aio/content/examples/i18n/src/locale/messages.es.xlf
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
- ¡Hola i18n!
- An introduction header for this sample
- User welcome
-
-
-
- No genero ningún elemento
-
-
-
- Logo de Angular
-
-
-
- {wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}}
-
-
-
- El heroe es
-
-
-
- {gender, select, m {hombre} f {mujer}}
-
-
-
- Aquí tenemos:
-
-
-
-
- {count, plural,
- =0 { nadie }
- =1 {{gender, select, m {un hombre} f {una mujer}}}
- other {{{heroes.length}} {gender, select, m {hombres} f {mujeres}}}
- }
-
-
-
-
-
diff --git a/aio/content/examples/i18n/src/locale/messages.es.xlf.html b/aio/content/examples/i18n/src/locale/messages.es.xlf.html
deleted file mode 100644
index eb5b96f15e..0000000000
--- a/aio/content/examples/i18n/src/locale/messages.es.xlf.html
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- ¡Hola i18n!
- An introduction header for this sample
- User welcome
-
-
-
-
-
-
-
- No genero ningún elemento
-
-
-
- Logo de Angular
-
-
-
-
-
- {wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}}
-
-
-
-
-
-
- El heroe es
-
-
-
-
-
- {gender, select, m {hombre} f {mujer}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Aquí tenemos:
-
-
-
-
-
-
- {count, plural,
- =0 { nadie }
- =1 {{gender, select, m {un hombre} f {una mujer}}}
- other {{{heroes.length}} {gender, select, m {hombres} f {mujeres}}}
- }
-
-
-
-
-
-
-
-
diff --git a/aio/content/examples/i18n/src/locale/messages.fr.xlf b/aio/content/examples/i18n/src/locale/messages.fr.xlf
new file mode 100644
index 0000000000..4b16a22496
--- /dev/null
+++ b/aio/content/examples/i18n/src/locale/messages.fr.xlf
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+ Bonjour i18n !
+
+
+ app\app.component.ts
+ 4
+
+ An introduction header for this sample
+ User welcome
+
+
+
+ Je n'affiche aucun élément
+
+ app\app.component.ts
+ 10
+
+
+
+
+ Logo d'Angular
+
+ app\app.component.ts
+ 16
+
+
+
+
+ Mis à jour
+
+ app\app.component.ts
+ 21
+
+
+
+
+ {VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a minutes} }
+
+ app\app.component.ts
+ 21
+
+
+
+
+ L'auteur est
+
+ app\app.component.ts
+ 27
+
+
+
+
+ {VAR_SELECT, select, m {un homme} f {une femme} o {autre} }
+
+ app\app.component.ts
+ 27
+
+
+
+
+ Mis à jour:
+
+
+ app\app.component.ts
+ 31
+
+
+
+
+ {VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a minutes par {VAR_SELECT, select, m {un homme} f {une femme} o {autre} }} }
+
+ app\app.component.ts
+ 31
+
+
+
+
+
diff --git a/aio/content/examples/i18n/src/locale/messages.xlf b/aio/content/examples/i18n/src/locale/messages.xlf
new file mode 100644
index 0000000000..4275f0bc04
--- /dev/null
+++ b/aio/content/examples/i18n/src/locale/messages.xlf
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+ app\app.component.ts
+ 4
+
+ An introduction header for this sample
+ User welcome
+
+
+
+
+ app\app.component.ts
+ 10
+
+
+
+
+
+ app\app.component.ts
+ 16
+
+
+
+
+
+ app\app.component.ts
+ 21
+
+
+
+
+
+ app\app.component.ts
+ 21
+
+
+
+
+
+ app\app.component.ts
+ 27
+
+
+
+
+
+ app\app.component.ts
+ 27
+
+
+
+
+
+ app\app.component.ts
+ 31
+
+
+
+
+
+ app\app.component.ts
+ 31
+
+
+
+
+
diff --git a/aio/content/examples/i18n/src/main.ts b/aio/content/examples/i18n/src/main.ts
index 894cecfb10..0740658908 100644
--- a/aio/content/examples/i18n/src/main.ts
+++ b/aio/content/examples/i18n/src/main.ts
@@ -1,10 +1,12 @@
// #docregion
-import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
-import { getTranslationProviders } from './app/i18n-providers';
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
-getTranslationProviders().then(providers => {
- const options = { providers };
- platformBrowserDynamic().bootstrapModule(AppModule, options);
-});
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/aio/content/examples/i18n/src/systemjs-text-plugin.js b/aio/content/examples/i18n/src/systemjs-text-plugin.js
deleted file mode 100644
index 581f87db1a..0000000000
--- a/aio/content/examples/i18n/src/systemjs-text-plugin.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// #docregion
-/*
- SystemJS Text plugin from
- https://github.com/systemjs/plugin-text/blob/master/text.js
-*/
-exports.translate = function (load) {
- if (this.builder && this.transpiler) {
- load.metadata.format = 'esm';
- return 'exp' + 'ort var __useDefault = true; exp' + 'ort default ' + JSON.stringify(load.source) + ';';
- }
-
- load.metadata.format = 'amd';
- return 'def' + 'ine(function() {\nreturn ' + JSON.stringify(load.source) + ';\n});';
-}
diff --git a/aio/content/examples/i18n/zipper.json b/aio/content/examples/i18n/zipper.json
new file mode 100644
index 0000000000..7f9c0a43cc
--- /dev/null
+++ b/aio/content/examples/i18n/zipper.json
@@ -0,0 +1,11 @@
+{
+ "files":[
+ "!dist/",
+ "!**/*.d.ts",
+ "!src/**/*.js",
+ "!doc-files/**/*",
+ "**/*.xlf"
+ ],
+ "removeSystemJsConfig": true,
+ "type": "i18n"
+}
diff --git a/aio/content/guide/i18n.md b/aio/content/guide/i18n.md
index 930b10720d..d046aec5c6 100644
--- a/aio/content/guide/i18n.md
+++ b/aio/content/guide/i18n.md
@@ -1,232 +1,323 @@
# Internationalization (i18n)
-Angular's _internationalization_ (_i18n_) tools help make your app available in multiple languages.
+Application internationalization is a many-faceted area of development, focused on making
+applications available and user-friendly to a worldwide audience. This page describes Angular's
+internationalization (i18n) tools, which can help you make your app available in multiple languages.
-Try this live example
-of a JIT-compiled app, translated into Spanish.
+See the i18n Example for a simple example of
+an AOT-compiled app, translated into French.
{@a angular-i18n}
-## Angular and _i18n_ template translation
+## Angular and i18n
-Application internationalization is a challenging, many-faceted effort that
-takes dedication and enduring commitment.
-Angular's _i18n_ internationalization facilities can help.
+Angular simplifies the following aspects of internationalization:
+* Displaying dates, number, percentages, and currencies in a local format.
+* Translating text in component templates.
+* Handling plural forms of words.
+* Handling alternative text.
-This page describes the _i18n_ tools available to assist translation of component template text
-into multiple languages.
+This document focuses on [**Angular CLI**](https://cli.angular.io/) projects, in which the Angular
+CLI generates most of the boilerplate necessary to write your app in multiple languages.
-
+{@a setting-up-locale}
+## Setting up the locale of your app
-Practitioners of _internationalization_ refer to a translatable text as a "_message_".
-This page uses the words "_text_" and "_message_" interchangeably and in the combination, "_text message_".
+A locale is an identifier (id) that refers to a set of user preferences that tend to be shared
+within a region of the world, such as country. This document refers to a locale identifier as a
+"locale" or "locale id".
+
+A Unicode locale identifier is composed of a Unicode language identifier and (optionally) the
+character `-` followed by a locale extension. (For historical reasons the character `_` is supported
+as an alternative to `-`.) For example, in the locale id `fr-CA` the `fr` refers to the French
+language identifier, and the `CA` refers to the locale extension Canada.
+
+
+
+Angular follows the Unicode LDML convention that uses stable identifiers (Unicode locale identifiers)
+based on the norm [BCP47](http://www.rfc-editor.org/rfc/bcp/bcp47.txt). It is very important that
+you follow this convention when you define your locale, because the Angular i18n tools use this
+locale id to find the correct corresponding locale data.
-The _i18n_ template translation process has four phases:
+By default, Angular uses the locale `en-US`, which is English as spoken in the United States of America.
-1. Mark static text messages in your component templates for translation.
+To set your app's locale to another value, use the CLI parameter `--locale` with the value
+of the locale id that you want to use:
-1. An angular _i18n_ tool extracts the marked messages into an industry standard translation source file.
+
+ ng serve --aot --locale fr
+
-1. A translator edits that file, translating the extracted text messages into the target language,
-and returns the file to you.
+If you use JIT, you also need to define the `LOCALE_ID` provider in your main module:
-1. The Angular compiler imports the completed translation files,
-replaces the original messages with translated text, and generates a new version of the application
-in the target language.
+
+
-You need to build and deploy a separate version of the application for each supported language.
-{@a i18n-attribute}
+For more information about Unicode locale identifiers, see the
+[CLDR core spec](http://cldr.unicode.org/core-spec#Unicode_Language_and_Locale_Identifiers).
+
+For a complete list of locales supported by Angular, see
+[the Angular repository](https://github.com/angular/angular/tree/master/packages/common/locales).
+
## i18n pipes
Angular pipes can help you with internationalization: the `DatePipe`, `CurrencyPipe`, `DecimalPipe`
-and `PercentPipe` use locale data to format your data based on your `LOCALE_ID`.
+and `PercentPipe` use locale data to format data based on the `LOCALE_ID`.
-By default Angular only contains locale data for the language `en-US`, if you set the value of
-`LOCALE_ID` to another locale, you will have to import new locale data for this language:
+By default, Angular only contains locale data for `en-US`. If you set the value of
+`LOCALE_ID` to another locale, you must import locale data for that new locale.
+The CLI imports the locale data for you when you use the parameter `--locale` with `ng serve` and
+`ng build`.
-
+If you want to import locale data for other languages, you can do it manually:
+
+
+The files in `@angular/common/locales` contain most of the locale data that you
+need, but some advanced formatting options might only be available in the extra dataset that you can
+import from `@angular/common/locales/extra`. An error message informs you when this is the case.
+
+
+
+
-Note that the files in `@angular/common/locales` contain most of the locale data that you will
-need, but some advanced formatting options might only be available in the extra dataset that you can
-import from `@angular/common/locales/extra`:
+ All locale data used by Angular are extracted from the Unicode Consortium's
+ Common Locale Data Repository (CLDR).
-
-
+
+
+
+## Template translations
+
+
+
+ This document refers to a unit of translatable text as "text," a "message", or a
+ "text message."
-## Mark text with the _i18n_ attribute
+The i18n template translation process has four phases:
-The Angular `i18n` attribute is a marker for translatable content.
-Place it on every element tag whose fixed text should be translated.
+1. Mark static text messages in your component templates for translation.
+
+2. An Angular i18n tool extracts the marked text into an industry standard translation source file.
+
+3. A translator edits that file, translating the extracted text into the target language,
+and returns the file to you.
+
+4. The Angular compiler imports the completed translation files,
+replaces the original messages with translated text, and generates a new version of the app
+in the target language.
+
+You need to build and deploy a separate version of the app for each supported language.
+
+{@a i18n-attribute}
+
+### Mark text with the i18n attribute
+
+The Angular `i18n` attribute marks translatable content. Place it on every element tag whose fixed
+text is to be translated.
+
+In the example below, an `
` tag displays a simple English language greeting, "Hello i18n!"
+
+
+
+
+To mark the greeting for translation, add the `i18n` attribute to the `
` tag.
+
+
+
-`i18n` is not an Angular _directive_.
-It's a custom _attribute_, recognized by Angular tools and compilers.
-After translation, the compiler removes it.
+ `i18n` is a custom attribute, recognized by Angular tools and compilers.
+ After translation, the compiler removes it. It is not an Angular directive.
-In the accompanying sample, an `
` tag displays a simple English language greeting
-that you translate into Spanish:
-
-
-
-
-Add the `i18n` attribute to the tag to mark it for translation.
-
-
-
{@a help-translator}
-### Help the translator with a _description_ and _meaning_
+### Help the translator with a description and meaning
-In order to translate it accurately, the translator may
-need a description of the message.
-Assign a description to the i18n attribute:
+To translate a text message accurately, the translator may need additional information or context.
-
+You can add a description of the text message as the value of the `i18n` attribute, as shown in the
+example below:
+
+
-In order to deliver a correct translation, the translator may need to
-know the _meaning_ or _intent_ of the text within _this particular_ application context.
+The translator may also need to know the meaning or intent of the text message within this particular
+app context.
-You add context by beginning the string with the _meaning_ and
-separating it from the _description_ with the `|` character (`|`):
+You add context by beginning the `i18n` attribute value with the _meaning_ and
+separating it from the _description_ with the `|` character: `|`
-
+
-While all appearances of a message with the _same_ meaning have the _same_ translation,
-a message with *a variety of possible meanings* could have different translations.
-The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
-to facilitate contextually-specific translations.
+All occurrences of a text message that have the same meaning will have the same translation.
+A text message that is associated with different meanings can have different translations.
+
+The Angular extraction tool preserves both the meaning and the description in the translation
+source file to facilitate contextually-specific translations, but only the combination of meaning
+and text message are used to generate the specific id of a translation. If you have two
+similar text messages with different meanings, they are extracted separately. If you have two similar
+text messages with different descriptions (not different meanings), then they are extracted only once.
{@a custom-id}
-### Set a custom _id_ to improve search and maintenance
+### Set a custom id for persistence and maintenance
-The angular _i18n_ extractor tool generates a file with a _translation unit_ entry for each `i18n` attribute in a template. By default, it assigns each translation unit a unique _id_ such as this one:
+The angular i18n extractor tool generates a file with a translation unit entry for each `i18n`
+attribute in a template. By default, it assigns each translation unit a unique id such as this one:
-
+
-This _id_ is obscure and difficult for humans to read or remember.
+When you change the translatable text, the extractor tool generates a new id for that translation unit.
+You must then update the translation file with the new id.
-Worse, when you change the translatable text, perhaps to fix a typo,
-the extractor tool generates a new _id_ for that translation.
-You will lose the translation unless you update it with the new _id_.
-That [complicates maintenance](#maintenance).
+Alternatively, you can specify a custom id in the `i18n` attribute by using the prefix `@@`.
+The example below defines the custom id `introductionHeader`:
-Consider specifying your own, meaningful _id_ in the `i18n` attribute, **prefixed with `@@`**.
-
-
+
-Now the extractor tool and compiler will generate a translation unit with _your custom id_ and never change it.
+When you specify a custom id, the extractor tool and compiler generate a translation unit with that
+custom id.
-
+
-Here is the `i18n` attribute with a _description_, followed by the custom `id`:
+The custom id is persistent. The extractor tool does not change it when the translatable text changes.
+Therefore, you do not need to update the translation. This approach makes maintenance easier.
-
+#### Use a custom id with a description
+
+You can use a custom id in combination with a description by including both in the value of the
+`i18n` attribute. In the example below, the `i18n` attribute value includes a description, followed
+by the custom `id`:
+
+
-Here is a _meaning_ and a _description_ and the _id_ at the end:
+You also can add a meaning, as shown in this example:
-
+
-
+#### Define unique custom ids
- Be sure to define _unique_ custom ids. If you use the same id for 2 _different_ blocks of text, only the first one will be extracted,
- and its translation used in both blocks of text.
+Be sure to define custom ids that are unique. If you use the same id for two different text messages,
+only the first one is extracted, and its translation is used in place of both original text messages.
- For example:
+In the example below the custom id `myId` is used for two different messages:
```html
-
Hello
+
Hello
+
Good bye
```
- with the translation:
+Consider this translation to French:
```xml
- Hola
+ Bonjour
```
- Both `
` elements will contain the text `Hola`.
+Because the custom id is the same, both of the elements in the resulting translation contain
+the same text, `Bonjour`:
+
+ ```html
+
Bonjour
+
+
Bonjour
+ ```
+
-
{@a no-element}
### Translate text without creating an element
-If there is a stretch of text that you'd like to translate, you could wrap it in a `` tag.
-But if you don't want to create a new DOM element merely to facilitate translation,
+If there is a section of text that you would like to translate, you can wrap it in a `` tag.
+However, if you don't want to create a new DOM element merely to facilitate translation,
you can wrap the text in an `` element.
-The `` will be transformed into an html comment:
+The `` is transformed into an html comment:
{@a translate-attributes}
-## Add _i18n_ translation attributes
-You've added an image to your template. You care about accessibility too so you add a `title` attribute:
+## Add i18n translation attributes
-
+You also can translate attributes.
+For example, assume that your template has an image with a `title` attribute:
+
+
-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.
+This `title` attribute needs to be translated.
-To translate the `title` on the `img` tag from the previous example, write:
+To mark an attribute for translation, add an attribute in the form of `i18n-x`,
+where `x` is the name of the attribute to translate. The following example shows how to mark the
+`title` attribute for translation by adding the `i18n-title` attribute on the `img` tag:
-You can also assign a meaning and a description with the `i18n-x="|"` syntax.
+This technique works for any attribute of any element.
-{@a cardinality}
+You also can assign a meaning, description, and id with the `i18n-x="|@@"`
+syntax.
-## Handle singular and plural
+{@a plural-ICU}
+
+## Translate 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.
+Suppose that you want to say that something was "updated x minutes ago".
+In English, depending upon the number of minutes, you could display "just now", "one minute ago",
+or "x minutes ago" (with x being the actual number).
+Other languages might express the cardinality differently.
-Here's how you could mark up the component template to display the phrase appropriate to the number of wolves:
+The example below shows how to use a `plural` ICU expression to display one of those three options
+based on when the update occurred:
-* The first parameter is the key. It is bound to the component property (`wolves`)
-that determines the number of wolves.
+* The first parameter is the key. It is bound to the component property (`minutes`), which determines
+the number of minutes.
* The second parameter identifies this as a `plural` translation type.
-* The third parameter defines a pluralization pattern consisting of pluralization
-categories and their matching values.
+* The third parameter defines a pluralization pattern consisting of pluralization categories and their matching values.
-Pluralization categories include:
+
+
+
+Pluralization categories include (depending on the language):
* =0 (or any other number)
* zero
@@ -236,295 +327,304 @@ Pluralization categories include:
* many
* other
-Put the default _English_ translation in braces (`{}`) next to the pluralization category.
+After the pluralization category, put the default English text in braces (`{}`).
-* 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}`.
+In the example above, the three options are specified according to that pluralization pattern. For
+talking about about zero minutes, you use `=0 {just now}`. For one minute, you use `=1 {one minute}`.
+Any unmatched cardinality uses `other {{{minutes}} minutes ago}`. You could choose to add patterns
+for two, three, or any other number if the pluralization rules were different. For the example of
+"minute", only these three patterns are necessary in English.
-{@a select}
+{@a select-ICU}
-## Select among alternative texts
+## Select among alternative text messages
-The application displays different text depending upon whether the hero is male or female.
-These text alternatives require translation too.
+If your template needs to display different text messages depending on the value of a variable, you
+need to translate all of those alternative text messages.
-You can handle this with a `select` translation.
-A `select` also follows the
-ICU message syntax.
-You choose among alternative translation based on a string value instead of a number.
+You can handle this with a `select` ICU expression. It is similar to the `plural` ICU expressions
+except that you choose among alternative translations based on a string value instead of a number,
+and you define those string values.
-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:
+The following format message in the component template binds to the component's `gender` property,
+which outputs one of the following string values: "m", "f" or "o".
+The message maps those values to the appropriate translations:
-## Nesting pluralization and selection expressions
+{@a nesting-ICUS}
-You can also nest different ICU expressions together. For example:
+## Nesting plural and select ICU expressions
+
+You can also nest different ICU expressions together, as shown in this example:
{@a ng-xi18n}
-## Create a translation source file with the _ng-xi18n_ tool
+## Create a translation source file with _ng xi18n_
-Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
-into a translation source file in an industry standard format.
+Use the `ng xi18n` command provided by the CLI to extract the text messages marked with `i18n` into
+a translation source file.
-This is an Angular CLI tool in the `@angular/compiler-cli` npm package.
-If you haven't already installed the CLI and its `platform-server` peer dependency, do so now:
+Open a terminal window at the root of the app project and enter the `ng xi18n` command:
- npm install @angular/compiler-cli @angular/platform-server --save
+ ng xi18n
-Open a terminal window at the root of the application project and enter the `ng-xi18n` command:
-
-
- ./node_modules/.bin/ng-xi18n
-
+By default, the tool generates a translation file named `messages.xlf` in the
+XML Localization Interchange File Format
+(XLIFF, version 1.2).
-Windows users may have to quote the command like this: `"./node_modules/.bin/ng-xi18n"`
+If you don't use the CLI, you can use the `ng-xi18n` tool directly from the `@angular/compiler-cli`
+package, or you can manually use the CLI Webpack plugin `ExtractI18nPlugin` from the
+`@ngtools/webpack` package.
-By default, the tool generates a translation file named **`messages.xlf`** in the
-XML Localization Interchange File Format (XLIFF, version 1.2).
-
{@a other-formats}
### Other translation formats
-Angular i18n tooling supports XLIFF 1.2 and XLIFF 2 as well as the
-XML Message Bundle (XMB).
+Angular i18n tooling supports three translation formats:
+* XLIFF 1.2 (default)
+* XLIFF 2
+* XML Message
+Bundle (XMB)
-You can specify your choice of format _explicitly_ with the `--i18nFormat` flag as illustrated in these example commands
+You can specify the translation format explicitly with the `--i18nFormat` flag as illustrated in
+these example commands:
-./node_modules/.bin/ng-xi18n --i18nFormat=xlf --outFile=messages.xlf
-./node_modules/.bin/ng-xi18n --i18nFormat=xlf2 --outFile=messages.xliff2.xlf
-./node_modules/.bin/ng-xi18n --i18nFormat=xmb --outFile=messages.xmb
+ng xi18n --i18nFormat=xlf
+ng xi18n --i18nFormat=xlf2
+ng xi18n --i18nFormat=xmb
-The sample in _this_ guide sticks with the default _XLIFF 1.2_ format.
+The sample in this guide uses the default XLIFF 1.2 format.
+
+
+
+ XLIFF files have the extension .xlf. The XMB format generates .xmb source files but uses
+ .xtb (XML Translation Bundle: XTB) translation files.
+
+
{@a ng-xi18n-options}
### Other options
-You may have to specify additional options.
-For example, if the `tsconfig.json` TypeScript configuration
-file is located somewhere other than in the root folder,
-you must identify the path to it with the `-p` option:
+You can specify the output path used by the CLI to extract your translation source file with
+the parameter `--outputPath`:
- ./node_modules/.bin/ng-xi18n -p path/to/tsconfig.json
- ./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p path/to/tsconfig.json
+
+ ng xi18n --outputPath src/locale
+
-{@a npm-i18n-script}
-
-### 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:
-
-
- "scripts": {
- "i18n": "ng-xi18n",
- ...
- }
-
-
-Now you can issue command variations such as these:
+You can change the name of the translation source file that is generated by the extraction tool with
+the parameter `--outFile`:
- npm run i18n
- npm run i18n -- -p path/to/tsconfig.json
- npm run i18n -- --i18nFormat=xmb -p path/to/tsconfig.json
+
+ ng xi18n --outFile source.xlf
+
-Note the `--` flag before the options.
-It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
+You can specify the base locale of your app with the parameter `--locale`:
+
+
+
+ ng xi18n --locale fr
+
+
+
+The extraction tool uses the locale to add the app locale information into your translation source
+file. This information is not used by Angular, but external translation tools may need it.
+
{@a translate}
## Translate text messages
-The `ng-xi18n` command generates 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
-files. The guide sample creates a Spanish translation file.
+The `ng xi18n` command generates a translation source file named `messages.xlf` in the project `src`
+folder.
+
+The next step is to translate this source file into the specific language
+translation files. The example in this guide creates a French translation file.
{@a localization-folder}
### Create a localization folder
-You will probably translate into more than one other language so it's a good idea
-for the project structure to reflect your entire internationalization effort.
+Most apps are translated into more than one other language. For this reason, it is standard practice
+for the project structure to reflect the entire internationalization effort.
-One approach is to dedicate a folder to localization and store related assets,
-such as internationalization files, there.
+One approach is to dedicate a folder to localization and store related assets, such as
+internationalization files, there.
-This guide follows that suggestion. It has a `locale` folder under `src/`.
-Assets within the folder carry a filename extension that matches a language-culture code from a
-well-known codeset.
+This guide follows that approach. It has a `locale` folder under `src/`.
+Assets within that folder have a filename extension that matches their associated locale.
-Make a copy of the `messages.xlf` file, put it in the `locale` folder and
-rename it `messages.es.xlf`for the Spanish language translation.
-Do the same for each target language.
+### Create the translation files
+
+For each translation source file, there must be at least one language translation file for the
+resulting translation.
+
+For this example:
+
+1. Make a copy of the `messages.xlf` file.
+2. Put the copy in the `locale` folder.
+3. Rename the copy to `messages.fr.xlf` for the French language translation.
+
+If you were translating to other languages, you would repeat these steps for each target language.
{@a translate-text-nodes}
### Translate text nodes
-In the real world, you send the `messages.es.xlf` file to a Spanish translator who fills in the translations
-using one of the
-many XLIFF file editors.
-This sample file is easy to translate without a special editor or knowledge of Spanish.
-Open `messages.es.xlf` and find the first `` section:
+In a large translation project, you would send the `messages.fr.xlf` file to a French translator who
+would enter the translations using an XLIFF file editor.
-
+This sample file is easy to translate without a special editor or knowledge of French.
+
+1. Open `messages.fr.xlf` and find the first `` section:
+
+>
-This XML element represents the translation of the `
` greeting tag you marked with the `i18n` attribute.
+> This XML element represents the translation of the `
` greeting tag that you marked with the
+ `i18n` attribute earlier in this guide.
-
+> Note that the translation unit `id=introductionHeader` is derived from the
+ [custom `id`](#custom-id "Set a custom id") that you set earlier, but
+ without the `@@` prefix required in the source HTML.
-Note that the translation unit `id=introductionHeader` is derived from the [_custom_ `id`](#custom-id "Set a custom id") that you set earlier, but **without the `@@` prefix** required in the source HTML.
-
+2. Duplicate the `` tag, rename it `target`, and then replace its content with the French
+ greeting. If you were working with a more complex translation, you could use the the information
+ and context provided by the source, description, and meaning elements to guide your selection of
+ the appropriate French translation.
-Using the _source_, _description_, and _meaning_ elements to guide your translation,
-replace the `` tag with the Spanish greeting:
-
-
+>
-Translate the other text nodes the same way:
+3. Translate the other text nodes the same way:
-
+>
-**The tool generated the `id`s for _these_ translation units. Don't touch them.**
-Each `id` depends upon the content of the message and its assigned meaning.
-Change either factor and the `id` changes as well.
-See the **[translation file maintenance discussion](#maintenance)**.
-
-This is why you should **[specify custom ids](#custom-id "Set a custom id")** and avoid tool generated ids.
+ **The Angular i18n tools generated the ids for these translation units. Don't change them.**
+ Each `id` depends upon the content of the template text and its assigned meaning.
+ If you change either the text or the meaning, then the `id` changes.
+ For more information, see the **[translation file maintenance discussion](#custom-id)**.
{@a translate-plural-select}
-## Translate _plural_ and _select_
-Translating _plural_ and _select_ messages is a little tricky.
+## Translate plural and select expressions
-The `