diff --git a/public/docs/_examples/i18n/e2e-spec.ts b/public/docs/_examples/i18n/e2e-spec.ts new file mode 100644 index 0000000000..6606ca8878 --- /dev/null +++ b/public/docs/_examples/i18n/e2e-spec.ts @@ -0,0 +1,13 @@ +/// +'use strict'; +describe('i18n E2E Tests', () => { + + beforeEach(function () { + browser.get(''); + }); + + it('should display i18n translated welcome: Bonjour i18n!', function () { + expect(element(by.css('h1')).getText()).toEqual('Bonjour i18n!'); + }); + +}); diff --git a/public/docs/_examples/i18n/ts/.gitignore b/public/docs/_examples/i18n/ts/.gitignore new file mode 100644 index 0000000000..8357331dc7 --- /dev/null +++ b/public/docs/_examples/i18n/ts/.gitignore @@ -0,0 +1,6 @@ +**/*.ngfactory.ts +**/*.metadata.json +**/messages.xlf +dist +!app/tsconfig.json +!rollup.js \ No newline at end of file diff --git a/public/docs/_examples/i18n/ts/app/app.component.1.html b/public/docs/_examples/i18n/ts/app/app.component.1.html new file mode 100644 index 0000000000..7813de7d31 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/app.component.1.html @@ -0,0 +1,11 @@ + +

Hello i18n!

+ + + +

Hello i18n!

+ + + +

Hello i18n!

+ diff --git a/public/docs/_examples/i18n/ts/app/app.component.html b/public/docs/_examples/i18n/ts/app/app.component.html new file mode 100644 index 0000000000..3469b42e86 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/app.component.html @@ -0,0 +1,4 @@ + + +

Hello i18n!

