W3cubDocs

/Angular

Internationalization (i18n)

Angular's internationalization (i18n) tools help make your app available in multiple languages.

Try this live example of a JIT-compiled app, translated into Spanish.

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 available to assist translation of component template text into multiple languages.

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".

The i18n template translation process has four phases:

  1. Mark static text messages in your component templates for translation.

  2. An angular i18n tool extracts the marked messages into an industry standard translation source file.

  3. A translator edits that file, translating the extracted text messages 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 application in the target language.

You need to build and deploy a separate version of the application for each supported language.

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.

i18n is not an Angular directive. It's a custom attribute, recognized by Angular tools and compilers. After translation, the compiler removes it.

In the accompanying sample, an <h1> tag displays a simple English language greeting that you translate into Spanish:

src/app/app.component.html
<h1>Hello i18n!</h1>

Add the i18n attribute to the tag to mark it for translation.

src/app/app.component.html
<h1 i18n>Hello i18n!</h1>

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:

src/app/app.component.html
<h1 i18n="An introduction header for this sample">Hello i18n!</h1>

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.

You add context by beginning the string with the meaning and separating it from the description with the | character (<meaning>|<description>):

src/app/app.component.html
<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>

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.

Set a custom id to improve search 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:

<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">

This id is obscure and difficult for humans to read or remember.

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.

Consider specifying your own, meaningful id in the i18n attribute, prefixed with @@.

app/app.component.html
<h1 i18n="@@introductionHeader">Hello i18n!</h1>

Now the extractor tool and compiler will generate a translation unit with your custom id and never change it.

<trans-unit id="introductionHeader" datatype="html">

Here is the i18n attribute with a definition, followed by the custom id:

app/app.component.html
<h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1>

Here is a meaning and a description and the id at the end:

app/app.component.html
<h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1>

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.

For example:

<p i18n="@@myId">Hello</p>
  <p i18n="@@myId">Good bye</p>

with the translation:

<trans-unit id="myId" datatype="html">
    <source>Hello</source>
    <target state="new">Hola</target>
  </trans-unit>

Both <p> elements will contain the text Hola.

Translate text without creating an element

Suppose there is a stretch of text that you'd like to translate. You could wrap it in a <span> tag but for some reason (CSS comes to mind) you don't want to create a new DOM element merely to facilitate translation.

Here are two techniques to try.

(1) Wrap the text in an <ng-container> element. The <ng-container> is never rendered:

src/app/app.component.html
<ng-container i18n>I don't output any element</ng-container>

(2) Wrap the text in a pair of HTML comments:

src/app/app.component.html
<!--i18n: optional meaning|optional description -->
I don't output any element either
<!--/i18n-->

Add i18n translation attributes

You've added an image to your template. You care about accessibility too so you add a title attribute:

src/app/app.component.html
<img [src]="logo" title="Angular logo">

The title attribute needs to be translated. Angular i18n support has more translation attributes in the form,i18n-x, where x is the name of the attribute to translate.

To translate the title on the img tag from the previous example, write:

src/app/app.component.html
<img [src]="logo" i18n-title title="Angular logo" />

You can also assign a meaning and a description with the i18n-x="<meaning>|<description>" syntax.

Handle singular and plural

Different languages have different pluralization rules.

Suppose your application says something about a collection of wolves. In English, depending upon the number of wolves, you could display "no wolves", "one wolf", "two wolves", or "a wolf pack". Other languages might express the cardinality differently.

Here's how you could mark up the component template to display the phrase appropriate to the number of wolves:

src/app/app.component.html
<span i18n>{wolves, plural, =0 {no wolves} =1 {one wolf} =2 {two wolves} other {a wolf pack}}</span>
  • The first parameter is the key. It is bound to the component property (wolves) that determines the number of wolves.
  • 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.

Pluralization categories include:

  • =0 (or any other number)
  • zero
  • one
  • two
  • few
  • many
  • other

Put the default English translation in braces ({}) next to the pluralization category.

  • When you're talking about one wolf, you could write =1 {one wolf}.

  • For zero wolves, you could write =0 {no wolves}.

  • For two wolves, you could write =2 {two wolves}.

You could keep this up for three, four, and every other number of wolves. Or you could specify the other category as a catch-all for any unmatched cardinality and write something like: other {a wolf pack}.

This syntax conforms to the ICU Message Format that derives from the Common Locale Data Repository (CLDR), which specifies the pluralization rules.

