22 KiB
@title Internationalization (i18n)
@intro Translate the app's template text into multiple languages.
@description
{@a top} Angular's internationalization (i18n) tools help make your app available in multiple languages.
Table of contents
- Angular and i18n template translation
- Mark text with the i18n attribute
- Add i18n-... translation attributes
- Handle singular and plural
- Select among alternative texts
- Create a translation source file with the ng-xi18n extraction tool
- Translate text messages
- Merge the completed translation file into the app
- Translation file maintenance and id changes Try this live example of a JIT-compiled app, translated into Spanish.
{@a angular-i18n}
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" interchangably and in the combination, "text message". The i18n template translation process has four phases:
-
Mark static text messages in your component templates for translation.
-
An angular i18n tool extracts the marked messages into an industry standard translation source file.
-
A translator edits that file, translating the extracted text messages into the target language, and returns the file to you.
-
The Angular compiler imports the completed translation files, replaces the original messages with translated text, and generates a new version of the application in the target language.
You need to build and deploy a separate version of the application for each supported language.
{@a i18n-attribute}
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:
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='greeting'}
Add the i18n
attribute to the tag to mark it for translation.
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='i18n-attribute'}
Help the translator with a description and intent
In order to translate it accurately, the translator may need a description of the message. Assign a description to the i18n attribute:
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='i18n-attribute-desc'}
In order to deliver a correct translation, the translator may need to
know your intent—the true meaning of the text
within this particular application context.
In front of the description, add some contextual meaning to the assigned string,
separating it from the description with the |
character (<meaning>|<description>
):
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-attribute-meaning'}
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 facilitiate contextually-specific translations.
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 renderered:
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-ng-container'}
(2) Wrap the text in a pair of HTML comments:
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-with-comment'}
{@a translate-attributes}
Add i18n-... translation attributes
You've added an image to your template. You care about accessibility too so you add a title
attribute:
{@example 'cb-i18n/ts/src/app/app.component.1.html' region='i18n-title'}
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:
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-title-translate'}
You can also assign a meaning and a description with the i18n-x="<meaning>|<description>"
syntax.
{@a cardinality}
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:
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-plural'}
- 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
- =1
- =5
- few
- 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.
{@a select}
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:
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-select'}
{@a ng-xi18n}
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:
Open a terminal window at the root of the application project and enter the ng-xi18n
command:
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 Localisation Interchange File Format (XLIFF, version 1.2).
{@a other-formats}
Other translation formats
You can generate a file named messages.xmb
in the
XML Message Bundle (XMB) format
by adding the --i18nFormat=xmb
flag.
This sample sticks with the XLIFF format.
{@a ng-xi18n-options}
Other options
You may have to specify additional options.
For example, if the tsconfig.json
TypeScript configuration
file is located somewhere other than in the root folder,
you must identify the path to it with the -p
option:
./node_modules/.bin/ng-xi18n -p path/to/tsconfig.json
./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p path/to/tsconfig.json
{@a npm-i18n-script}
Add an npm script for convenience
Consider adding a convenience shortcut to the scripts
section of the package.json
to make the command easier to remember and run:
"scripts": {
"i18n": "ng-xi18n",
...
}
Now you can issue command variations such as these:
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
.
{@a translate}
Translate text messages
The ng-xi18n
command generates a translation source file
in the project root folder named messages.xlf
.
The next step is to translate the English language template
text into the specific language translation
files. The cookbook sample creates a Spanish translation file.
{@a localization-folder}
Create a localization folder
You will probably translate into more than one other language so it's a good idea for the project structure to reflect your entire internationalization effort.
One approach is to dedicate a folder to localization and store related assets
(for example, internationalization files) there.
Localization and internationalization are
different but closely related terms.This cookbook follows that suggestion. It has a locale
folder under the 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.xlf
for 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:
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-hello'}
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 Spanish greeting:
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-hello'}
Note that the tool generates the `id`. **Don't touch it.**
Its value depends on the content of the message and its assigned meaning.
Change either factor and the `id` changes as well.
See the **[translation file maintenance discussion](guide/i18n#maintenance)**.
Translate the other text nodes the same way:
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-other-nodes'}
{@a translate-plural-select}
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; it soon will.
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:
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-plural'}
Translate select
The select
behaves a little differently. Here again is the ICU format message in the component template:
{@example 'cb-i18n/ts/src/app/app.component.html' region='i18n-select'}
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.
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translate-select-1'}
The second translation unit, immediately below the first one, contains the select
message. Translate that.
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translate-select-2'}
Here they are together, after translation:
{@example 'cb-i18n/ts/src/locale/messages.es.xlf.html' region='translated-select'}
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:
{@example 'cb-i18n/ts/src/app/app.component.html'} {@example 'cb-i18n/ts/src/app/app.component.ts'} {@example 'cb-i18n/ts/src/app/app.module.ts'} {@example 'cb-i18n/ts/src/main.1.ts'} {@example 'cb-i18n/ts/src/locale/messages.es.xlf.html'}{@a merge}
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 (.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
(
es
oren-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.
{@a jit}
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:
- Determining the language version for the current user.
- Importing the appropriate language translation file as a string constant.
- Creating corresponding translation providers to guide the JIT compiler.
- Bootstrapping the application with those providers.
Open index.html
and revise the launch script as follows:
{@example 'cb-i18n/ts/src/index.html' region='i18n'}
In this sample, the user's language is hardcoded as a global document.locale
variable
in the index.html
.
{@a text-plugin}
SystemJS Text plugin
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:
{@example 'cb-i18n/ts/src/systemjs-text-plugin.js'}
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
orxtb
.LOCALE_ID
is the locale of the target language.
The getTranslationProviders
function in the following src/app/i18n-providers.ts
creates those providers based on the user's locale
and the corresponding translation file:
{@example 'cb-i18n/ts/src/app/i18n-providers.ts'}
-
It gets the locale from the global
document.locale
variable that was set inindex.html
. -
If there is no locale or the language is U.S. English (
en-US
), there is no need to translate. The function returns an emptynoProviders
array as aPromise
. It must return aPromise
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.
-
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. -
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 src/main.ts
and modify the bootstrap code as follows:
{@example 'cb-i18n/ts/src/main.ts'}
Notice that it waits for the getTranslationProviders
promise to resolve before
bootstrapping the app.
The app is now internationalized for English and Spanish and there is a clear path for adding more languages.
{@a aot}
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 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 cookbook 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
{@a maintenance}
Translation 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 most changes to existing markup trigger
generation of new id
s for the affected translation units.
After an id
changes, the translation files are no longer in-sync.
All translated versions of the application will fail during re-compilation.
The error messages identify the old id
s that are no longer valid but
they don't tell you what the new id
s should be.
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
help you find and update id
changes across your translation files.