This is the archived documentation for Angular v6. Please visit angular.io to see documentation for the current version of Angular.

Internationalization (i18n)

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.

See the i18n Examplei18n Example for a simple example of an AOT-compiled app, translated into French.

Angular and i18n

Internationalization is the process of designing and preparing your app to be usable in different languages. Localization is the process of translating your internationalized app into specific languages for particular locales.

Angular simplifies the following aspects of internationalization:

  • Displaying dates, number, percentages, and currencies in a local format.
  • Preparing text in component templates for translation.
  • Handling plural forms of words.
  • Handling alternative text.

For localization, you can use the Angular CLI to generate most of the boilerplate necessary to create files for translators, and to publish your app in multiple languages. After you have set up your app to use i18n, the CLI can help you with the following steps:

  • Extracting localizable text into a file that you can send out to be translated.
  • Building and serving the app for a given locale, using the translated text.
  • Creating multiple language versions of your app.

Setting up the locale of your app

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

By default, Angular uses the locale en-US, which is English as spoken in the United States of America.

To set your app's locale to another value, use the CLI parameter --configuration with the value of the locale id that you want to use:

ng serve --configuration=fr
      
      ng serve --configuration=fr
    

If you use JIT, you also need to define the LOCALE_ID provider in your main module:

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 { }
src/app/app.module.ts
      
      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 { }
    

For more information about Unicode locale identifiers, see the CLDR core spec.

For a complete list of locales supported by Angular, see the Angular repository.

The locale identifiers used by CLDR and Angular are based on BCP47. These specifications change over time; the following table maps previous identifiers to current ones at time of writing:

Locale name Old locale id New locale id
Indonesian in id
Hebrew iw he
Romanian Moldova mo ro-MD
Norwegian Bokmål no, no-NO nb
Serbian Latin sh sr-Latn
Filipino tl fil
Portuguese Brazil pt-BR pt
Chinese Simplified zh-cn, zh-Hans-CN zh-Hans
Chinese Traditional zh-tw, zh-Hant-TW zh-Hant
Chinese Traditional Hong Kong zh-hk zh-Hant-HK

i18n pipes

Angular pipes can help you with internationalization: the DatePipe, CurrencyPipe, DecimalPipe and PercentPipe use locale data to format data based on the LOCALE_ID.

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 --configuration with ng serve and ng build.

If you want to import locale data for other languages, you can do it manually:

import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; // the second parameter 'fr' is optional registerLocaleData(localeFr, 'fr');
src/app/app.module.ts
      
      import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';

// the second parameter 'fr' is optional
registerLocaleData(localeFr, 'fr');
    

The first parameter is an object containing the locale data imported from @angular/common/locales. By default, the imported locale data is registered with the locale id that is defined in the Angular locale data itself. If you want to register the imported locale data with another locale id, use the second parameter to specify a custom locale id. For example, Angular's locale data defines the locale id for French as "fr". You can use the second parameter to associate the imported French locale data with the custom locale id "fr-FR" instead of "fr".

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.

import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; registerLocaleData(localeFr, 'fr-FR', localeFrExtra);
src/app/app.module.ts
      
      import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';

registerLocaleData(localeFr, 'fr-FR', localeFrExtra);
    

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

The i18n template translation process has four phases:

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

  2. Create a translation file: Use the Angular CLI xi18n command to extract the marked text into an industry-standard translation source file.

  3. Edit the generated translation file: Translate the extracted text into the target language.

  4. Merge the completed translation file into the app. To do this, use the Angular CLI build command to compile the app, choosing a locale-specific configuration, or specifying the following command options.

    • --i18nFile=path to the translation file
    • --i18nFormat=format of the translation file
    • --i18nLocale= locale id

The command 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.

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 <h1> tag displays a simple English language greeting, "Hello i18n!"

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

To mark the greeting for translation, add the i18n attribute to the <h1> tag.

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

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

Help the translator with a description and meaning

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:

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

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 i18n attribute value with the meaning and separating it from the description with the | character: <meaning>|<description>

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

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.

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:

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

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.