Select among alternative texts

The application displays different text depending upon whether the hero is male or female. These text alternatives require translation too.

You can handle this with a select translation. A select also follows the ICU message syntax. You choose among alternative translation based on a string value instead of a number.

The following format message in the component template binds to the component's gender property, which outputs either an "m" or an "f". The message maps those values to the appropriate translation:

src/app/app.component.html
<span i18n>The hero is {gender, select, m {male} f {female}}</span>

Nesting pluralization and selection expressions

You can also nest different ICU expressions together. For example:

src/app/app.component.html
<span i18n>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}}}
}</span>

Create a translation source file with the ng-xi18n tool

Use the ng-xi18n extraction tool to extract the i18n-marked texts into a translation source file in an industry standard format.

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:

npm install @angular/compiler-cli @angular/platform-server --save

Open a terminal window at the root of the application project and enter the ng-xi18n command:

./node_modules/.bin/ng-xi18n

Windows users may have to quote the command like this: "./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).

Other translation formats

Angular i18n tooling supports XLIFF 1.2 and XLIFF 2 as well as the XML Message Bundle (XMB).

You can specify your choice of 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

The sample in this guide sticks with the default XLIFF 1.2 format.

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:

./node_modules/.bin/ng-xi18n -p path/to/tsconfig.json
  ./node_modules/.bin/ng-xi18n  --i18nFormat=xmb -p path/to/tsconfig.json

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:

npm run i18n
  npm run i18n -- -p path/to/tsconfig.json
  npm run i18n -- --i18nFormat=xmb -p path/to/tsconfig.json

Note the -- flag before the options. It tells npm to pass every flag thereafter to ng-xi18n.

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.

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.

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.

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.

Make a copy of the messages.xlf file, put it in the locale folder and rename it messages.es.xlffor the Spanish language translation. Do the same for each target language.

Translate text nodes

In the real world, you send the messages.es.xlf file to a Spanish translator who fills in the translations 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 <trans-unit> section:

src/locale/messages.es.xlf ()
<trans-unit id="introductionHeader" datatype="html">
  <source>Hello i18n!</source>
  <target>¡Hola i18n!</target>
  <note priority="1" from="description">An introduction header for this sample</note>
  <note priority="1" from="meaning">User welcome</note>
</trans-unit>

This XML element represents the translation of the <h1> greeting tag you marked with the i18n attribute.

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.

Using the source, description, and meaning elements to guide your translation, replace the <target/> tag with the Spanish greeting:

src/locale/messages.es.xlf (, after translation)
<trans-unit id="introductionHeader" datatype="html">
  <source>Hello i18n!</source>
  <target>¡Hola i18n!</target>
  <note priority="1" from="description">An introduction header for this sample</note>
  <note priority="1" from="meaning">User welcome</note>
</trans-unit>

Translate the other text nodes the same way:

src/locale/messages.es.xlf ()
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
  <source>I don&apos;t output any element</source>
  <target>No genero ningún elemento</target>
</trans-unit>
<trans-unit id="df3cf8b55cb528cf8c00167e0b119bfb828538b5" datatype="html">
  <source>I don&apos;t output any element either</source>
  <target>Yo tampoco genero ningún elemento</target>
  <note priority="1" from="description">optional description</note>
  <note priority="1" from="meaning">optional meaning</note>
</trans-unit>
<trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html">
  <source>Angular logo</source>
  <target>Logo de Angular</target>
</trans-unit>

The tool generated the ids 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.

This is why you should specify custom ids and avoid tool generated ids.

Translate plural and select

Translating plural and select messages is a little tricky.

The <source> tag is empty for plural and select translation units, which makes them hard to correlate with the original template. The XLIFF format doesn't yet support the ICU rules. However, the XMB format does support the ICU rules.

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.

Translate plural

To translate a plural, translate its ICU format match values:

src/locale/messages.es.xlf ()
<trans-unit id="6e22e74e8cbd3095560cfe08993c4fdfa3c50eb0" datatype="html">
  <source/>
  <target>{wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}}</target>
</trans-unit>

Translate select

The select behaves a little differently. Here again is the ICU format message in the component template:

src/app/app.component.html
<span i18n>The hero is {gender, select, m {male} f {female}}</span>

The extraction tool broke that into two translation units.

The first unit contains the text that was outside the select. In place of the select is a placeholder, <x id="ICU">, that represents the select message. Translate the text and leave the placeholder where it is.

