Skip to main content

How to use Internationalization (i18n) in Angular

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

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
Copy
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>
Copy
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);
}
Copy
Finally, inside of styles.scss let’s default the html and body styles:

html,
body {
  padding: 0px;
  margin: 0px;
}
Copy
This is now what we have:



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>
Copy
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"
  }
}
Copy
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>
Copy
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>
Copy
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>
Copy
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>
Copy
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>
Copy
Let’s build our translations once again:

$ npm run int:extract
Copy
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>
Copy
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"
Copy
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>
Copy
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>
Copy
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"
            }
          }
        }
      }
    }
  }
}
Copy
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"
    }
  }
}
Copy
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"
  }
}
Copy
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
Copy
This gives us our application three times, running in a different language each:

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...