Alternatively, you can specify a custom id in the i18n attribute by using the prefix @@. The example below defines the custom id introductionHeader:

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

When you specify a custom id, the extractor tool and compiler generate a translation unit with that custom id.

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

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:

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

You also can add a meaning, as shown in this example:

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

Define unique custom ids

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.

In the example below the custom id myId is used for two different messages:

<h3 i18n="@@myId">Hello</h3> <!-- ... --> <p i18n="@@myId">Good bye</p>
      
      <h3 i18n="@@myId">Hello</h3>
<!-- ... -->
<p i18n="@@myId">Good bye</p>
    

Consider this translation to French:

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

Because the custom id is the same, both of the elements in the resulting translation contain the same text, Bonjour:

<h3>Bonjour</h3> <!-- ... --> <p>Bonjour</p>
      
      <h3>Bonjour</h3>
<!-- ... -->
<p>Bonjour</p>
    

Translate text without creating an element

If there is a section of text that you would like to translate, you can wrap it in a <span> tag. However, if you don't want to create a new DOM element merely to facilitate translation, you can wrap the text in an <ng-container> element. The <ng-container> is transformed into an html comment:

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

Translate attributes

Displayed text is sometimes supplied as the value of an attribute, rather than the content of tag. For example, if your template has an image with a title attribute, the text value of the title attribute needs to be translated.

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

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:

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

This technique works for any attribute of any element.

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

Regular expressions for plurals and selections

Different languages have different pluralization rules and grammatical constructions that add complexity to the translation task. You can use regular expressions with the plural and select clauses to provide patterns that aid translation in these cases.

Pluralization

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.

The example below shows how to use a plural ICU expression to display one of those three options based on when the update occurred:

<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
src/app/app.component.html
      
      <span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
    
  • 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.

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
  • one
  • two
  • few
  • many
  • other

After the pluralization category, put the default English text in braces ({}).

In the example above, the three options are specified according to that pluralization pattern. For talking 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.

You can use interpolations and html markup inside of your translations.

Select among alternative text messages

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 ICU expression. It is similar to the plural 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 one of the following string values: "m", "f" or "o". The message maps those values to the appropriate translations:

<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
src/app/app.component.html
      
      <span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
    

Nesting plural and select ICU expressions

You can also nest different ICU expressions together, as shown in this example:

<span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}} </span>
src/app/app.component.html
      
      <span i18n>Updated: {minutes, plural,
  =0 {just now}
  =1 {one minute ago}
  other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}
</span>
    

Create a translation source file

When your app is ready, you can use the Angular CLI to extract the text messages marked with i18n and attributes marked with i18n-x into a translation source file. Open a terminal window at the root of the app project and run the CLI command xi18n.

ng xi18n
      
      ng xi18n
    

By default, the command creates a file named messages.xlf in your src/ folder.

If you don't use the CLI, you have two options:

Output options

You can supply command options to change the format, the name, the location, and the source locale of the extracted file. For example, to create a file in the src/locale folder, specify the output path:

ng xi18n --output-path src/locale
      
      ng xi18n --output-path src/locale
    

By default, the xi18n command generates a translation file named messages.xlf in the XML Localization Interchange File Format (XLIFF, version 1.2).

The command can read and write files in three translation formats:

You can specify the translation format explicitly with the --i18nFormat command option, as illustrated in these example commands:

ng xi18n --i18n-format=xlf ng xi18n --i18n-format=xlf2 ng xi18n --i18n-format=xmb
      
      ng xi18n  --i18n-format=xlf
ng xi18n  --i18n-format=xlf2
ng xi18n  --i18n-format=xmb
    

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.

You can change the name of the translation source file that is generated by the extraction tool with the --outFile command option:

ng xi18n --out-file source.xlf
      
      ng xi18n --out-file source.xlf
    

You can specify the base locale of your app with the--i18n-locale command option:

ng xi18n --i18n-locale fr
      
      ng xi18n --i18n-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.