src/locale/messages.es.xlf ()
<trans-unit id="61cafedb85466ab789b3ae817bba1a545468ee1c" datatype="html">
  <source>The hero is <x id="ICU"/></source>
  <target>El heroe es <x id="ICU"/></target>
</trans-unit>

The second translation unit, immediately below the first one, contains the select message. Translate that.

src/locale/messages.es.xlf ()
<trans-unit id="14c7055d67771a3b7b6888d282ac092896be06b6" datatype="html">
  <source/>
  <target>{gender, select, m {hombre} f {mujer}}</target>
</trans-unit>

Here they are together, after translation:

src/locale/messages.es.xlf ()
<trans-unit id="61cafedb85466ab789b3ae817bba1a545468ee1c" datatype="html">
  <source>The hero is <x id="ICU"/></source>
  <target>El heroe es <x id="ICU"/></target>
</trans-unit>
<trans-unit id="14c7055d67771a3b7b6888d282ac092896be06b6" datatype="html">
  <source/>
  <target>{gender, select, m {hombre} f {mujer}}</target>
</trans-unit>

Translate a nested expression

A nested expression is not different from the previous ones. As in the previous example, we have two translation units.

The first one contains the text outside the nested expression:

src/locale/messages.es.xlf ()
<trans-unit id="2cf9a08c5b6e3612572a2a36dd46563013848382" datatype="html">
  <source>Here we have: <x id="ICU"/></source>
  <target>Aquí tenemos: <x id="ICU"/></target>
</trans-unit>

The second unit contains the complete nested expression:

src/locale/messages.es.xlf ()
<trans-unit id="db1b921b55301ce3957e382090729562002da036" datatype="html">
  <source/>
  <target>
    {count, plural,
      =0 { nadie }
      =1 {{gender, select, m {un hombre} f {una mujer}}}
      other {{{heroes.length}} {gender, select, m {hombres} f {mujeres}}}
    }
  </target>
</trans-unit>

And both together:

src/locale/messages.es.xlf ()
<trans-unit id="2cf9a08c5b6e3612572a2a36dd46563013848382" datatype="html">
  <source>Here we have: <x id="ICU"/></source>
  <target>Aquí tenemos: <x id="ICU"/></target>
</trans-unit>
<trans-unit id="db1b921b55301ce3957e382090729562002da036" datatype="html">
  <source/>
  <target>
    {count, plural,
      =0 { nadie }
      =1 {{gender, select, m {un hombre} f {una mujer}}}
      other {{{heroes.length}} {gender, select, m {hombres} f {mujeres}}}
    }
  </target>
</trans-unit>

The entire template translation is complete. It's time to incorporate that translation into the application.

The app before translation

When the previous steps finish, the sample app and its translation file are as follows:

src/app/app.component.html
<h1 i18n="User welcome|An introduction header for this sample@@introductionHeader">
  Hello i18n!
</h1>

<ng-container i18n>I don't output any element</ng-container>

<br />

<!--i18n: optional meaning|optional description -->
I don't output any element either
<!--/i18n-->

<br />

