Internationalization (i18n) is the process of building applications for multiple languages and regions. In this blog post we will explore Angular's support for i18n.
Angular's built-in i18n is a compile-time solution. That means Angular will build the application multiple times for each language that needs to be supported. This is different to other solutions like ngx-translate and transloco that offer runtime translation capabilities.
Setup ¶
To get started with Angular's built-in i18n support, add the localize package.
ng add @angular/localize
This command installs the @angular/localize
package as development dependency to the package.json.
It also updates tsconfig.app
and angular.json
adding the polyfills and type support for the package.
Angular uses the Unicode locale identifier (Unicode locale ID) to reference the different language variants.
{language_id}-{locale_extension}
For example fr-CA
for Canadian French and fr-FR
for French (France). You can also omit the locale extension, for example fr
. In the following demo application I will use de
and en
.
After installing the localize library, configure the source language in which all the texts in the components will be written.
Open the angular.json
file and add the following configuration:
"projects": {
"angular-i18n-demo": {
"projectType": "application",
"i18n": {
"sourceLocale": "en",
If sourceLocale
is not set, Angular will use the default value of en-US
(English—United States).
Marking text for translation ¶
The next step is to mark the texts in the application that should be translated. Angular provides a simple way to mark text for translation using the i18n
attribute. Put this attribute on any element that contains text you want to translate.
<p i18n>
This page demonstrates the built-in internationalization features of Angular.
Explore text, attributes, ICUs (plural/select), and date/number formatting.
</p>
The i18n
attribute can have a value, that specifies metadata information. The format of the metadata string is as follows:
{meaning}|{description}@@{custom_id}
meaning
: the meaning or intent of the text within the specific contextdescription
: Additional information or contextcustom_id
: Custom identifier. If not provided, Angular will generate a unique ID.
<h1 i18n="Title|Title on the home page">Welcome to the i18n Showcase</h1>
With a custom identifier
<p i18n="downloadAction|Button label to start a download@@downloadLabel">
Download
</p>
Marked text can contain interpolations.
<p i18n="greeting|Greeting users with their name">Hello, {{ name }}!</p>
Text can also contains HTML elements.
<p i18n>
Learn more in the
<a href="https://angular.dev/guide/i18n" target="_blank" rel="noopener"
>Angular i18n guide</a
>.
</p>
To mark inline text not wrapped in an element, use the ng-container
element.
<ng-container i18n>Hello World</ng-container>
To mark attributes for translation, add the same attribute with the i18n-
prefix to the element.
This example marks the placeholder and aria-label attribute for translation.
<input
placeholder="Search"
i18n-placeholder
aria-label="Search"
i18n-aria-label />
To mark text in the component code, use the $localize
tagged message string. If you want to add the optional i18n metadata (meaning, description, id)
add them after $localize
surrounded by colons (:). After that follows the source text.
items = [
$localize`:fruitApples:Apples`,
$localize`:fruitBananas:Bananas`,
$localize`:fruitCherries:Cherries`
];
You can use interpolations in a $localize
tagged message string.
greeting = $localize`Hello ${this.name}, you have ${this.itemCount} items in your cart.`;
ICU expressions ¶
ICU (International Components for Unicode) expressions allow for advanced localization scenarios, such as pluralization. Angular supports the plural
expression for pluralization and the select
expression for text based on choices.
Here an example of a plural
expression.
<p i18n>
You have
{itemCount, plural, =0 {no items} =1 {one item} other {more than one item}} in
your cart.
</p>
ICU expressions are enclosed in single { }. The first part is a component property, followed by a comma, and then the type of expression (e.g., plural
). After another comma follows the categories and their translations.
In the example above if itemCount
is 0, the output will be "You have no items in your cart." If it is 1, the output will be "You have one item in your cart."
For any other value, it will say, "You have more than one item in your cart."
The plural expression supports the following categories:
=0 { }
orzero { }
: Property has value 0=1 { }
orone { }
: Property has value 1=2 { }
ortwo { }
: Property has value 2few { }
: Used for a small number. This depends on the language (e.g., 2–4 in some languages, 3–10 in others).many { }
: Used for a larger number. This can also be used for fractions in some languages.other { }
: This is the default, required category. It's a catch-all for any number that don't fit into the other categories.
The select
expression is used for choosing between different text options based on a variable's value. Here's an example:
<p i18n>
{gender, select, male {He} female {She} other {They}} added a new comment.
</p>
In this example, the output will change based on the value of the gender
component property. If gender
is set to male
, the output will be "He added a new comment." If it's set to female
, the output will be "She added a new comment."
For any other value, it will say, "They added a new comment."
ICU expressions can be nested in more complex scenarios.
<p i18n>
{unreadCount, plural,
=0 {
{gender, select, male {He has} female {She has} other {They have}}
no unread messages
}
=1 {
{gender, select, male {He has} female {She has} other {They have}}
one unread message
}
other {
{gender, select, male {He has} female {She has} other {They have}}
multiple unread messages
}
}
</p>
Translation ¶
After marking all the texts in the application, we need to extract them into a format suitable for translation.
For this purpose the Angular CLI provides the extract-i18n
command.
In this demo application I added the command as a script to the package.json file. This allows me to run the extraction with npm run extract-i18n
.
"extract-i18n": "ng extract-i18n --output-path src/i18n --format json",
The extract-i18n
can export the texts into various formats:
- ARB: Application Resource Bundle .arb
- JSON: JavaScript Object Notation .json
- XLIFF 1.2: XML Localization Interchange File Format, version 1.2 .xlf
- XLIFF 2: XML Localization Interchange File Format, version 2 .xlf
- XMB: XML Message Bundle .xmb (.xtb)
The default format is XLIFF 2.0 (xlf2
) which works well with Translation Management Systems (TMS). For this example I use JSON, which is a simpler format and easier to work with in a text editor.
After extracting the source language file, create a file for each language you want to support. In this example I've created a German translation file. The filename must include the locale code (e.g., messages.xlf --> messages.{locale}.xlf).
cp src/i18n/messages.json src/i18n/messages.de.json
After copying the source language file, you can delete it, as it is no longer necessary. The next step is to translate all the texts in the translation files. Angular's i18n guide provides helpful information on how to do this.
After all the translation work is done, the files need to be merged into the application. In the angular.json
file, where the sourceLocale
is defined, add the locales
configuration. This configuration tells Angular what locales are available and where to look for the translation files.
"projects": {
"angular-i18n-demo": {
"projectType": "application",
"i18n": {
"sourceLocale": "en",
"locales": {
"de": {
"translation": "src/i18n/messages.de.json"
}
}
},
In the build
section of the angular.json
file, we need to tell the Angular compiler what language-specific
versions of the application to build. This is done using the localize
option. This can be either true
to build all locales defined in
the i18n
configuration, or an array of specific locales to build only those (e.g. "localize": ["de"]
).
Set to false
, will disable localization and not generate any locale-specific versions.
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [],
"styles": ["src/styles.css"],
"polyfills": ["@angular/localize/init"],
"localize": true
},
The build command (ng build
) will build the application multiple times, once for each configured locale.
After building my example project, I will find the English version in dist\angular-i18n-demo\browser\en
and the German version in dist\angular-i18n-demo\browser\de
.
During build, when the Angular compiler encounters a missing translation, it will, by default, print a warning message to the console.
This behavior can be customized by configuring the i18nMissingTranslation
option in the angular.json
file.
error
: Throw an error and the build failsignore
: Do nothingwarning
: Display warning messages in console (default)
"build": {
"builder": "@angular/build:application",
"options": {
"i18nMissingTranslation": "error"
},
Production ¶
In production each language build needs to be served from the corresponding locale path. In my example that is de
resp. en
.
For example, https://example.com/de for the German version and https://example.com/en for the English version.
That means you need either a page in front of your application, where the user can click on a link with the desired language.
Another approach is to configure a reverse proxy in that way that he automatically redirects the user according the Accept-Language
HTTP header. In the Angular i18n guide you find configuration examples for Nginx and Apache.
It is also possible to add a language switcher in your application. But because Angular's built-in i18n support is a compile-time solution, language switching means that the user has to download the whole web application again for the new language.
Development ¶
Because of the complexity of i18n and to keep the rebuild time to a minimum, the development server only supports one locale at a time.
If the localize
option is set to true
and multiple languages are configured, the ng serve
command will automatically disable the localize
option and serve the application in the source language.
To test the other locales add a new configuration in the build.configurations
section. This will then override the value in the build.options
section.
"de": {
"localize": ["de"]
}
In the serve
section add a new configuration, that combines the development
and de
build configurations.
"de": {
"buildTarget": "angular-i18n-demo:build:development,de"
}
With this configuration in place, we can easily start the development server for each locale.
I added these two lines to the scripts section of package.json
.
"serve:de": "ng serve --configuration=de",
"serve:en": "ng serve --configuration=development",
npm run serve:en
starts the development configuration which disable localize
and opens the application in the source language. npm run serve:de
starts the development server with the German translation.
Conclusion ¶
This concludes our overview of Angular's built-in internationalization (i18n) features. You have seen how to use Angular's i18n capabilities to create a multilingual application, including how to mark text for translation, extract translatable strings, and manage translations for different locales. For more information, check out the official Angular i18n guide.
We have seen that Angular's built-in i18n is a compile-time solution that creates multiple versions of an application. If you are interested in a runtime solution, check out my other blog post about Transloco.