Translate the source text

The ng xi18n command generates a translation source file named messages.xlf in the project src folder. The next step is to translate the display strings in this source file into language-specific translation files. The example in this guide creates a French translation file.

Create a localization folder

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.

Localization and internationalization are different but closely related terms.

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.

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.

Translate text nodes

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 <trans-unit> section:
<trans-unit id="introductionHeader" datatype="html"> <source>Hello i18n!</source> <note priority="1" from="description">An introduction header for this sample</note> <note priority="1" from="meaning">User welcome</note> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="introductionHeader" datatype="html">
  <source>Hello i18n!</source>
  <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 that you marked with the i18n attribute earlier in this guide.

Note that the translation unit id=introductionHeader is derived from the custom id that you set earlier, but without the @@ prefix required in the source HTML.

  1. Duplicate the <source/> 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 information and context provided by the source, description, and meaning elements to guide your selection of the appropriate French translation.
<trans-unit id="introductionHeader" 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>
src/locale/messages.fr.xlf (<trans-unit>, after translation)
      
      <trans-unit id="introductionHeader" 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>
    
  1. Translate the other text nodes the same way:
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don&apos;t output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
  <source>I don&apos;t output any element</source>
  <target>Je n'affiche aucun élément</target>
</trans-unit>
<trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html">
  <source>Angular logo</source>
  <target>Logo d'Angular</target>
</trans-unit>
    

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.

Translating plural and select expressions

The plural and select ICU expressions are extracted separately, so they require special attention when preparing for translation.

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.

Translate plural

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

<trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target>
</trans-unit>
    

You can add or remove plural cases, with each language having its own cardinality. (See CLDR plural rules.)

Translate select

Below is the content of our example select ICU expression in the component template:

<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
src/app/app.component.html
      
      <span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
    

The extraction tool broke that into two translation units because ICU expressions are extracted separately.

The first unit contains the text that was outside of the select. In place of the select is a placeholder, <x id="ICU">, that represents the select message. 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.

</trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      </trans-unit>
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
  <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
  <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
</trans-unit>
    

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

<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
  <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
  <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
</trans-unit>
    

Here they are together, after translation:

</trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      </trans-unit>
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
  <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
  <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
  <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
  <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
</trans-unit>
    

Translate a nested expression

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:

<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
  <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
  <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>
    

The second unit contains the complete nested expression:

<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
</trans-unit>
    

And both together:

<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit>
src/locale/messages.fr.xlf (<trans-unit>)
      
      <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
  <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
  <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
</trans-unit>
    

The entire template translation is complete. The next section describes how to load that translation into the app.

The app and its translation file

The sample app and its translation file are now as follows:

<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 /> <img [src]="logo" i18n-title title="Angular logo" /> <br> <button (click)="inc(1)">+</button> <button (click)="inc(-1)">-</button> <span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span> ({{minutes}}) <br><br> <button (click)="male()">&#9794;</button> <button (click)="female()">&#9792;</button> <button (click)="other()">&#9895;</button> <span i18n>The author is {gender, select, male {male} female {female} other {other}}</span> <br><br> <span i18n>Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}} </span> import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent { minutes = 0; gender = 'female'; fly = true; logo = 'https://angular.io/assets/images/logos/angular/angular.png'; heroes: string[] = ['Magneta', 'Celeritas', 'Dynama']; inc(i: number) { this.minutes = Math.min(5, Math.max(0, this.minutes + i)); } male() { this.gender = 'male'; } female() { this.gender = 'female'; } other() { this.gender = 'other'; } } 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 { } 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); <?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> <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="introductionHeader" 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> <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html"> <source>I don&apos;t output any element</source> <target>Je n'affiche aucun élément</target> </trans-unit> <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html"> <source>Angular logo</source> <target>Logo d'Angular</target> </trans-unit> <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target> </trans-unit> </trans-unit> <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html"> <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source> <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target> </trans-unit> <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html"> <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source> <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target> </trans-unit> <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html"> <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source> <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target> </trans-unit> <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html"> <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source> <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target> </trans-unit> </body> </file> </xliff>
      
      
  1. <h1 i18n="User welcome|An introduction header for this sample@@introductionHeader">
  2. Hello i18n!
  3. </h1>
  4.  
  5. <ng-container i18n>I don't output any element</ng-container>
  6.  
  7. <br />
  8.  
  9. <img [src]="logo" i18n-title title="Angular logo" />
  10. <br>
  11. <button (click)="inc(1)">+</button> <button (click)="inc(-1)">-</button>
  12. <span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
  13. ({{minutes}})
  14. <br><br>
  15. <button (click)="male()">&#9794;</button> <button (click)="female()">&#9792;</button> <button (click)="other()">&#9895;</button>
  16. <span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
  17. <br><br>
  18. <span i18n>Updated: {minutes, plural,
  19. =0 {just now}
  20. =1 {one minute ago}
  21. other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}
  22. </span>

