Maintaining Multi-language Angular Applications with i18n
Tags: angular, javascript, webdev
Published: 2021-04-12 08:55:04

**Angular i18n and the localizing of applications had an overhaul with version 9**, enabled by the new rendering engine Ivy. In this article, we **take a closer look at how this built-in package of Angular now works**, while pointing out the benefits and drawbacks we find.

We then set up an application with Angular internationalization and go through the complete process from marking texts for translation, extracting them to translation files, and how we manage these files to get the application deployed and maintained while keeping users all over the world happy with our translations.

*Illustration by [Vero Karén](https://twitter.com/VeroIsabellaK)*

## Internationalization and localization

It’s easy to get confused with the terms **internationalization (i18n)** and **localization (i10n),** and where to draw the line between them. *Internationalization* is the process of designing your application so that it can be adapted to different locales around the world while *localization* is the process of building the versions of the applications to different locales.

Together they help us in adapting software to different languages and local variations in the look and feel expected by the target audience.

## How localization works with Ivy

The new localization process of [Angular Ivy](https://angular.io/guide/ivy) is based on the concept of [tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). Tags allow you to parse template literals with a function. The tag used here is the global identifier `$localize`. Instead of translating the strings, the Ivy template compiler converts all template text marked with `i18n` attributes to `$localize` tagged strings.

So when we add:

```html
<h1 i18n>Hello World!</h1>
```

It will be compiled to `$localize` calls and somewhere in the compiled code we will be able to find:

```js
$localize`Hello World!`
```

The way the *tagged template* works is that you put the function that you want to run against the string before the template. Instead of `function()`, you have `function`` ` or as in this case `$localize`` `.

When this step is done we have two choices:

* **compile-time inlining**: the [`$localize`](https://angular.io/api/localize/init/$localize) tag is transformed at compile time by a transpiler, removing the tag and replacing the template literal string with the translation.

* **run-time evaluation**: the [`$localize`](https://angular.io/api/localize/init/$localize) tag is a run-time function that replaces the template literal string with translations loaded at run-time.

In this article, we use *compile-time inlining* to achieve our goals. At the very end of the build process, we run a step for the translation files by providing an option flag to get a localized application for the languages. Since we are doing the translations compile-time we get one application per locale.

At the end of the article, we take a further look into *run-time evaluation*.
> Because the application does not need to be built again for each locale, the build process is much faster than before v9 of Angular.

![You can read more about this in [Angular localization with Ivy](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075767519/BbSs51O0K.html) from where this picture is.](https://cdn-images-1.medium.com/max/2696/0*GMtohFA2nbkDKzpB.png)*You can read more about this in [Angular localization with Ivy](https://blog.angular.io/angular-localization-with-ivy-4d8becefb6aa) from where this picture is.*

Now that we understand the process of building the application we start to get an understanding of what it entails.

## The good and the bad

The standard Angular internationalization and localization are designed to produce one compiled application per language. By doing this we get optimal performance since there is no overhead of loading translation files and compiling them at run-time. But, this also means that each language has to be deployed to a separate URL:

```
www.mydomain.com/en
www.mydomain.com/nb
www.mydomain.com/fi
```

This means we need to do a bit more set up on our webserver. A limitation with `ng serve` is that it only works with one language at a time and to run different languages also needs some configuration. To run all languages locally we need to use a local webserver. We look into how we do all this in this article.

Angular i18n uses **XLIFF** and **XMB** formats that are XML-based, more verbose formats than JSON. But since these files are used at compile-time it doesn’t matter. It makes sense to use JSON when we load the translation files at run-time to keep the file sizes smaller. The formats chosen for the built-in i18n are used by translation software which helps us with our translations as we will see.

The number one drawback that people find with this solution is that you need to reload the application when you switch languages. But, is this really going to be a problem for you? People usually switch languages once if ever. And that couple of seconds it takes to reload applications will not be a problem.

Having one bundle per language is not a problem for a web SPA other than that you have to configure your web server for this. But for standalone apps, this means you got to make the user download every translated bundle, or distribute a different app for every version.

It’s important to understand your requirements before deciding which route to take.

## Transloco

If the standard Angular i18n doesn’t give you what you want then the best alternative today in my opinion is [Transloco](https://ngneat.github.io/transloco/). It’s being actively maintained and has an active community. It will get you up and running faster and is more flexible than the built-in solution. Since Transloco is runtime translation you have just `www.mydoman.com` and can change localization on the fly.

So, before choosing which way to go in such a fundamental choice you should check Transloco out to see if it would be a better fit for you.

OK, enough technicalities let’s see some code!

## Add localize to Angular project

`@angular/localize` package was [released with Angular 9](https://blog.angular.io/version-9-of-angular-now-available-project-ivy-has-arrived-23c97b63cfa3#b939) and supports i18n in Ivy applications. This package requires a global [`$localize`](https://angular.io/api/localize/init/$localize) symbol to exist. The symbol is loaded by importing the [`@angular/localize/init`](https://angular.io/api/localize/init) module.

To add the localization features provided by Angular, we need to add the `@angular/localize` package to our project:

```
ng add @angular/localize
```

This command:

* Updates `package.json` and installs the package.

* Updates `polyfills.ts`to import the `@angular/localize` package.

If you try using i18n without adding this package you get a self-explanatory error message reminding us to run `ng add @angular/localize`.

## Translating templates

To translate templates in our application, we need first to prepare the texts by marking them with the `i18n` attribute.
> [i18n](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization) is a custom attribute from the [WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) API. It’s recognized by Angular tools and compilers. During the compilation, it is removed, and the tag content is replaced with the translations.

We mark the text like this:

```html
<span i18n>Welcome</span>
```

This `<span>` tag is now marked and ready for the next step in the translation process.

## Translating TypeScript files
> NB! You need Angular 10.1 or later to extract strings from source code (.ts) files.

It’s not only our templates that need to be translated. Sometimes we have code in our TypeScript files that also need a translation. To localize a string in the source code, we use the `$localize` template literal:

```js
title = $localize`My page`;
```

Note that [template literals](https://michael-karen.medium.com/getting-started-with-modern-javascript-template-literals-d72f25511ab5) use the backtick character instead of double or single quotes.

## Extracting texts

When our application is prepared to be translated, we can use the [extract-i18n](https://angular.io/cli/extract-i18n) command to extract the marked texts into a **source language file** named `messages.xlf`.

The command options we can use are:


* `--output-path`: Change the location of the source language file.

* `--outFile`: Change the file name.

* `--format`: Change file format. Possible formats are [XLIFF 1.2](https://en.wikipedia.org/wiki/XLIFF) (default), XLIFF 2, and [XML Message Bundle (XMB)](http://cldr.unicode.org/development/development-process/design-proposals/xmb).

Running this command from the root directory of the project:

```
ng extract-i18n
```

We get the `messages.xlf` file looking like this:

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en-US" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="3492007542396725315" datatype="html">
        <source>Welcome</source>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">7</context>
        </context-group>
      </trans-unit>
      <trans-unit id="5513198529962479337" datatype="html">
        <source>My page</source>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.ts</context>
          <context context-type="linenumber">9</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>
```

We can see that we have the texts “Welcome” and “My page” in the file but what does it all mean?

* `trans-unit` is the tag containing a single translation. `id` is a translation identifier that `extract-i18n` generates so don’t modify it!

* `source` contains translation source text.

* `context-group` specifies where the given translation can be found.

* `context-type="sourcefile"` shows the file where translation is from.

* `context-type="linenumber"` tells the line of code of the translation.

Now that we have extracted the source file, how do we get files with the languages we want to translate?

## Create translation files

After we have generated the `messages.xlf` file, we can add new languages by copying it and naming the new file accordingly with the associated locale.

To store Norwegian translations we rename the copied file to `messages.nb.xlf`. Then we send this file to the translator so that he can do the translations with an XLIFF editor. But, let’s not get ahead of us and first do a manual translation to get a better understanding of the translation files.

### Translating files manually

Open the file and find the `<trans-unit>` element, representing the translation of the `<h1>` greeting tag that was previously marked with the `i18n` attribute. Duplicate the `<source>...</source>` element in the text node, rename it to `target`, and then replace its content with the Norwegian text:

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en-US" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="3492007542396725315" datatype="html">
        <source>Welcome</source>
        <target>Velkommen</target>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.html</context>
          <context context-type="linenumber">7</context>
        </context-group>
      </trans-unit>
      <trans-unit id="5513198529962479337" datatype="html">
        <source>my page</source>
        <context-group purpose="location">
          <context context-type="sourcefile">src/app/app.component.ts</context>
          <context context-type="linenumber">9</context>
        </context-group>
      </trans-unit>
    </body>
  </file>
</xliff>
```

This is all that there is to it to add the translations to the files. Let’s see how we do it with an editor.

### Translating files with an editor

Before we can use an editor, we need to provide the translation language. We can do this by adding the `target-language` attribute for the file tag so that translation software can detect the locale:

```xml
<file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="nb">
```


Let’s open this file in a translation tool to see what we are working with. I’m using the free version of [PoEdit](https://poedit.net/) in this article:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075769143/GoAaKAWlq.png)

This looks much easier to work with than the manual way. We even get some suggestions for translations. Let’s translate “my page” and save the file. If we then open `messages.nb.xlf` we can see that it has added the translation in a target block like when we did it manually:

```xml
<source>My page</source>
<target state="translated">Min side</target>
```


We see that it added `state="translated"` to the target tag. This is an optional attribute that can have the values `translated`, `needs-translation`, or `final`. This helps us when using the editor to find the texts that are not yet translated.

This is a great start but before we try out the translations in our application, let’s see what more we can do by adding more information into the box in the screenshot named “Notes for translators”.

## Notes for translators

Sometimes the translator needs more information about what they are translating. We can add a **description** of the translation as the value of the i18n attribute:

```html
<span i18n="Welcome message">Welcome</span>
```

We can add even more context to the translator by adding the **meaning** of the text message. We can add the *meaning* together with the *description* and separate them with the `|` character: `<meaning>|<description>`. In this example we might want to let the translator know that this welcome message is located in the toolbar:

```html
<span i18n="toolbar header|Welcome message">Welcome</span>
```

The last part that we can add to the value of the `i18n` attribute is an ID by using `@@`. Be sure to define unique custom ids. If you use the same id for two different text messages, only the first one is extracted, and its translation is used in place of both original text messages.

Here we add the ID `toolbarHeader`:

```html
<span i18n="toolbar header|Welcome message@@toolbarHeader">Welcome</span>
```


If we don’t add an ID for the translation, Angular will generate a random ID as we saw earlier. Running `ng extract-i18n` again we can see that the helpful information has been added to our translation unit:

```xml
<trans-unit id="toolbarHeader" datatype="html">
  <source>Welcome</source>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">7</context>
  </context-group>
  <note priority="1" from="description">Welcome message</note>
  <note priority="1" from="meaning">toolbar header</note>
</trans-unit>
```

* There are now a couple of `note` tags that provide the translation `description` and `meaning` and the `id` is no longer a random number.

If we copy these to the `messages.ng.xlf` file and open it in PoEdit we see that all these are now visible in “Notes for translators”:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075770919/WrcgDKyAx.png)

## Providing context in TypeScript files

Like with Angular templates you can provide more context to translators by providing `meaning`, `description`, and `id` in TypeScript files. The format is the same as used for `i18n` markers in the templates. Here are the different options as found in the [Angular Docs](https://angular.io/api/localize/init/$localize#description):

```js
$localize`:meaning|description@@id:source message text`;
$localize`:meaning|:source message text`;
$localize`:description:source message text`;
$localize`:@@id:source message text`;
```

Adding an `id` and `description` to our title could look like this:

```js
title = $localize`:Header on first page@@firstPageTitle:My page`;
```

If the template literal string contains expressions, you can provide the placeholder name wrapped in `:` characters directly after the expression:

```js
$localize`Hello ${person.name}:name:`;
```

## Specialized use cases

There are some specialized use cases for translations that we need to look at. **Attributes** can easily be overlooked but are also important to translate, not least for accessibility.

Different languages have different pluralization rules and grammatical constructions that can make translation difficult. To simplify translation, we can use `plural` to mark the uses of plural numbers and `select` to mark alternate text choices.

### Attributes

Apart from the usual suspects of HTML tags, we need to also be aware that we need to translate HTML attributes. This is especially important when we are making our applications accessible to all people.

Let’s take the example of an `img` tag. People using a screen reader would not see the picture but instead, the `alt` attribute would be read to them. For this reason and others, provide a useful value for `alt` whenever possible.

```html
<img [src]="logo" alt="Welcome logo" />
```

To mark an attribute for translation, add `i18n-` followed by the attribute that is being translated. To mark the `alt` attribute on the `img` tag we add `i18n-alt`:

```html
<img [src]="logo" i18n-alt alt="Welcome logo" />
```

In this case, the text “Welcome logo” will be extracted for translation.
> You can also assign a meaning, description, and custom ID with the 
`i18n-attribute="<meaning>|<description>@@<id>"` syntax.

### Plurals

Pluralization rules between languages differ. We need to account for all potential cases. We use the `plural` clause to mark expressions we want to translate depending on the number of subjects.

For example, imagine we do a search and want to show how many results were found. We want to show “nothing found” or the number of results appended with “items found”. And of course, let’s not forget about the case with only one result.

The following expression allows us to translate the different plurals:

```html
<p i18n>
{itemCount, plural, =0 {nothing found} =1 {one item found} other {{{itemCount}} items found}}
</p>
```

* `itemCount` is a property with the number of items found.

* `plural` identifies the translation type.

* The third parameter lists all the possible cases (0, 1, other) and the corresponding text to display. Unmatched cases are caught by `other`. Angular supports more categories [listed here](https://angular.io/guide/i18n#mark-plurals).

When we translate plural expression we have two trans units: One for the regular text placed before the plural and one for the plural versions.

### Alternates

If your text depends on the value of a variable, you need to translate all alternatives. Much like `plural`, we can use the `select` clause to mark choices of alternate texts. It allows you to choose one of the translations based on a value:

```html
<p i18n>Color: {color, select, red {red} blue {blue} green {green}}</p>
```

Based on the value of `color` we display either “red”, “blue”, or “green”. Like when translating plural expressions we get two trans units:

```xml
<trans-unit id="7195591759695550088" datatype="html">
  <source>Color: <x id="ICU" equiv-text="{color, select, red {red} blue {blue} green {green}}"/></source>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">12</context>
  </context-group>
</trans-unit>
<trans-unit id="3928679011634560837" datatype="html">
  <source>{VAR_SELECT, select, red {red} blue {blue} green {green}}</source>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">12</context>
  </context-group>
</trans-unit>
```

The editors understand these units and help us with the translations:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1618075772614/avS2wCzQV.png)

### Interpolation

Let’s combine a welcome message the `title` property:

```html
<h1 i18n>Welcome to {{ title }}</h1>
```


This places the value of the `title` variable that we earlier translated in the text. When we extract this text we see how the interpolation is handled:

```xml
<source>Welcome to <x id="INTERPOLATION" equiv-text="{{ title }}"/></source>
```

For the translation the `<x.../>` stays the same for the target language:

```xml
<target>Velkommen til <x id="INTERPOLATION" equiv-text="{{ title }}"/></target>
```


And that’s the last example of translations that we are looking at. Now, let’s see how we can get this applications up and running with our new language!

## Configuring locales

To be able to run our application in many languages we need to define the locales in the build configuration. In the `angular.json` file, we can define locales for a project under the `i18n` option and `locales`, that maps locale identifiers to translation files:

```json
"projects": {
  "i18n-app": {
    "i18n": {
      "sourceLocale": "en-US",
      "locales": {
        "nb": "messages.nb.xlf"
      }
   }
}
```

Here we added the configuration for the Norwegian language. We provide the path for the translation file for the locale `"nb"`. In our case, the file is still in the root directory.

The `sourceLocale` is the locale you use within the app source code. The default is `en-US` so we could leave this line out or we could change it to another language. Whatever value we use here is also used to build an application together with the `locales` we define.

To use your locale definition in the build configuration, use the `"localize"` option in `angular.json` to tell the CLI which locales to generate for the build configuration:

* Set `"localize"` to `true` for *all* the locales previously defined in the build configuration.

* Set `"localize"` to an array of a subset of the previously-defined locale identifiers to build only those locale versions.

The development server only supports localizing a single locale at a time. Setting the `"localize"` option to `true` will cause an error when using `ng serve` if more than one locale is defined. Setting the option to a specific locale, such as `"localize": ["nb"]`, can work if you want to develop against a specific locale.

Since we want to be able to `ng serve` our application with a single language, we create a custom locale-specific configuration by specifying a single locale in `angular.json` as follows:

```json
"build": {
  "configurations": {
    "nb": {
      "localize": ["nb"]
    }
  }
},
"serve": {
  "configurations": {
    "nb": {
      "browserTarget": "ng-i18n:build:nb"
    }
  }
}
```

With this change we can serve the Norwegian version of the app and make sure the translations are working by sending in `nb` to the `configuration` option:

```bash
ng serve --configuration=nb
```

We can also build the app with a specific locale:

```bash
ng build --configuration=production,nb
```

Or with all the locales at once:

```bash
ng build --prod --localize
```

In other words, it’s more flexible to configure it the way we did but we could also have just set `localize` and `aot` to true and be done with it.

## Run multiple languages locally

For performance reasons, running `ng serve` only supports one locale at a time. As we saw earlier we can serve the specific languages by sending in the locale to the `configuration` option. But, how can we run the application with all the configured languages?

### Multiple languages

To run all languages simultaneously we need first to build the project. We can build applications with the locales defined in the build configuration with the `localize` option:

```bash
ng build --prod --localize
```

When the build is localized and ready we need to set up a local webserver to serve the applications. Remember we have one application per language, which is what makes this a bit more complex.

In [Angular Docs](https://angular.io/guide/i18n#configuring-servers), there are a couple of examples of server-side code that we can use.

### Nginx

To get our application up and running we need to:

1. Install [Nginx](https://www.nginx.com/resources/wiki/start/)

1. Add config from [Angular Docs](https://angular.io/guide/i18n#nginx) to `conf/nginx.conf`

1. Build our applications

1. Copy applications to the folder defined in `root` in `nginx.conf`.

1. Open browser in `localhost`

The port is set in `listen` and is normally set to 80. You change languages by changing the URL. We should now see our Norwegian application at `localhost/nb`.

Here is an example of the `nginx.conf` file:

```nginx
events{}
http {
  types {
    module;
  }
  include /etc/nginx/mime.types;

  # Expires map for caching resources
  map $sent_http_content_type $expires {
    default                    off;
    text/html                  epoch;
    text/css                   max;
    application/javascript     max;
    ~image/                    max;
  }

  # Browser preferred language detection
  map $http_accept_language $accept_language {
    ~*^en en;
    ~*^nb nb;
  }

  server {
	  listen       80;
    root         /usr/share/nginx/html;

    # Set cache expires from the map we defined.
    expires $expires;

    # Security. Don't send nginx version in Server header.
    server_tokens off;

    # Fallback to default language if no preference defined by browser
    if ($accept_language ~ "^$") {
      set $accept_language "nb";
    }

    # Redirect "/" to Angular app in browser's preferred language
    rewrite ^/$ /$accept_language permanent;

    # Everything under the Angular app is always redirected to Angular in the correct language
    location ~ ^/(en|nb) {
      try_files $uri /$1/index.html?$args;
      
      # Add security headers from separate file
      include /etc/nginx/security-headers.conf;
    }

    # Proxy for APIs.
    location /api {
      proxy_pass https://api.address.here;
    }
  }
}
```

If we use Nginx in production, it makes sense to also test our application locally with it.

## Deploy to production

If you are using Nginx in production, then you already have the language configuration setup. If not, you need to find out what changes you need for your particular server configuration.

We have to take into consideration if we are running the application locally or in production. We can do this by using [`isDevMode`](https://angular.io/api/core/isDevMode), which returns whether Angular is in development mode:

```js
isDevMode() ? '/' : `/${locale}/`;
```

So, when we are running the application locally with `ng serve` we don’t add the locale to the URL as we do when we have localized the application in the production build.

## Maintaining the application

Usually, when the application has been deployed it’s time to end the article. This time I wanted to address a few more things before ending. Let’s start by looking into what challenges we run into when going into maintenance mode.

The biggest challenge is the handling of the translation files. We need to make sure that the marked texts find their way to the translators and back to the application before it’s deployed. To help with this we need to find a way to **automate** the generation of translation files and get **notified** when we have missing translations.

### Generating the translation files

It’s not sustainable to keep merging the translation files manually. We need some automation! To implement this, I’m using a free tool called [Xliffmerge](https://github.com/martinroob/ngx-i18nsupport/tree/master/projects/xliffmerge).
> Since this tool has old Angular versions as `peerDependencies` we need to use `--legacy-peer-deps` if we are using a new version of NPM (v7) that would otherwise fail on installation.

The documentation for Xliffmerge is targeting older versions of Angular, but after some experimentation, I found it enough to install the `@ngx-i18nsupport/tooling` package:

```bash
npm install -D @ngx-i18nsupport/tooling --legacy-peer-deps
```

Note that `-D` installs to `devDependencies`, and for use in a CI pipeline, you should omit it to use in `dependencies`.

Then we can add new languages to the configurations in `angular.json` under `projects -&gt; projectName -&gt; architect -&gt; xliffmerge`.

```json
"xliffmerge": {
  "builder": "@ngx-i18nsupport/tooling:xliffmerge",
  "options": {
    "xliffmergeOptions": {
      "defaultLanguage": "en-US",
      "languages": ["nb"]
    }
  }
}
```

After adding new translations, we can extract them and migrate them to our translation files by running this script:

```bash
ng extract-i18n && ng run projectName:xliffmerge
```

We get a couple of warnings running the script which tells us its working!

```
WARNING: merged 1 trans-units from master to "nb"
WARNING: please translate file "messages.nb.xlf" to target-language="nb"
```

After this, you can distribute the language files to the translators. And when the translations finish, the files need to be merged back into the project repository.
> Just a word of caution that this library was not being actively maintained at the time of this writing, so you might want to look into other options. There is an Angular [issue on merging translated files](https://github.com/angular/angular/issues/37655). Go and upvote it if you think this is something that we need!

### Missing Translations

Another way to make sure the translations are valid is to get noticed if translations are missing. By default, the build succeeds but generates a warning of missing translations. We can configure the level of the warning generated by the Angular compiler:

* `error`: An error message is displayed, and the build process is aborted.

* `warning` (default): Show a Missing translation warning in the console or shell.

* `ignore`: Do nothing.

Specify the warning level in the options section for the build target of your Angular CLI configuration file, `angular.json`. The following example shows how to set the warning level to error:

```json
"options": {
  "i18nMissingTranslation": "error"
}
```

If you run the application and no translation is found, the application displays the source-language text. We have to make a decision here on how important the translations are. If they are crucial then we should break the build to make sure we get all translations delivered.

## Format data based on locale

Languages are not the only thing to take into consideration when localizing applications. A couple of the more obvious things we need to think about is how we present dates and numbers to our local customers.

In Angular, we provide the [`LOCALE_ID`](https://angular.io/api/core/LOCALE_ID) token to set the locale of the application and register locale data with [`registerLocaleData()`](https://angular.io/api/common/registerLocaleData). When we use the `--localize` option with `ng build` or run the `--configuration` flag with `ng serve`, the Angular CLI automatically includes the locale data and sets the `LOCALE_ID` value.

With the `LOCALE_ID` set to the correct locale, we can use the built-in [pipes](https://angular.io/guide/glossary#pipe) of Angular to format our data. Angular provides the following pipes:

* [`DatePipe`](https://angular.io/api/common/DatePipe): Formats a date value.

* [`CurrencyPipe`](https://angular.io/api/common/CurrencyPipe): Transforms a number to a currency string.

* [`DecimalPipe`](https://angular.io/api/common/DecimalPipe): Transforms a number into a decimal number string.

* [`PercentPipe`](https://angular.io/api/common/PercentPipe): Transforms a number to a percentage string.

For example, `{{myDate | date}}` uses `DatePipe` to display the date in the correct format. We can also use the pipes in TypeScript files as long as we provide them to the module.

## Runtime translations

When we run `ng serve --configuration=xx` or `ng build --localize` then the application is compiled and translated before we run it. However, if we don’t tell Angular to localize our application, then the `$localize` tags are left in the code, and it’s possible to instead do the translation at runtime.

This means that we can ship a single application and load the translations that we want to use before the application starts. There is a function [`loadTranslations`](https://angular.io/api/localize/loadTranslations) in `@angular/localize` that can be used to load translations, in the form of key/value pairs, before the application starts.

Since the translations have to be called before any module file is imported, we can put it in `polyfills.ts`. You could also use it in `main.ts` by using a dynamic `import(...)` for the module.

Here is an example of using `loadTranslations` in `polyfills.ts`:

```ts
import '@angular/localize/init';
import { loadTranslations } from '@angular/localize';

loadTranslations({
  'welcome': 'Velkommen'
});
```

Note that the outcome of this is effectively the same as translation at compile-time. The translation happens only once If you want to change the language at runtime then you must restart the whole application. Since `$localize` messages are only processed on the first encounter, they do not provide dynamic language changing without refreshing the browser.

The main benefit is allowing the project to deploy a single application with many translation files. The documentation on this part is still lacking, but hopefully, we get [official documentation](https://github.com/angular/angular/issues/37563) on how to best work with `loadTranslations` and `$localize`. There are 3rd party libraries like [Soluling](https://github.com/soluling/I18N/tree/master/Library/Angular) out there trying to bridge the gaps.

If a dynamic and runtime-friendly solution is what you are looking for, then you should use [Transloco](https://ngneat.github.io/transloco/).

## Conclusion

We started this article by looking into how the new Ivy engine changed the i18n and localizing of applications with Angular. We looked into what benefits and drawbacks this entails and if and when we should use alternative solutions.

We then looked into adding the built-in package to a solution and how we mark texts for translation. We learned how to configure the application for localization and added tooling to manage our translation files. When we used an editor for translating, we saw how adding context to translations helps.

Finally, after configuring and translating the application, we set up a web server to serve our application both locally and in production.

There are many parts to localizing an application and I hope that after reading this article, you have a better understanding of how you can create and manage multi-language applications with Angular.

### Resources

* [Angular Docs](https://angular.io/guide/i18n)

* [Angular localization with Ivy](https://blog.angular.io/angular-localization-with-ivy-4d8becefb6aa) by [Pete Bacon Darwin](https://medium.com/@petebd)

* [Internationalization with @angular/localize](https://blog.ninja-squad.com/2019/12/10/angular-localize/) by [Cédric Exbrayat](https://twitter.com/cedric_exbrayat)