+ \ No newline at end of file diff --git a/public/docs/_examples/i18n/ts/app/app.component.ts b/public/docs/_examples/i18n/ts/app/app.component.ts new file mode 100644 index 0000000000..76c99e60c7 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/app.component.ts @@ -0,0 +1,10 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } + diff --git a/public/docs/_examples/i18n/ts/app/app.module.ts b/public/docs/_examples/i18n/ts/app/app.module.ts new file mode 100644 index 0000000000..a8b40a7650 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/app.module.ts @@ -0,0 +1,13 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) + +export class AppModule { } diff --git a/public/docs/_examples/i18n/ts/app/main.1.ts b/public/docs/_examples/i18n/ts/app/main.1.ts new file mode 100644 index 0000000000..e11324f519 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/main.1.ts @@ -0,0 +1,7 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +const platform = platformBrowserDynamic(); +platform.bootstrapModule(AppModule); diff --git a/public/docs/_examples/i18n/ts/app/main.ts b/public/docs/_examples/i18n/ts/app/main.ts new file mode 100644 index 0000000000..76801570b0 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/main.ts @@ -0,0 +1,19 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID } from '@angular/core'; + +import { AppModule } from './app.module'; +import { TRANSLATION } from './messages.fr'; + +// Compile using french translations +const platform = platformBrowserDynamic(); +platform.bootstrapModule( + AppModule, + { + providers: [ + {provide: TRANSLATIONS, useValue: TRANSLATION}, + {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'}, + {provide: LOCALE_ID, useValue: 'fr'} + ] + } +); diff --git a/public/docs/_examples/i18n/ts/app/messages.fr.1.ts b/public/docs/_examples/i18n/ts/app/messages.fr.1.ts new file mode 100644 index 0000000000..5f78563420 --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/messages.fr.1.ts @@ -0,0 +1,5 @@ +// #docregion +export const TRANSLATION = ` + +`; + diff --git a/public/docs/_examples/i18n/ts/app/messages.fr.ts b/public/docs/_examples/i18n/ts/app/messages.fr.ts new file mode 100644 index 0000000000..c01e1c02bd --- /dev/null +++ b/public/docs/_examples/i18n/ts/app/messages.fr.ts @@ -0,0 +1,17 @@ +// #docregion +export const TRANSLATION = ` + + + + + + Hello i18n! + Bonjour i18n! + An introduction header for this sample + User welcome + + + + +`; + diff --git a/public/docs/_examples/i18n/ts/example-config.json b/public/docs/_examples/i18n/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/i18n/ts/index.html b/public/docs/_examples/i18n/ts/index.html new file mode 100644 index 0000000000..685110c5d1 --- /dev/null +++ b/public/docs/_examples/i18n/ts/index.html @@ -0,0 +1,37 @@ + + + + + Angular i18n example + + + + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/i18n/ts/messages.fr.xlf b/public/docs/_examples/i18n/ts/messages.fr.xlf new file mode 100644 index 0000000000..f6b0094bd9 --- /dev/null +++ b/public/docs/_examples/i18n/ts/messages.fr.xlf @@ -0,0 +1,13 @@ + + + + + + Hello i18n! + + An introduction header for this sample + User welcome + + + + \ No newline at end of file diff --git a/public/docs/_examples/i18n/ts/messages.fr.xlf.ts b/public/docs/_examples/i18n/ts/messages.fr.xlf.ts new file mode 100644 index 0000000000..a8b3923dc8 --- /dev/null +++ b/public/docs/_examples/i18n/ts/messages.fr.xlf.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +// #docregion + + + + + + Hello i18n! + + An introduction header for this sample + User welcome + + + + \ No newline at end of file diff --git a/public/docs/_examples/i18n/ts/plnkr.json b/public/docs/_examples/i18n/ts/plnkr.json new file mode 100644 index 0000000000..9886709b1e --- /dev/null +++ b/public/docs/_examples/i18n/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "i18n", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*", + "!**/*.metadata.json" + ], + "tags": ["i18n"] +} \ No newline at end of file diff --git a/public/docs/_examples/package.json b/public/docs/_examples/package.json index 7baee8a5a9..693ce2a99d 100644 --- a/public/docs/_examples/package.json +++ b/public/docs/_examples/package.json @@ -20,7 +20,8 @@ "test:webpack": "karma start karma.webpack.conf.js", "build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail", "build:cli": "ng build", - "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup.js" + "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup.js", + "extract": "ng-xi18n" }, "keywords": [], "author": "", diff --git a/public/docs/dart/latest/guide/i18n.jade b/public/docs/dart/latest/guide/i18n.jade new file mode 100644 index 0000000000..6778b6af28 --- /dev/null +++ b/public/docs/dart/latest/guide/i18n.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") diff --git a/public/docs/js/latest/guide/i18n.jade b/public/docs/js/latest/guide/i18n.jade new file mode 100644 index 0000000000..6778b6af28 --- /dev/null +++ b/public/docs/js/latest/guide/i18n.jade @@ -0,0 +1 @@ +!= partial("../../../_includes/_ts-temp") diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index 2bd99c1ca6..0ccac518fe 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -105,6 +105,11 @@ "intro": "Talk to a remote server with an HTTP Client." }, + "i18n": { + "title": "Internationalization", + "intro": "Translate your application into multiple languages" + }, + "lifecycle-hooks": { "title": "Lifecycle Hooks", "intro": "Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them." diff --git a/public/docs/ts/latest/guide/i18n.jade b/public/docs/ts/latest/guide/i18n.jade new file mode 100644 index 0000000000..8d69c169fd --- /dev/null +++ b/public/docs/ts/latest/guide/i18n.jade @@ -0,0 +1,262 @@ +include ../_util-fns + +:marked + With internationalization, also known as i18n, we can display our website in multiple languages. + Angular provides tools to export translation files that translators can work on. + Those finished translations can then be merged with your application to make it + available to multiple audiences. + + + ## Table of contents + + * [Angular and i18n](#angular-i18n) + * [The i18n attribute](#the-i18n-attribute) + * [Extract messages with ng-xi18n](#ng-xi18n) + * [Translate messages](#translate) + * [Merging translations](#merging-translations) + * [JiT configuration](#jit-configuration) + * [AoT configuration](#aot-configuration) + +:marked + **Try it out**. Here's a link to a of the JiT application. + + +a(id="angular-i18n") +.l-main-section +:marked + ## Angular and i18n + + Angular allows us to replace text in our application with the translation of our choice. + + We would usually start with a working application that already has text in a single language. + We then extract from our app a translation file that translators can open on their translation software to work on the translation. + Finally we use their translation on our app to override the original text. + + We can then build the application for each language we need, and deploy each translated application separately. + + +a(id="the-i18n-attribute") +.l-main-section +:marked + ### i18n attribute + + The `i18n` attribute is a market for translatable content. + + We'll start with a simple greeting in a `