Merge the completed translation file into the app

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 (fr or en-US for instance).

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, you pass the information as a configuration
  • With JIT, you provide the information at bootstrap time.

Merge with the AOT compiler

The AOT compiler is part of a build process that produces a small, fast, ready-to-run application package, typically for production.

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.

To instruct the AOT compiler to use your translation configuration, set the three "i18n" build configuration options in your angular.json file.

  • i18nFile: the path to the translation file.
  • i18nFormat: the format of the translation file.
  • i18nLocale: the locale id.

You should also direct the output to a locale-specific folder to keep it separate from other locale versions of your app, by setting the outputPath configuration option.

"build": { ... "configurations": { ... "fr": { "aot": true, "outputPath": "dist/my-project-fr/", "i18nFile": "src/locale/messages.fr.xlf", "i18nFormat": "xlf", "i18nLocale": "fr", ... } } }, "serve": { ... "configurations": { ... "fr": { "browserTarget": "*project-name*:build:fr" } } }
      
      
  1. "build": {
  2. ...
  3. "configurations": {
  4. ...
  5. "fr": {
  6. "aot": true,
  7. "outputPath": "dist/my-project-fr/",
  8. "i18nFile": "src/locale/messages.fr.xlf",
  9. "i18nFormat": "xlf",
  10. "i18nLocale": "fr",
  11. ...
  12. }
  13. }
  14. },
  15. "serve": {
  16. ...
  17. "configurations": {
  18. ...
  19. "fr": {
  20. "browserTarget": "*project-name*:build:fr"
  21. }
  22. }
  23. }

You can then pass this configuration to the ng serve or ng build commands. The example below shows how to serve the French language file created in previous sections of this guide:

ng serve --configuration=fr
      
      ng serve --configuration=fr
    

For production builds, you define a separate production-fr build configuration in the CLI configuration file, angular.json.

