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 @@ + + + + + + + + + Hello i18n! + An introduction header for this sample + User welcome + + + + + + + Hello i18n! + Bonjour i18n ! + An introduction header for this sample + User welcome + + + + + + + I don't output any element + Je n'affiche aucun élément + + + Angular logo + Logo d'Angular + + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago} } + {VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a minutes} } + + + + + + The author is + L'auteur est + + + + + {VAR_SELECT, select, m {male} f {female} o {other} } + {VAR_SELECT, select, m {un homme} f {une femme} o {autre} } + + + + + + + Updated: + Mis à jour: + + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago by {VAR_SELECT, select, m {male} f {female} o {other} }} } + {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 @@ - - - - - - - Hello i18n! - - - An introduction header for this sample - User welcome - - - I don't output any element - - - - Angular logo - - - - - - - - The hero is - - - - - - - - Here we have: - - - - - - - - - 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 @@ - - - - - - Hello i18n! - ¡Hola i18n! - An introduction header for this sample - User welcome - - - I don't output any element - No genero ningún elemento - - - Angular logo - Logo de Angular - - - - {wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}} - - - The hero is - El heroe es - - - - {gender, select, m {hombre} f {mujer}} - - - Here we have: - 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 @@ - - - - - - - - - - - Hello i18n! - ¡Hola i18n! - An introduction header for this sample - User welcome - - - - - - - I don't output any element - No genero ningún elemento - - - Angular logo - Logo de Angular - - - - - - {wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}} - - - - - - The hero is - El heroe es - - - - - - {gender, select, m {hombre} f {mujer}} - - - - - Here we have: - - - - - - - - - - Here we have: - 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 @@ + + + + + + + Hello i18n! + + + Bonjour i18n ! + + + app\app.component.ts + 4 + + An introduction header for this sample + User welcome + + + I don't output any element + Je n'affiche aucun élément + + app\app.component.ts + 10 + + + + Angular logo + Logo d'Angular + + app\app.component.ts + 16 + + + + Updated + Mis à jour + + app\app.component.ts + 21 + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago} } + {VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a minutes} } + + app\app.component.ts + 21 + + + + The author is + L'auteur est + + app\app.component.ts + 27 + + + + {VAR_SELECT, select, m {male} f {female} o {other} } + {VAR_SELECT, select, m {un homme} f {une femme} o {autre} } + + app\app.component.ts + 27 + + + + Updated: + + Mis à jour: + + + app\app.component.ts + 31 + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago by {VAR_SELECT, select, m {male} f {female} o {other} }} } + {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 @@ + + + + + + + Hello i18n! + + + app\app.component.ts + 4 + + An introduction header for this sample + User welcome + + + I don't output any element + + app\app.component.ts + 10 + + + + Angular logo + + app\app.component.ts + 16 + + + + Updated + + app\app.component.ts + 21 + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago} } + + app\app.component.ts + 21 + + + + The author is + + app\app.component.ts + 27 + + + + {VAR_SELECT, select, m {male} f {female} o {other} } + + app\app.component.ts + 27 + + + + Updated: + + + app\app.component.ts + 31 + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago by {VAR_SELECT, select, m {male} f {female} o {other} }} } + + 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 Hello - 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: + +
+ + This syntax conforms to the + ICU Message Format + as specified in the + CLDR pluralization rules. + +
+ +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.
-This syntax conforms to the -ICU Message Format -that derives from the -Common Locale Data Repository (CLDR), -which specifies the -pluralization rules. + You can use interpolations and html markup inside of your translations.
-{@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.
-Localization and internationalization are -different but closely related terms. + Localization and internationalization are + different but + closely related terms.
-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 `` tag is empty for `plural` and `select` translation -units, which makes them hard to correlate with the original template. -The `XLIFF` format doesn't yet support the ICU rules. -However, the `XMB` format does support the ICU rules. +_Plural_ and _select_ ICU expressions are extracted separately, so they require special attention +when preparing for translation. -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. +Look for these expressions 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. {@a translate-plural} ### Translate _plural_ + To translate a `plural`, translate its ICU format match values: - + +You can add or remove plural cases, with each language having its own cardinality. (See +[CLDR plural rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html).) + {@a translate-select} ### Translate _select_ -The `select` behaves a little differently. Here again is the ICU format message in the component template: + +Below is the content of our example `select` ICU expression in the component template: -The extraction tool broke that into _two_ translation units. +The extraction tool broke that into two translation units because ICU expressions are extracted +separately. -The first unit contains the text that was _outside_ the `select`. +The first unit contains the text that was outside of the `select`. In place of the `select` is a placeholder, ``, that represents the `select` message. -Translate the text and leave the placeholder where it is. +Translate the text and move around the placeholder if necessary, but don't remove it. If you remove +the placeholder, the ICU expression will not be present in your translated app. - + -The second translation unit, immediately below the first one, contains the `select` message. Translate that. +The second translation unit, immediately below the first one, contains the `select` message. +Translate that as well. - + Here they are together, after translation: - + +{@a translate-nested} + ### Translate a nested expression -A nested expression is not different from the previous ones. As in the previous example, we have _two_ translation units. +A nested expression is similar to the previous examples. As in the previous example, there are +two translation units. The first one contains the text outside of the nested expression: -The first one contains the text outside the nested expression: - - + The second unit contains the complete nested expression: - + And both together: - + +The entire template translation is complete. The next section describes how to load that translation +into the app. +{@a app-pre-translation} -The entire template translation is complete. It's -time to incorporate that translation into the application. +### The app and its translation file - - -### The app before translation - -When the previous steps finish, the sample app _and_ its translation file are as follows: +The sample app and its translation file are now as follows: @@ -533,9 +633,9 @@ When the previous steps finish, the sample app _and_ its translation file are as - + - + @@ -543,224 +643,73 @@ When the previous steps finish, the sample app _and_ its translation file are as ## Merge the completed translation file into the app -To merge the translated text into component templates, -compile the application with the completed translation file. -The process is the same whether the file is in `.xlf` format or -in another format that Angular understands, such as `.xtb`. - -You provide the Angular compiler with three new pieces of information: +To merge the translated text into component templates, compile the app with the completed +translation file. Provide the Angular compiler with three translation-specific pieces of information: * The translation file. * The translation file format. - * The _Locale ID_ - (`es` or `en-US` for instance). + * The locale (`fr` or `en-US` for instance). -_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 compilation process is the same whether the translation file is in `.xlf` format or in another +format that Angular understands, such as `.xtb`. +How you provide this information depends upon whether you compile with +the JIT compiler or the AOT compiler. + + * With [AOT](guide/i18n#aot), you pass the information as a CLI parameter. * With [JIT](guide/i18n#jit), you provide the information at bootstrap time. - * With [AOT](guide/i18n#aot), you pass the information as `ngc` options. + + +{@a aot} + +### Merge with the AOT compiler + +The AOT (_Ahead-of-Time_) compiler is part of a build process that produces a small, fast, +ready-to-run application package. + +When you internationalize with the AOT compiler, you must pre-build a separate application +package for each language and serve the appropriate package based on either server-side language +detection or url parameters. + +You also need to instruct the AOT compiler to use your translation file. To do so, you use three +options with the `ng serve` or `ng build` commands: + +* `--i18nFile`: the path to the translation file. +* `--i18nFormat`: the format of the translation file. +* `--locale`: the locale id. + +The example below shows how to serve the French language file created in previous sections of this +guide: + + + ng serve --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr + {@a jit} ### Merge with the JIT compiler -The JIT compiler compiles the application in the browser as the application loads. +The JIT compiler compiles the app in the browser as the app loads. Translation with the JIT compiler is a dynamic process of: -1. Determining the language version for the current user. -2. Importing the appropriate language translation file as a string constant. -3. Creating corresponding translation providers to guide the JIT compiler. -4. Bootstrapping the application with those providers. - -Open `index.html` and revise the launch script as follows: - - - - -In this sample, the user's language is hard-coded as a global `document.locale` variable -in the `index.html`. - -{@a text-plugin} - -### SystemJS text plugin - -
- - This plugin only applies to an application using SystemJS. If you are using the Angular CLI, please refer to their - [docs](https://github.com/angular/angular-cli/wiki/xi18n). - -
- -Notice the SystemJS mapping of `text` to a `systemjs-text-plugin.js`. -With the help of a text plugin, SystemJS can read any file as raw text and -return the contents as a string. -You'll need it to import the language translation file. - -SystemJS doesn't ship with a raw text plugin but it's easy to add. -Create the following `systemjs-text-plugin.js` in the `src/` folder: - - - - -{@a create-translation-providers} - -### Create translation providers +1. Importing the appropriate language translation file as a string constant. +2. Creating corresponding translation providers for the JIT compiler. +3. Bootstrapping the app with those providers. Three providers tell the JIT compiler how to translate the template texts for a particular language -while compiling the application: +while compiling the app: * `TRANSLATIONS` is a string containing the content of the translation file. * `TRANSLATIONS_FORMAT` is the format of the file: `xlf`, `xlf2`, or `xtb`. * `LOCALE_ID` is the locale of the target language. -The `getTranslationProviders()` function in the following `src/app/i18n-providers.ts` -creates those providers based on the user's _locale_ -and the corresponding translation file: +The Angular `bootstrapModule` method has a second `compilerOptions` parameter that can influence the +behavior of the compiler. You can use it to provide the translation providers: - + -1. It gets the locale from the global `document.locale` variable that was set in `index.html`. +Then provide the `LOCALE_ID` in the main module: -1. If there is no locale or the language is U.S. English (`en-US`), there is no need to translate. - The function returns an empty `noProviders` array as a `Promise`. - It must return a `Promise` because this function could read a translation file asynchronously from the server. - -1. It creates a transaction filename from the locale according to the name and location convention -[described earlier](guide/i18n#localization-folder). - -1. The `getTranslationsWithSystemJs()` method reads the translation and returns the contents as a string. -Notice that it appends `!text` to the filename, telling SystemJS to use the [text plugin](guide/i18n#text-plugin). - -1. The callback composes a providers array with the three translation providers. - -1. Finally, `getTranslationProviders()` returns the entire effort as a promise. - -
- - The `LOCALE_ID` has to be a valid locale id as explained in [here](http://userguide.icu-project.org/locale). - -
- -{@a bootstrap-the-app} - -### Bootstrap with translation providers - -The Angular `bootstrapModule()` method has a second _options_ parameter -that can influence the behavior of the compiler. - -You'll create an _options_ object with the translation providers from `getTranslationProviders()` -and pass it to `bootstrapModule`. -Open the `src/main.ts` and modify the bootstrap code as follows: - - + - -Notice that it waits for the `getTranslationProviders()` promise to resolve before -bootstrapping the app. - -The app is now _internationalized_ for English and Spanish and there is a clear path for adding -more languages. - -{@a aot} - -### _Internationalization_ with the AOT compiler - -The JIT compiler translates the application into the target language -while compiling dynamically in the browser. -That's flexible but may not be fast enough for your users. - -The AOT (_Ahead-of-Time_) compiler is part of a build process that -produces a small, fast, ready-to-run application package. -When you internationalize with the AOT compiler, you pre-build -a separate application package for each -language. Then in the host web page, in this case `index.html`, -you determine which language the user needs -and serve the appropriate application package. - -This guide doesn't cover how to build multiple application packages and -serve them according to the user's language preference. -It does explain the few steps necessary to tell the AOT compiler to apply a translations file. - -Internationalization with the AOT compiler requires -some setup specifically for AOT compilation. -Start with the application project as shown -[just before merging the translation file](guide/i18n#app-pre-translation) -and refer to the [AOT guide](guide/aot-compiler) to make it _AOT-ready_. - -Next, issue an `ngc` compile command for each supported language, including English. -The result is a separate version of the application for each language. - -Tell AOT how to translate by adding three options to the `ngc` command: - - * `--i18nFile`: the path to the translation file. - * `--locale`: the name of the locale. - * `--i18nFormat`: the format of the localization file. - -For this sample, the Spanish language command would be: - - - ./node_modules/.bin/ngc --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf - - -
- -Windows users may have to quote the command: - - - "./node_modules/.bin/ngc" --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf - - -
- -### Report missing translations - -If you forgot to provide a translation, the build will succeed with a warning that you might easily overlook. -You can configure the Angular compiler for different "missing translation" behaviors: - -* Error -* Warning (default) -* Ignore - -To change the behavior in JIT, you can use the following configuration: - - - { provide: CompilerConfig, useValue: new CompilerConfig({ missingTranslation: MissingTranslationStrategy.Error }) } - - -A good place to use it is the translation providers: - - - -To change the behavior in AOT, add the `--missingTranslation` flag to the compilation command: - - - ./node_modules/.bin/ngc --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf --missingTranslation=error - - -{@a maintenance} - -## 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 the `id` _can be a serious problem!_ - -If the `id` is generated by the tool, _most_ changes to _existing_ markup -cause the tool to generate a _new_ `id` for the affected translation unit. - -After an `id` changes, the translation files are no longer in sync. -Because of that, you get some warning messages during re-compilation. -The warning messages identify that some translations are missing, but they don't tell you which -old `ids` are no longer valid. - -If you use a [custom id](#custom-id "Set a custom id"), -the tooling preserves the custom `id` as you make changes to the corresponding translation unit. **Use custom _ids_ unless you have a very good reason to do otherwise.** - -Whether you use generated or custom `ids`, **always 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 `ids` and other changes across your translation files. - diff --git a/aio/tools/example-zipper/README.md b/aio/tools/example-zipper/README.md index fc8d9bf5e1..8202383ab3 100644 --- a/aio/tools/example-zipper/README.md +++ b/aio/tools/example-zipper/README.md @@ -46,6 +46,8 @@ Here you find a: * **base.json** - All the common scripts and packages * **cli.json** - Extra scripts and packages for the CLI * **webpack.json** - Extra scripts and packages for Webpack +* **universal.json** - Extra scripts and packages for universal +* **i18n.json** - Extra scripts and packages for i18n * **systemjs.json** - All the System.js related packages but it also contains the remainder scripts that are not in the other files. diff --git a/aio/tools/example-zipper/customizer/package-json/i18n.json b/aio/tools/example-zipper/customizer/package-json/i18n.json new file mode 100644 index 0000000000..567306590f --- /dev/null +++ b/aio/tools/example-zipper/customizer/package-json/i18n.json @@ -0,0 +1,21 @@ +{ + "scripts": [ + { "name": "start", "command": "ng serve --aot" }, + { "name": "start:fr", "command": "ng serve --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr" }, + { "name": "build", "command": "ng build --prod" }, + { "name": "build:fr", "command": "ng build --prod --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr" }, + { "name": "test", "command": "ng test" }, + { "name": "lint", "command": "ng lint" }, + { "name": "e2e", "command": "ng e2e --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr" }, + { "name": "extract", "command": "ng xi18n --outputPath=src/locale" } + ], + "dependencies": [ + "web-animations-js" + ], + "devDependencies": [ + "@angular/cli", + "@types/jasminewd2", + "karma-coverage-istanbul-reporter", + "ts-node" + ] +} diff --git a/aio/tools/examples/run-example-e2e.js b/aio/tools/examples/run-example-e2e.js index e9a096b18e..ab909c7600 100644 --- a/aio/tools/examples/run-example-e2e.js +++ b/aio/tools/examples/run-example-e2e.js @@ -20,7 +20,6 @@ const IGNORED_EXAMPLES = [ // temporary ignores 'quickstart', 'http', 'setup', - 'i18n', 'webpack', 'upgrade-p' ]; @@ -203,7 +202,7 @@ function runProtractorAoT(appDir, outputFile) { // CLI version function runE2eTestsCLI(appDir, outputFile) { // --preserve-symlinks is needed due the symlinked node_modules in each example - const e2eSpawn = spawnExt('ng', ['e2e', '--preserve-symlinks'], { cwd: appDir }); + const e2eSpawn = spawnExt('yarn', ['e2e', '--preserve-symlinks'], { cwd: appDir }); return e2eSpawn.promise.then( function () { fs.appendFileSync(outputFile, `Passed: ${appDir}\n\n`);