` tag: + ++makeExample('i18n/ts/app/app.component.1.html', 'greeting', 'app/app.component.html')(format=".") + +:marked + We simply add the attribute `i18n` to our tag to mark it as a translation point. + ++makeExample('i18n/ts/app/app.component.1.html', 'i18n-attribute', 'app/app.component.html')(format=".") + +:marked + To help the translators, we can add more information about the meaning and context of this string. + Simply add a description to the i18n attribute: + ++makeExample('i18n/ts/app/app.component.1.html', 'i18n-attribute-desc', 'app/app.component.html')(format=".") + +:marked + We can add some meaning as well, separate the meaning from the description with the `|` character: + ++makeExample('i18n/ts/app/app.component.html', 'i18n-attribute-meaning', 'app/app.component.html')(format=".") + +:marked + While the same text with the same meaning should have the same translation, + the same text with *different meanings* can have different translations. + Both the meaning and the description will be extracted by our messages extractor and added to the messages file. + + This will help our translators to translate our application with a better understanding of what our text means. + + +a(id="ng-xi18n") +.l-main-section +:marked + ### Extract messages with ng-xi18n + + Now that our template has been updated to support i18n translations, we can use the `ng-xi18n` messages extractor. + + This CLI tool is based on `ngc` and is available in the `@angular/compiler-cli` npm package. + You can read more about `ngc` on the [AoT cookbook](../cookbook/aot-compiler.html). + + To use it, the first thing that we have to do is to install it and it's `platform-server` peer dependency: + +code-example(language="sh" class="code-shell"). + npm install @angular/compiler-cli @angular/platform-server --save + +:marked + Like `ngc`, `ng-xi18n` is based on `tsc`, the TypeScript compiler. + We can use it to generate our transfile file at the root of our project: + +code-example(language="sh" class="code-shell"). + ./node_modules/.bin/ng-xi18n + +:marked + The translation file is generated by default in the format `XML Localisation Interchange File Format` (XLIFF, version 1.2). + + `ng-x18n` and Angular also supports the `XML Message Bundle`(XMB) format. We can switch to this + format by adding the `--i18nFormat=xmb` to our command. + +.alert.is-helpful + :marked + It is considered good practice to create a new npm command that will be used to run `ng-xi18n`. + + Edit `package.json` and add the following command in the `scripts` property: `"extract": "ng-xi18n"`. + We can now generate our translations using the command `npm run extract`. + + +a(id="translate") +.l-main-section +:marked + ### Translate le message + + Now that we have generated a `./messages.xlf` file we could edit it, or send it to + translators to edit, using one of the many editors that support `xlf`. + You can find a list of editors [here](https://en.wikipedia.org/wiki/XLIFF#Editors). + + For the sake of simplicity, we will make our French translation by editing the translation file manually in our text editor. + + Make a copy of `messages.xlf` called `messages.fr.xlf`, open it and find the following section: + + ``` + + Hello i18n! + + An introduction header for this sample + User welcome + + ``` + + This XML element represents the translation of our header tag. + You might have a different string in `id="af2ccf4b5dba59616e92cf1531505af02da8f6d2"`, this is normal. + It depends on the content of the message and it's meaning, so if you change either it will also change. + + Replace `` with `Bonjour i18n!` to add our French translation. + That's all for our french translation! + +.alert.is-helpful + :marked + Whenever we add new messages - or edit existing ones - in our application, we have to repeat this process. + + Using specialized translation software can help us find out easily what new translations have been generated by + the messages extractor. + + +a(id="merging-translations") +.l-main-section +:marked + ## Merging translations + + Now that we have a localized file, we can tell Angular to use it for all of our elements that have the `i18n` marker. + + Angular understands `xlf`, `xlif` and `xtb` formats, but we have to provide these messages into our application. + + However, our paths diverge depending on whether we use JiT or AoT: + + * if we are using Just-in-Time compilation, we must incorporate our translation file content into + our application code at bootstrap time. + * if we are using Ahead-in-Time compilation, we can include our translation file via `ngc` options. + + In both approaches the general idea is the same. we have to give angular these three things: + * Translations file + * Translation file format + * Locale ID (`en-US` for instance) + + Our untranslated app now looks like this: + ++makeTabs(` + i18n/ts/app/app.component.html, + i18n/ts/app/app.component.ts, + i18n/ts/app/app.module.ts, + i18n/ts/app/main.1.ts, + i18n/ts/messages.fr.xlf.ts +`, '', ` + app/app.component.html, + app/app.component.ts, + app/app.module.ts, + app/main.ts, + messages.fr.xlf +`) + +a(id="jit-configuration") +.l-main-section +:marked + ### Merge translations with the JiT approach + + When we use JiT, we'll provide those three things when bootstrapping out `AppModule`. + + We have to provide three values: `TRANSLATIONS`, `TRANSLATIONS_FORMAT` and `LOCALE_ID`. + * `TRANSLATIONS` is a string containing the content of our `messages` file for the chosen locale. + * `TRANSLATIONS_FORMAT` is either `xlf`, `xlif` or `xtb` depending on the format of our `messages` file. + * `LOCALE_ID` is a string representing the locale of our chosen language. + + Starting out with our default `main.ts`: + ++makeExample('i18n/ts/app/main.1.ts', null, 'app/main.ts')(format=".") + +:marked + We'll import `TRANSLATIONS`, `TRANSLATIONS_FORMAT` and `LOCALE_ID` from `@angular/core`, then + use them to provide our own values in the `providers` array: + ++makeExample('i18n/ts/app/main.ts', null, 'app/main.ts')(format=".") + +:marked + But... We don't have a `./messages.fr.ts` file yet. + + Since TypeScript is unable to import an `xlf` file, we have to create a `.ts` file that exports the content of our + `messages.fr.xlf` file. + + Create `app/messages.fr.ts`. We just want to export a string, so we'll start out with a very + simple file exporting an empty template literal: + ++makeExample('i18n/ts/app/messages.fr.1.ts', null, 'app/messages.fr.ts')(format=".") + +:marked + Now copy the contents of `message.fr.xlf` into the empty template literal: + ++makeExample('i18n/ts/app/messages.fr.ts', null, 'app/messages.fr.ts')(format=".") + +.alert.is-helpful + :marked + If you use Webpack you can use the [raw loader](https://github.com/webpack/raw-loader) to import your translations + file directly like this: `const TRANSLATION = require("raw!./i18n/messages.fr.xlf");` + +:marked + That's it, our application is now internationalized! Angular will replace the content of our elements using + the `i18n` attribute with the french translations that we provided at bootstrap. + + Your JiT app should now look just like the app at the end of [Merging translations](#merging-translations) + except for these two files: + ++makeTabs(` + i18n/ts/app/main.ts, + i18n/ts/app/messages.fr.ts + `, '', ` + app/main.ts, + app/messages.fr.ts + `) + +a(id="aot-configuration") +.l-main-section +:marked + ### Merge translations with the AoT approach + + Using the AoT approach requires a little bit of setup to make the `ngc` compiler work. Start with + the files shown at the end of [Merging translations](#merging-translations) and refer to the + [AoT cookbook](../cookbook/aot-compiler.html) to make it AoT ready. + + Once our project is AoT-ready, we will use `ngc` to compile a version of our application per locale. To do that + we will add three arguments to the cli command: + * `--i18nFile`: the path to our localization file + * `--locale`: the name of the locale + * `--i18nFormat`: the format of our localization file + +code-example(language="sh" class="code-shell"). + ./node_modules/.bin/ngc --i18nFile=./messages.fr.xlf --locale=fr --i18nFormat=xlf + +:marked + That's all that we have to do! The `ngc` compiler will replace the content of our elements that have + the i18n attribute with our translations in the AoT generated templates. + +