<img [src]="logo" i18n-title title="Angular logo" />
<br>
<button (click)="inc(1)">+</button> <button (click)="inc(-1)">-</button>
<span i18n>{wolves, plural, =0 {no wolves} =1 {one wolf} =2 {two wolves} other {a wolf pack}}</span>
({{wolves}})
<br><br>
<button (click)="male()">&#9794;</button> <button (click)="female()">&#9792;</button>
<span i18n>The hero is {gender, select, m {male} f {female}}</span>
<br><br>
<span i18n>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}}}
}</span>
src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html'
})
export class AppComponent {
  wolves = 0;
  gender = 'f';
  fly = true;
  logo = 'https://angular.io/resources/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));
  }
  male()   { this.gender = 'm'; }
  female() { this.gender = 'f'; }
}
src/app/app.module.ts
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 { }
src/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);
src/locale/messages.es.xlf
<?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="introductionHeader" datatype="html">
        <source>Hello i18n!</source>
        <target>¡Hola i18n!</target>
        <note priority="1" from="description">An introduction header for this sample</note>
        <note priority="1" from="meaning">User welcome</note>
      </trans-unit>
      <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
        <source>I don&apos;t output any element</source>
        <target>No genero ningún elemento</target>
      </trans-unit>
      <trans-unit id="df3cf8b55cb528cf8c00167e0b119bfb828538b5" datatype="html">
        <source>I don&apos;t output any element either</source>
        <target>Yo tampoco genero ningún elemento</target>
        <note priority="1" from="description">optional description</note>
        <note priority="1" from="meaning">optional meaning</note>
      </trans-unit>
      <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html">
        <source>Angular logo</source>
        <target>Logo de Angular</target>
      </trans-unit>
      <trans-unit id="6e22e74e8cbd3095560cfe08993c4fdfa3c50eb0" datatype="html">
        <source/>
        <target>{wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}}</target>
      </trans-unit>
      <trans-unit id="61cafedb85466ab789b3ae817bba1a545468ee1c" datatype="html">
        <source>The hero is <x id="ICU"/></source>
        <target>El heroe es <x id="ICU"/></target>
      </trans-unit>
      <trans-unit id="14c7055d67771a3b7b6888d282ac092896be06b6" datatype="html">
        <source/>
        <target>{gender, select, m {hombre} f {mujer}}</target>
      </trans-unit>
      <trans-unit id="db04527df562d12c8607eab2b5723ef6e2066ba0" datatype="html">
        <source>Here we have: <x id="ICU"/></source>
        <target/>
      </trans-unit>
      <trans-unit id="000058be4e6f08b685d1d0a70f9da68067df7379" datatype="html">
        <source/>
        <target/>
      </trans-unit>
      <trans-unit id="2cf9a08c5b6e3612572a2a36dd46563013848382" datatype="html">
        <source>Here we have: <x id="ICU"/></source>
        <target>Aquí tenemos: <x id="ICU"/></target>
      </trans-unit>
      <trans-unit id="db1b921b55301ce3957e382090729562002da036" datatype="html">
        <source/>
        <target>
          {count, plural,
            =0 { nadie }
            =1 {{gender, select, m {un hombre} f {una mujer}}}
            other {{{heroes.length}} {gender, select, m {hombres} f {mujeres}}}
          }
        </target>
      </trans-unit>
    </body>
  </file>
</xliff>

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:

  • The translation file.
  • The translation file format.
  • The Locale ID (es 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.

  • With JIT, you provide the information at bootstrap time.
  • With AOT, you pass the information as ngc options.

Merge with the JIT compiler

The JIT 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 follows:

index.html (launch script)
<script>
  // Get the locale id somehow
  document.locale = 'es';

  // Map to the text plugin
  System.config({
    map: {
      text: 'systemjs-text-plugin.js'
    }
  });

  // Launch the app
  System.import('main.js').catch(function(err){ console.error(err); });
</script>

In this sample, the user's language is hard-coded as a global document.locale variable in the index.html.

SystemJS text plugin

This plugin only applies to an application using SystemJS. If you are using the Angular CLI, please refer to their docs.

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:

src/systemjs-text-plugin.js
/*
  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});';
}

Create translation providers

Three providers tell the JIT compiler how to translate the template texts for a particular language while compiling the application:

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:

src/app/i18n-providers.ts
import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID, MissingTranslationStrategy } from '@angular/core';
import { CompilerConfig } from '@angular/compiler';

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 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`;

  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
}
  1. It gets the locale from the global document.locale variable that was set in index.html.

  2. 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.

  3. It creates a transaction filename from the locale according to the name and location convention described earlier.

  4. 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.

  5. The callback composes a providers array with the three translation providers.

  6. Finally, getTranslationProviders() returns the entire effort as a promise.

The LOCALE_ID has to be a valid locale id as explained in here.

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:

src/main.ts
import { platformBrowserDynamic }  from '@angular/platform-browser-dynamic';
import { getTranslationProviders } from './app/i18n-providers';

import { AppModule } from './app/app.module';

getTranslationProviders().then(providers => {
  const options = { providers };
  platformBrowserDynamic().bootstrapModule(AppModule, options);
});

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.

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 and refer to the AOT guide 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:

src/app/i18n-providers.ts
return getTranslationsWithSystemJs(translationFile)
  .then( (translations: string ) => [
    { provide: TRANSLATIONS, useValue: translations },
    { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
    { provide: LOCALE_ID, useValue: locale },
    { provide: CompilerConfig, useValue: new CompilerConfig({ missingTranslation: MissingTranslationStrategy.Error }) }
  ])
  .catch(() => noProviders); // ignore if file not found

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

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, 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.

© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/guide/i18n