Home | Send Feedback | Share on Bluesky |

Angular i18n built-in support

Published: 11. August 2025  •  angular

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

angular.json

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>

home.html

The i18n attribute can have a value, that specifies metadata information. The format of the metadata string is as follows:

{meaning}|{description}@@{custom_id}
<h1 i18n="Title|Title on the home page">Welcome to the i18n Showcase</h1>

home.html

With a custom identifier

<p i18n="downloadAction|Button label to start a download@@downloadLabel">
  Download
</p>

home.html

Marked text can contain interpolations.

<p i18n="greeting|Greeting users with their name">Hello, {{ name }}!</p>

home.html

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>

home.html

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

home.html


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

home.ts

You can use interpolations in a $localize tagged message string.

  greeting = $localize`Hello ${this.name}, you have ${this.itemCount} items in your cart.`;

home.ts


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>

home.html

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:


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>

home.html

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>

home.html

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

package.json

The extract-i18n can export the texts into various formats:

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"
          }
        }
      },

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

angular.json

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.

        "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"]
            }

angular.json

In the serve section add a new configuration, that combines the development and de build configurations.

            "de": {
              "buildTarget": "angular-i18n-demo:build:development,de"
            }

angular.json

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

package.json

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.