docs(i18n): add internationalization (i18n) guide (#2491)
* docs(i18n): add internationalization (i18n) guide * docs(cb-i18n): revamped to System.import the translation file
This commit is contained in:
parent
64d5b3dc23
commit
e6199a8dfb
|
@ -297,7 +297,7 @@ function runE2eTsTests(appDir, outputFile) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
exampleConfig = {};
|
exampleConfig = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
build: exampleConfig.build || 'tsc',
|
build: exampleConfig.build || 'tsc',
|
||||||
run: exampleConfig.run || 'http-server:e2e'
|
run: exampleConfig.run || 'http-server:e2e'
|
||||||
|
@ -1263,7 +1263,7 @@ function apiExamplesWatch(postShredAction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function devGuideExamplesWatch(shredOptions, postShredAction, focus) {
|
function devGuideExamplesWatch(shredOptions, postShredAction, focus) {
|
||||||
var watchPattern = focus ? '**/' + focus + '/**/*.*' : '**/*.*';
|
var watchPattern = focus ? '**/{' + focus + ',cb-' + focus+ '}/**/*.*' : '**/*.*';
|
||||||
var includePattern = path.join(shredOptions.examplesDir, watchPattern);
|
var includePattern = path.join(shredOptions.examplesDir, watchPattern);
|
||||||
// removed this version because gulp.watch has the same glob issue that dgeni has.
|
// removed this version because gulp.watch has the same glob issue that dgeni has.
|
||||||
// var excludePattern = '!' + path.join(shredOptions.examplesDir, '**/node_modules/**/*.*');
|
// var excludePattern = '!' + path.join(shredOptions.examplesDir, '**/node_modules/**/*.*');
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/// <reference path='../_protractor/e2e.d.ts' />
|
||||||
|
'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!');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
**/*.ngfactory.ts
|
||||||
|
**/*.metadata.json
|
||||||
|
**/messages.xlf
|
||||||
|
dist
|
||||||
|
!app/tsconfig.json
|
||||||
|
!rollup.js
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!--#docregion greeting-->
|
||||||
|
<h1>Hello i18n!</h1>
|
||||||
|
<!--#enddocregion greeting-->
|
||||||
|
|
||||||
|
<!--#docregion i18n-attribute-->
|
||||||
|
<h1 i18n>Hello i18n!</h1>
|
||||||
|
<!--#enddocregion i18n-attribute-->
|
||||||
|
|
||||||
|
<!--#docregion i18n-attribute-desc-->
|
||||||
|
<h1 i18n="An introduction header for this sample">Hello i18n!</h1>
|
||||||
|
<!--#enddocregion i18n-attribute-desc-->
|
|
@ -0,0 +1,4 @@
|
||||||
|
<!--#docregion-->
|
||||||
|
<!--#docregion i18n-attribute-meaning-->
|
||||||
|
<h1 i18n="User welcome|An introduction header for this sample">Hello i18n!</h1>
|
||||||
|
<!--#enddocregion i18n-attribute-meaning-->
|
|
@ -0,0 +1,10 @@
|
||||||
|
// #docregion
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: 'my-app',
|
||||||
|
templateUrl: 'app.component.html'
|
||||||
|
})
|
||||||
|
export class AppComponent { }
|
||||||
|
|
|
@ -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 { }
|
|
@ -0,0 +1,33 @@
|
||||||
|
// #docregion
|
||||||
|
import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID } from '@angular/core';
|
||||||
|
|
||||||
|
export function getTranslationProviders(): Promise<Object[]> {
|
||||||
|
|
||||||
|
// 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: Object[] = [];
|
||||||
|
|
||||||
|
// No locale or English: no translation providers
|
||||||
|
if (!locale || locale === 'en') {
|
||||||
|
return Promise.resolve(noProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex: 'i18n/fr/messages.fr.xlf`
|
||||||
|
const translationFile = `./i18n/${locale}/messages.${locale}.xlf`;
|
||||||
|
|
||||||
|
return getTranslationsWithSystemJs(translationFile)
|
||||||
|
.then( (translations: string ) => [
|
||||||
|
{ provide: TRANSLATIONS, useValue: translations },
|
||||||
|
{ provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
|
||||||
|
{ provide: LOCALE_ID, useValue: locale }
|
||||||
|
])
|
||||||
|
.catch(() => noProviders); // ignore if file not found
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var System: any;
|
||||||
|
|
||||||
|
function getTranslationsWithSystemJs(file: string) {
|
||||||
|
return System.import(file + '!text'); // relies on text plugin
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
// #docregion
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -0,0 +1,10 @@
|
||||||
|
// #docregion
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
import { getTranslationProviders } from './i18n-providers';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
getTranslationProviders().then(providers => {
|
||||||
|
const options = { providers };
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule, options);
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||||
|
<body>
|
||||||
|
<trans-unit id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
|
||||||
|
<source>Hello i18n!</source>
|
||||||
|
<target>Bonjour i18n!</target>
|
||||||
|
<note priority="1" from="description">An introduction header for this sample</note>
|
||||||
|
<note priority="1" from="meaning">User welcome</note>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!-- The `messages.fr.xlf` after translation for documentation purposes -->
|
||||||
|
<!-- #docregion -->
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||||
|
<body>
|
||||||
|
<!-- #docregion trans-unit -->
|
||||||
|
<trans-unit id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
|
||||||
|
<source>Hello i18n!</source>
|
||||||
|
<target>Bonjour i18n!</target>
|
||||||
|
<note priority="1" from="description">An introduction header for this sample</note>
|
||||||
|
<note priority="1" from="meaning">User welcome</note>
|
||||||
|
</trans-unit>
|
||||||
|
<!-- #enddocregion trans-unit -->
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!-- #docregion -->
|
||||||
|
<trans-unit id="af2ccf4b5dba59616e92cf1531505af02da8f6d2" datatype="html">
|
||||||
|
<source>Hello i18n!</source>
|
||||||
|
<target/>
|
||||||
|
<note priority="1" from="description">An introduction header for this sample</note>
|
||||||
|
<note priority="1" from="meaning">User welcome</note>
|
||||||
|
</trans-unit>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- #docregion -->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Angular i18n example</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
|
||||||
|
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||||
|
|
||||||
|
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||||
|
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
|
||||||
|
<script src="systemjs.config.js"></script>
|
||||||
|
|
||||||
|
<!-- #docregion i18n -->
|
||||||
|
<script>
|
||||||
|
// Get the locale id somehow
|
||||||
|
document.locale = 'fr';
|
||||||
|
|
||||||
|
// Map to the text plugin
|
||||||
|
System.config({
|
||||||
|
map: {
|
||||||
|
text: 'systemjs-text-plugin.js'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Launch the app
|
||||||
|
System.import('app').catch(function(err){ console.error(err); });
|
||||||
|
</script>
|
||||||
|
<!-- #enddocregion i18n -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<my-app>Loading...</my-app>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"description": "i18n",
|
||||||
|
"files": [
|
||||||
|
"app/**/*.css",
|
||||||
|
"app/**/*.html",
|
||||||
|
"app/**/*.ts",
|
||||||
|
"i18n/messages.xlf",
|
||||||
|
"i18n/fr/messages.fr.xlf",
|
||||||
|
|
||||||
|
"!**/*.[1].*",
|
||||||
|
|
||||||
|
"styles.css",
|
||||||
|
"systemjs-text-plugin.js",
|
||||||
|
"index.html"
|
||||||
|
],
|
||||||
|
"tags": ["i18n"]
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// #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});';
|
||||||
|
}
|
|
@ -20,7 +20,8 @@
|
||||||
"test:webpack": "karma start karma.webpack.conf.js",
|
"test:webpack": "karma start karma.webpack.conf.js",
|
||||||
"build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail",
|
"build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail",
|
||||||
"build:cli": "ng build",
|
"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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
@ -47,6 +47,12 @@
|
||||||
"hide": true
|
"hide": true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"i18n": {
|
||||||
|
"title": "Internationalization (i18n)",
|
||||||
|
"intro": "Translate the app's template text into multiple languages",
|
||||||
|
"hide": true
|
||||||
|
},
|
||||||
|
|
||||||
"rc4-to-rc5": {
|
"rc4-to-rc5": {
|
||||||
"title": "RC4 to RC5 Migration",
|
"title": "RC4 to RC5 Migration",
|
||||||
"intro": "Migrate your RC4 app to RC5 in minutes.",
|
"intro": "Migrate your RC4 app to RC5 in minutes.",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
include ../../../_includes/_ts-temp
|
|
@ -0,0 +1 @@
|
||||||
|
!= partial("../../../_includes/_ts-temp")
|
|
@ -42,6 +42,12 @@
|
||||||
"intro": "Validate user's form entries"
|
"intro": "Validate user's form entries"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"i18n": {
|
||||||
|
"title": "Internationalization (i18n)",
|
||||||
|
"intro": "Translate the app's template text into multiple languages",
|
||||||
|
"hide": true
|
||||||
|
},
|
||||||
|
|
||||||
"rc4-to-rc5": {
|
"rc4-to-rc5": {
|
||||||
"title": "RC4 to RC5 Migration",
|
"title": "RC4 to RC5 Migration",
|
||||||
"intro": "Migrate your RC4 app to RC5 in minutes.",
|
"intro": "Migrate your RC4 app to RC5 in minutes.",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
include ../../../_includes/_ts-temp
|
|
@ -0,0 +1 @@
|
||||||
|
!= partial("../../../_includes/_ts-temp")
|
|
@ -46,6 +46,11 @@
|
||||||
"intro": "Validate user's form entries"
|
"intro": "Validate user's form entries"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"i18n": {
|
||||||
|
"title": "Internationalization (i18n)",
|
||||||
|
"intro": "Translate the app's template text into multiple languages"
|
||||||
|
},
|
||||||
|
|
||||||
"rc4-to-rc5": {
|
"rc4-to-rc5": {
|
||||||
"title": "RC4 to RC5 Migration",
|
"title": "RC4 to RC5 Migration",
|
||||||
"intro": "Migrate your RC4 app to RC5 in minutes."
|
"intro": "Migrate your RC4 app to RC5 in minutes."
|
||||||
|
|
|
@ -0,0 +1,350 @@
|
||||||
|
include ../_util-fns
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Angular's _internationalization_ ("_i18n_") tools help make your app availble in multiple languages.
|
||||||
|
|
||||||
|
<a id="top"></a>
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Angular and i18n template translation](#angular-i18n)
|
||||||
|
* [Mark text with the _i18n_ attribute](#i18n-attribute)
|
||||||
|
* [Extract text with ng-xi18n](#ng-xi18n)
|
||||||
|
* [Translate](#translate)
|
||||||
|
* [Merge the translation file into the app](#merge)
|
||||||
|
* [JiT configuration](#jit)
|
||||||
|
* [AoT configuration](#aot)
|
||||||
|
|
||||||
|
:marked
|
||||||
|
**Try this** <live-example>live example</live-example> of a JiT-compiled app, translated into French.
|
||||||
|
|
||||||
|
|
||||||
|
a#angular-i18n
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Angular and _i18n_ template translation
|
||||||
|
|
||||||
|
Application internationalization is a challenging, many-faceted effort that
|
||||||
|
takes dedication and enduring commitment.
|
||||||
|
Angular's _i18n_ internationalization facilities can help.
|
||||||
|
|
||||||
|
This page describes the _i18n_ tools to assist translation of component template text
|
||||||
|
into multiple languages.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Practitioners of _internationalization_ refer to a translatable text as a "_message_".
|
||||||
|
This page uses the words "_text_" and "_message_" interchangably and in the combination, "_text message_".
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The _i18n_ template translation process has four phases:
|
||||||
|
|
||||||
|
1. Mark static text messages in your component templates for translation.
|
||||||
|
|
||||||
|
1. An angular _i18n_ tool extracts the marked messages into an industry standard translation source file.
|
||||||
|
|
||||||
|
1. A translator edits that file, translating the extracted text messages into the target language,
|
||||||
|
and returns the file to you.
|
||||||
|
|
||||||
|
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 build and deploy a separate version of the application for each supported language.
|
||||||
|
|
||||||
|
a#i18n-attribute
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Mark text with the _i18n_ attribute
|
||||||
|
|
||||||
|
The Angular `i18n` attribute is a marker for translatable content.
|
||||||
|
Place it on every element tag whose fixed text should be translated.
|
||||||
|
|
||||||
|
.alert.is-helpful
|
||||||
|
:marked
|
||||||
|
`i18n` is not an Angular _directive_. It's a custom _attribute_, recognized by Angular tools and compilers.
|
||||||
|
|
||||||
|
:marked
|
||||||
|
In the accompanying sample, an `<h1>` tag displays a simple English language greeting which you will translate to French:
|
||||||
|
+makeExample('cb-i18n/ts/app/app.component.1.html', 'greeting', 'app/app.component.html')(format=".")
|
||||||
|
:marked
|
||||||
|
Add the `i18n` attribute to the tag to mark it for translation.
|
||||||
|
|
||||||
|
+makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-attribute', 'app/app.component.html')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The translator may need a description of the message to translate it accurately.
|
||||||
|
Assign a description to the i18n attribute:
|
||||||
|
|
||||||
|
+makeExample('cb-i18n/ts/app/app.component.1.html', 'i18n-attribute-desc', 'app/app.component.html')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The true _meaning_ of the text may require some application context.
|
||||||
|
Add a contextual meaning to the assigned string, separating it from the description with the `|` character:
|
||||||
|
|
||||||
|
+makeExample('cb-i18n/ts/app/app.component.html', 'i18n-attribute-meaning', 'app/app.component.html')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
While all appearance of a message with the _same_ meaning should have the _same_ translation,
|
||||||
|
a message with *different meanings* could have different translations.
|
||||||
|
The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
|
||||||
|
to facilitiate contextually-specific transations.
|
||||||
|
|
||||||
|
a#ng-xi18n
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Extract translatable text with the _ng-xi18n_ command
|
||||||
|
|
||||||
|
Use the `ng-xi18n` extraction tool to generate a translation source file in an industry standard format.
|
||||||
|
|
||||||
|
This is an Angular CLI tool based on the `ngc` compiler in the `@angular/compiler-cli` npm package.
|
||||||
|
If you haven't already installed the CLI and its `platform-server` peer dependency, do so now:
|
||||||
|
|
||||||
|
code-example(language="sh" class="code-shell").
|
||||||
|
npm install @angular/compiler-cli @angular/platform-server --save
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Open a terminal window at the root of the application project and enter the `ng-xi18n` command:
|
||||||
|
|
||||||
|
code-example(language="sh" class="code-shell").
|
||||||
|
./node_modules/.bin/ng-xi18n
|
||||||
|
|
||||||
|
:marked
|
||||||
|
By default the tool generates a translation file named **`messages.xlf`** in the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">XML Localisation Interchange File Format (XLIFF, version 1.2)</a>.
|
||||||
|
|
||||||
|
code-example(language="sh" class="code-shell").
|
||||||
|
./node_modules/.bin/ng-xi18n --i18nFormat=xmb
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Windows users may have to quote the command:
|
||||||
|
code-example(language="sh").
|
||||||
|
"./node_modules/.bin/ng-xi18n"
|
||||||
|
:marked
|
||||||
|
Consider adding a convenience shortcut to the `scripts` section of the `package.json`
|
||||||
|
to make the command easier to remember and run:
|
||||||
|
code-example(format='.').
|
||||||
|
"scripts": {
|
||||||
|
"i18n": "ng-xi18n",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
:marked
|
||||||
|
Now you can enter:
|
||||||
|
code-example(language="sh" class="code-shell").
|
||||||
|
npm run i18n
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### Other translation formats
|
||||||
|
|
||||||
|
You can generate a file named **`messages.xmb`** in the
|
||||||
|
<a href="http://cldr.unicode.org/development/development-process/design-proposals/xmb" target="_blank">XML Message Bundle (XMB)</a> format
|
||||||
|
by adding the `--i18nFormat=xmb` switch.
|
||||||
|
|
||||||
|
This sample sticks with the _XLIFF_ format.
|
||||||
|
|
||||||
|
a#translate
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Translate _le message textuel_
|
||||||
|
|
||||||
|
The `ng-xi18n` command generated a translation source file in the project root folder named `messages.xlf`.
|
||||||
|
The next step is to translate the English language template text into the specific language translation
|
||||||
|
files. The cookbook sample creates a French translation file.
|
||||||
|
|
||||||
|
a#i18n-file-structure
|
||||||
|
:marked
|
||||||
|
### Create an i18n project structure
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
One approach is to create an `i18n` folder with subfolders for each language or locale, e.g. `i18n/fr` for French.
|
||||||
|
This sample puts `i18n/fr` under the project root.
|
||||||
|
|
||||||
|
Move the `messages.xlf` into the `i18n` folder where it will become the source for all language-specific translations.
|
||||||
|
Then copy `messages.xlf` into `i18n/fr` and rename it as `messages.fr.xlf` .
|
||||||
|
|
||||||
|
Follow the same convention for each target language.
|
||||||
|
|
||||||
|
### Translate
|
||||||
|
In the real world, you send the `messages.fr.xlf` file to a French translator who would fill in the translations
|
||||||
|
using one of the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/XLIFF#Editors" target="_blank">many XLIFF file editors</a>.
|
||||||
|
|
||||||
|
This sample file is easy to translate without a special editor or knowledge of French.
|
||||||
|
Open `messages.fr.xlf` and find the `<trans-unit>` section:
|
||||||
|
|
||||||
|
+makeExample('cb-i18n/ts/i18n/trans-unit.html', '', 'i18n/messages.fr.xlf (<trans-unit>)')(format=".")
|
||||||
|
:marked
|
||||||
|
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
|
||||||
|
|
||||||
|
Using the _source_, _description_, and _meaning_ elements to guide your translation,
|
||||||
|
replace the `<target/>` tag with the French greeting:
|
||||||
|
+makeExample('cb-i18n/ts/i18n/fr/messages.fr.xlf.html', 'trans-unit', 'i18n/fr/messages.fr.xlf (<trans-unit>, after translation)')(format=".")
|
||||||
|
:marked
|
||||||
|
Note that the `id` is generated by the tool. Don't touch it.
|
||||||
|
Its value depends on the content of the message, its meaning, and its description.
|
||||||
|
Change any of these factors and the `id` changes as well.
|
||||||
|
.alert.is-helpful
|
||||||
|
:marked
|
||||||
|
Repeat the extraction process whenever you add new app messages or edit existing ones.
|
||||||
|
Be careful not to lose the previous translations.
|
||||||
|
Specialized software can help manage the change process.
|
||||||
|
|
||||||
|
#app-pre-translation
|
||||||
|
:marked
|
||||||
|
### The app before translation
|
||||||
|
|
||||||
|
After the previous steps, the sample app _and_ its translation file are as follows:
|
||||||
|
|
||||||
|
+makeTabs(`
|
||||||
|
cb-i18n/ts/app/app.component.html,
|
||||||
|
cb-i18n/ts/app/app.component.ts,
|
||||||
|
cb-i18n/ts/app/app.module.ts,
|
||||||
|
cb-i18n/ts/app/main.1.ts,
|
||||||
|
cb-i18n/ts/i18n/fr/messages.fr.xlf.html
|
||||||
|
`, '', `
|
||||||
|
app/app.component.html,
|
||||||
|
app/app.component.ts,
|
||||||
|
app/app.module.ts,
|
||||||
|
app/main.ts,
|
||||||
|
i18n/fr/messages.fr.xlf
|
||||||
|
`)
|
||||||
|
|
||||||
|
a#merge
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Merge the translation file
|
||||||
|
|
||||||
|
To merge the translated text into component templates,
|
||||||
|
you compile the application with the completed translation file.
|
||||||
|
The process is the same whether the file is in `.xlf` format or
|
||||||
|
in one of the other formats (`.xlif` and `.xtb`) that Angular understands.
|
||||||
|
|
||||||
|
You provide the Angular compiler with three new pieces of information:
|
||||||
|
* the translation file
|
||||||
|
* the translation file format
|
||||||
|
* the _Locale ID_ (`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.
|
||||||
|
|
||||||
|
* with [JiT](#jit), you provide the information at bootstrap time.
|
||||||
|
* with [AoT](#aot), you pass the information as `ngc` options.
|
||||||
|
|
||||||
|
a#jit
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
### Merge with the JiT compiler
|
||||||
|
|
||||||
|
The JiT (_Just-in-Time_) compiler compiles the application in the browser as the application 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 shown here:
|
||||||
|
+makeExample('cb-i18n/ts/index.html', 'i18n', 'index.html (launch script)')(format='.')
|
||||||
|
:marked
|
||||||
|
In this sample, the user's language is hardcoded as a global `document.locale` variable
|
||||||
|
in the `index.html`.
|
||||||
|
|
||||||
|
a#text-plugin
|
||||||
|
:marked
|
||||||
|
### SystemJS Text plugin
|
||||||
|
|
||||||
|
Notice the SystemJS mapping of `text` to a `systemjs-text-plugin.js`.
|
||||||
|
With the help of a text pluglin, 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 root folder:
|
||||||
|
+makeExample('cb-i18n/ts/systemjs-text-plugin.js', null, 'systemjs-text-plugin.js')(format='.')
|
||||||
|
:marked
|
||||||
|
### Create translation providers
|
||||||
|
|
||||||
|
Three providers tell the JiT compiler how to translate the template texts for a particular language
|
||||||
|
while compiling the application:
|
||||||
|
|
||||||
|
* `TRANSLATIONS` is a string containing the content of the translation file.
|
||||||
|
* `TRANSLATIONS_FORMAT` is the format of the file: `xlf`, `xlif` or `xtb`
|
||||||
|
* `LOCALE_ID` is the locale of the target language.
|
||||||
|
|
||||||
|
The `getTranslationProviders` function in the following `app/i18n-providers.ts`
|
||||||
|
creates those providers based on the user's _locale_
|
||||||
|
and the corresponding translation file:
|
||||||
|
+makeExample('cb-i18n/ts/app/i18n-providers.ts', null, 'app/i18n-providers.ts')
|
||||||
|
:marked
|
||||||
|
* It gets the locale from the global `document.locale` variable that was set in `index.html`.
|
||||||
|
|
||||||
|
* If there is no locale or the language is English, 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.
|
||||||
|
|
||||||
|
* It creates a transaction filename from the locale according to the name and location convention
|
||||||
|
[described earlier](#i18n-file-structure).
|
||||||
|
|
||||||
|
* 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](#text-plugin).
|
||||||
|
|
||||||
|
* The callback composes a providers array with the three translation providers.
|
||||||
|
|
||||||
|
* Finally, `getTranslationProviders` returns the entire effort as a promise.
|
||||||
|
|
||||||
|
### Bootstrap the app 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 `app/main.ts` and modify the bootstrap code as follows:
|
||||||
|
+makeExample('cb-i18n/ts/app/main.ts', null, 'app/main.ts')(format=".")
|
||||||
|
:marked
|
||||||
|
Notice that it waits for the `getTranslationProviders` promise to resolve before
|
||||||
|
bootstrapping the app.
|
||||||
|
|
||||||
|
The app is now _internationalized_ for English and French and there is a clear path for adding
|
||||||
|
more languages.
|
||||||
|
|
||||||
|
a#aot
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
### _Internationalize_ 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 (`index.html`), you determine which language the user needs
|
||||||
|
and serve the appropriate application package.
|
||||||
|
|
||||||
|
This cookbook 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 to apply a translations file.
|
||||||
|
|
||||||
|
Internationalization with the AoT compiler requires some setup specifically for AoT.
|
||||||
|
Start with application project as shown [just before merging the translation file](#app-pre-translationStart)
|
||||||
|
and refer to the [AoT cookbook](aot-compiler.html) 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 French language command would be
|
||||||
|
code-example(language="sh" class="code-shell").
|
||||||
|
./node_modules/.bin/ngc --i18nFile=./i18n/fr/messages.fr.xlf --locale=fr --i18nFormat=xlf
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Windows users may have to quote the command:
|
||||||
|
code-example(language="sh" class="code-shell").
|
||||||
|
"./node_modules/.bin/ngc" --i18nFile=./i18n/fr/messages.fr.xlf --locale=fr --i18nFormat=xlf
|
|
@ -147,11 +147,11 @@
|
||||||
|
|
||||||
"upgrade": {
|
"upgrade": {
|
||||||
"title": "Upgrading from 1.x",
|
"title": "Upgrading from 1.x",
|
||||||
"intro": "Angular 1 applications can be incrementally upgraded to Angular 2."
|
"intro": "Incrementally upgrade an Angular 1 application to Angular 2."
|
||||||
},
|
},
|
||||||
|
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"title": "Webpack: an introduction",
|
"title": "Webpack: an introduction",
|
||||||
"intro": "Create your Angular applications with a Webpack based tooling"
|
"intro": "Create Angular applications with a Webpack based tooling"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue