How to use Internationalization (i18n) in Angular

Paul Halliday

The consumption of modern web and mobile experiences is a worldwide thing. It isn’t just related to the locals around you and the surrounding culture. Therefore, just as you’d ensure that your design is aestheticly-pleasing and accessible, you should also ensure that your text is localized.

This is the process of ensuring that your application supports multiple languages. This is traditionally done in an Angular application via multiple ways.

You can, for example, use third party libraries such as ngx-translate, or, you can use the built-in i18n functionality.

We’ll specifically be looking at using the built-in i18n inside of this article.

🐊 Alligator.io recommends

Our recommended Angular courses

Video Version

Here’s a video I recorded if you’d rather watch a video about how to implement i18n in Angular:

New Angular Project

Create a new Angular project with the Angular CLI to establish a common base:

# Install the Angular CLI globally
$ npm i @angular/cli -g

# Create a new Angular project && change directory
$ ng new AngularInt

> N
> SCSS

$ cd AngularInt

# Open this up in VS Code and Serve
$ code . && ng serve

Translations

By default, Angular considers everything to be in en-US locale. We’ll have to add other locales and update our configuration to support this. You can see a list of various locales here: http://cldr.unicode.org/core-spec#Unicode_Language_and_Locale_Identifiers.

Project Templates

To create the basis for our translation project, head over to app.component.html and create the following template:

<section>
  <article>
    <h1>Under Construction!</h1>
    <p>This page is under construction.</p>
  </article>
</section>

We can also add some css to make it a little more magic, inside of app.component.scss:

section {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: #8e2de2; /* fallback for old browsers */
  background: -webkit-linear-gradient(to right, #4a00e0, #8e2de2); /* Chrome 10-25, Safari 5.1-6 */
  background: linear-gradient(to right, #4a00e0, #8e2de2);
}

article {
  text-align: center;
  color: white;
  border: 1px solid white;
  padding: 40px;
  box-shadow: 1px 1px 100px 10px rgba(0, 0, 0, 0.8);
}

Finally, inside of styles.scss let’s default the html and body styles:

html,
body {
  padding: 0px;
  margin: 0px;
}

This is now what we have:

Screenshot of our app

Text Marking

Let’s start by marking text that we’d like to translate within our application. We’ll be translating the application into fr and de with Google Translate providing the translations.

Add the i18n directive to all of the text that you’d like to translate:

<section>
  <article>
    <h1 i18n>Under Construction!</h1>
    <p i18n>This page is under construction.</p>
  </article>
</section>

Then we’ll make a script inside package.json that uses the Angular CLI to extract this into a messages.xlf file which contains all of our marked items:

{
  "scripts": {
    "int:extract": "ng xi18n"
  }
}

After adding this, run npm run int:extract inside of your terminal. Then, open up messages.xlf and you’ll see something similar to this:

<?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="48a16ab522feaff81571155668deb1a4304291f4" datatype="html">
        <source>Under Construction!</source>
        <context-group purpose="location">
          <context context-type="sourcefile">app/app.component.html</context>
          <context context-type="linenumber">3</context>
        </context-group>
      </trans-unit>
      <trans-unit id="84c778d7a95cb5dc26c9cc9feb5b7abb4d295792" datatype="html">
        <source>This page is under construction.</source>
        <context-group purpose="location">
          <context context-type="sourcefile">app/app.component.html</context>
          <context context-type="linenumber">4</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>

For each item that needs translating (i.e. has the i18n directive), a trans-unit will be created.

<trans-unit id="48a16ab522feaff81571155668deb1a4304291f4" datatype="html">
  <source>Under Construction!</source>
  <context-group purpose="location">
    <context context-type="sourcefile">app/app.component.html</context>
    <context context-type="linenumber">3</context>
  </context-group>
</trans-unit>

We can also use this structure to provide more information about the translation. This is useful if you’re getting each message translated by a third party and want to provide more information.

Inside of app.component.html, update the i18n items with a description:

<article>
  <h1 i18n="Title for the under construction card">Under Construction!</h1>
  <p i18n="A description for the under construction card.">This page is under construction.</p>
</article>

You can further add context to this by using the | seperator. This gives an item meaning and each item with the same meaning will have the same translation.

<section>
  <article>
    <h1 i18n="Card Header|Title for the under construction card">Under Construction!</h1>
    <p i18n="Card Descritpion| A description for the under construction card.">This page is under construction.</p>
  </article>
</section>

We can also give each i18n item an ID by using @@ to enforce persistence when we generate our translations:

<article>
  <h1 i18n="Card Header|Title for the under construction card@@constructionHeader">Under Construction!</h1>
  <p i18n="Card Descritpion|A description for the under construction card.@@constructionDescription">This page is under construction.</p>
</article>

Let’s build our translations once again:

$ npm run int:extract

Our items will now be updated with the id, meaning and description:

<body>
  <trans-unit id="constructionHeader" datatype="html">
    <source>Under Construction!</source>
    <context-group purpose="location">
      <context context-type="sourcefile">app/app.component.html</context>
      <context context-type="linenumber">3</context>
    </context-group>
    <note priority="1" from="description">Title for the under construction card</note>
    <note priority="1" from="meaning">Card Header</note>
  </trans-unit>
  <trans-unit id="constructionDescription" datatype="html">
    <source>This page is under construction.</source>
    <context-group purpose="location">
      <context context-type="sourcefile">app/app.component.html</context>
      <context context-type="linenumber">4</context>
    </context-group>
    <note priority="1" from="description">A description for the under construction card.</note>
    <note priority="1" from="meaning">Card Descritpion</note>
  </trans-unit>
</body>

Translations

Now that we have a messages.xlf file that contains all of the items we want to translate, we can drag this into a src/locales folder and make the respective messages.de.xlf and messages.fr.xlf files.

At this stage, we can also update our int:extract script to handle this:

"int:extract": "ng xi18n --output-path src/locale"

French

Starting with messages.fr.xlf, let’s look to translate the messages by using target and source.

Firstly, copy everything from messages.xlf into messages.fr.xlf. We can then add a target attribute for each item, which is equal to the translation in that language.

<body>
  <trans-unit id="constructionHeader" datatype="html">
    <source>Under Construction!</source>
    <target>En construction</target>
    <context-group purpose="location">
      <context context-type="sourcefile">app/app.component.html</context>
      <context context-type="linenumber">3</context>
    </context-group>
    <note priority="1" from="description">Title for the under construction card</note>
    <note priority="1" from="meaning">Card Header</note>
  </trans-unit>

  <trans-unit id="constructionDescription" datatype="html">
    <source>This page is under construction.</source>
    <target>Cette page est en construction</target>
    <context-group purpose="location">
      <context context-type="sourcefile">app/app.component.html</context>
      <context context-type="linenumber">4</context>
    </context-group>
    <note priority="1" from="description">A description for the under construction card.</note>
    <note priority="1" from="meaning">Card Descritpion</note>
  </trans-unit>

</body>

Do the same for messages.de.xlf in Germany:


<body>

  <trans-unit id="constructionHeader" datatype="html">
    <source>Under Construction!</source>
    <target>Im Bau</target>
    <context-group purpose="location">
      <context context-type="sourcefile">app/app.component.html</context>
      <context context-type="linenumber">3</context>
    </context-group>
    <note priority="1" from="description">Title for the under construction card</note>
    <note priority="1" from="meaning">Card Header</note>
  </trans-unit>

  <trans-unit id="constructionDescription" datatype="html">
    <source>This page is under construction.</source>
    <target>Diese Seite befindet sich im Aufbau</target>
    <context-group purpose="location">
      <context context-type="sourcefile">app/app.component.html</context>
      <context context-type="linenumber">4</context>
    </context-group>
    <note priority="1" from="description">A description for the under construction card.</note>
    <note priority="1" from="meaning">Card Descritpion</note>
  </trans-unit>

</body>

Locale builds

Great! We’ve now got versions of our application that are translated based on locale.

We can use the Angular CLI to generate specific builds for each locale that we want to support.

Head over to angular.json and inside of the build settings add the configurations. I’ve omitted most of the boilerplate that already exists.

{
  "projects": {
    "AngularInt": {
      "architect": {
        "build": {
          "configurations": {
            "fr": {
              "aot": true,
              "outputPath": "dist/under-construction-fr/",
              "i18nFile": "src/locale/messages.fr.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "fr",
              "i18nMissingTranslation": "error"
            },
            "de": {
              "aot": true,
              "outputPath": "dist/under-construction-de/",
              "i18nFile": "src/locale/messages.de.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "de",
              "i18nMissingTranslation": "error"
            }
          }
        }
      }
    }
  }
}

We can also update configurations inside of serve to allow us to serve the fr and de folders. Once again, I’ve kept this brief:

{
"serve": {
  "configurations": {
    "production": {
      "browserTarget": "AngularInt:build:production"
    },
    "fr": {
      "browserTarget": "AngularInt:build:fr"
    },
    "de": {
      "browserTarget": "AngularInt:build:de"
    }
  }
}

We can now make some more scripts inside of package.json which include the ability to build and serve our new locales:

{
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "start:fr": "ng serve --configuration=fr",
    "start:de": "ng serve --configuration=de",
    "build": "ng build",
    "build:fr": "ng build --configuration=fr",
    "build:de": "ng build --configuration=de",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "int:extract": "ng xi18n --output-path src/locale"
  }
}

We can start all of our projects by running the following in the terminal:

$ npm run start
$ npm run start:fr -- --port=4201
$ npm run start:de -- --port=4202

This gives us our application three times, running in a different language each:

Tada!

Et Voilà! 🇫🇷🇩🇪

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#