... "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { ... }, "configurations": { "fr": { "aot": true, "outputPath": "dist/my-project-fr/", "i18nFile": "src/locale/messages.fr.xlf", "i18nFormat": "xlf", "i18nLocale": "fr", "i18nMissingTranslation": "error", } // ... "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "my-project:build" }, "configurations": { "production": { "browserTarget": "my-project:build:production" }, "fr": { "browserTarget": "my-project:build:fr" } } },
      
      
  1. ...
  2. "architect": {
  3. "build": {
  4. "builder": "@angular-devkit/build-angular:browser",
  5. "options": { ... },
  6. "configurations": {
  7. "fr": {
  8. "aot": true,
  9. "outputPath": "dist/my-project-fr/",
  10. "i18nFile": "src/locale/messages.fr.xlf",
  11. "i18nFormat": "xlf",
  12. "i18nLocale": "fr",
  13. "i18nMissingTranslation": "error",
  14. }
  15. // ...
  16. "serve": {
  17. "builder": "@angular-devkit/build-angular:dev-server",
  18. "options": {
  19. "browserTarget": "my-project:build"
  20. },
  21. "configurations": {
  22. "production": {
  23. "browserTarget": "my-project:build:production"
  24. },
  25. "fr": {
  26. "browserTarget": "my-project:build:fr"
  27. }
  28. }
  29. },

The same configuration options can also be provided through the CLI with your existing production configuration.

ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr
      
      ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr
    

Merge with the JIT compiler

The JITcompiler compiles your app in the browser as the app loads. To support translation with the JIT compiler, you must do the following:

  1. Import the appropriate language translation file as a string constant.
  2. Create corresponding translation providers for the JIT compiler.
  3. Bootstrap the app with those providers.

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

The Angular bootstrapModule method has a second compilerOptions parameter that can influence the behavior of the compiler. You can use it to specify the translation providers:

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'} ] });
src/main.ts
      
      
  1. import { enableProdMode, TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular/core';
  2. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
  3.  
  4. import { AppModule } from './app/app.module';
  5. import { environment } from './environments/environment';
  6.  
  7. if (environment.production) {
  8. enableProdMode();
  9. }
  10.  
  11. // use the require method provided by webpack
  12. declare const require;
  13. // we use the webpack raw-loader to return the content as a string
  14. const translations = require(`raw-loader!./locale/messages.fr.xlf`);
  15.  
  16. platformBrowserDynamic().bootstrapModule(AppModule, {
  17. providers: [
  18. {provide: TRANSLATIONS, useValue: translations},
  19. {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'}
  20. ]
  21. });

Then provide the LOCALE_ID in the main module:

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 { }
src/app/app.module.ts
      
      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 { }
    

Report missing translations

By default, when a translation is missing, the build succeeds but generates a warning such as Missing translation for message "foo". You can configure the level of warning that is generated by the Angular compiler:

  • Error: throw an error. If you are using AOT compilation, the build will fail. If you are using JIT compilation, the app will fail to load.
  • Warning (default): show a 'Missing translation' warning in the console or shell.
  • Ignore: do nothing.

You specify the warning level in the configurations section your Angular CLI build configuration. The example below shows how to set the warning level to error:

"configurations": { ... "fr": { ... "i18nMissingTranslation": "error" } }
      
      "configurations": {
  ...
  "fr": {
    ...
    "i18nMissingTranslation": "error"
  }
}
    

If you use the JIT compiler, specify the warning level in the compiler config at bootstrap by adding the 'MissingTranslationStrategy' property. The example below shows how to set the warning level to error:

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: [ // ... ] });
src/main.ts
      
      
  1. import { MissingTranslationStrategy } from '@angular/core';
  2. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
  3. import { AppModule } from './app/app.module';
  4.  
  5. // ...
  6.  
  7. platformBrowserDynamic().bootstrapModule(AppModule, {
  8. missingTranslation: MissingTranslationStrategy.Error,
  9. providers: [
  10. // ...
  11. ]
  12. });

Build for multiple locales

When you use the CLI build or serve command to build your application for different locales, change the output path using the --outputPath command option (along with the i18n-specific command options), so that the translation files are saved to different locations. When you are serving a locale-specific version from a subdirectory, you can also change the base URL used by your app by specifying the --baseHref option.

For example, if the French version of your application is served from https://myapp.com/fr/, configure the build for the French version as follows.

"configurations": { "fr": { "aot": true, "outputPath": "dist/my-project-fr/", "baseHref": "/fr/", "i18nFile": "src/locale/messages.fr.xlf", "i18nFormat": "xlf", "i18nLocale": "fr", "i18nMissingTranslation": "error", }
      
      "configurations": {
  "fr": {
    "aot": true,
    "outputPath": "dist/my-project-fr/",
    "baseHref": "/fr/",
    "i18nFile": "src/locale/messages.fr.xlf",
    "i18nFormat": "xlf",
    "i18nLocale": "fr",
    "i18nMissingTranslation": "error",
  }
    

For more details about how to create scripts to generate an app in multiple languages and how to set up Apache 2 to serve them from different subdirectories, read this tutorial by Philippe Martin.