In the following post I will show how to combine i18n translations with Ahead of Time Compilation (AoT).

i18n deals with Internationalization of content and is important for multilingual applications.

I will not cover the details of Angular's implementation of i18n since it's already covered well in the documentation.

Instead I will focus on how to combine i18n with AoT application bundles.

For the purposes of this post I have created a trivial sample application where I will show how to use i18n to display a greeting in Norwegian and English.

I am only showing the app.component for brevity, but the whole source can be found here

app.component.ts

import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: 'app.component.html' }) export class AppComponent { }

app.component.html

<h1 i18n="Greeting|A greeting to the user">Hello and welcome i18n!</h1>

As you can tell the template is annotated with i18n attributes. This is how we tell the i18n compiler that this is a template where we want to leverage i18n functionality.

The default language is English, so any English text is inlined directly in the template.

Loading Translations

Many existing translation frameworks take a standard data driven approach to translations. Based on language preferences you typically load a separate json file per language. The json file contains language “keys” that are used to look up translated text.

Basically translations are treated like regular data from any random api.

In Angular's AoT based i18n implementation things work differently.

Instead of deploying a single application and pull in json files at runtime, the approach is to deploy a separate application per language. In my example I will have two different applications. One for English and a second application for Norwegian.

Don't be scared, but this means supporting 20 languages requires 20 different versions of your application.

Luckily all the applications can be generated from a single code base. In the next section I will show you how.

English Version

The first thing we have to do is build an application based on the default language.

This is pretty straightforward since all you have to do is AoT compile the application without even worrying about i18n

AoT compile the application using a standard ngc command:

ngc -p tsconfig.json

ngc will AoT compile the application, but we still need to create an application bundle. In this example I will be using Rollup, a popular bundler.

Rollup needs some configuration in order to process the application. It's easiest to specify the necessary Rollup configurations in a configuration file. I have put the configuration in rollup-config.js

rollup-config.js

import rollup from 'rollup'; import nodeResolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; import uglify from 'rollup-plugin-uglify'; export default { entry: 'i18n/main-aot.js', format: 'iife', plugins: [ nodeResolve({jsnext: true, module: true}), commonjs({ include: 'node_modules/rxjs/**', }), uglify() ] }

The Rollup config is pretty standard. But you may have noticed that I didn't specify a dest attribute. I intentionally left that out of the config since I want to reuse it for all languages.

Instead I will pass in an override from the command line.

The Rollup process can be triggered by running:

rollup -c i18n/rollup-config.js -o i18n/dist/build-en.js

The -o setting specifies where to output the English version of the application.

It may be preferable to combine the AoT and Rollup steps into one by &&-ing them together like so:

ngc -p i18n/tsconfig.json && rollup -c i18n/rollup-config.js -o i18n/dist/build-en.js

Other languages

We now have a working English version of our application.

To generate applications for the other languages we have to make a few additional tweaks.

The first step is to generate an xlf file where we can specify translations for each language by running:

ng-xi18n -p i18n/tsconfig.json

This outputs a generic xml template that can be used to create language specific versions. In my sample I only have one version called messages.no.xlf. This is where Norwegian language translations will go.

The next step is to tell ngc to generate a new application where the English default text is overridden with Norwegian translations.

This is very similar to regular AoT compilation, but we have to pass the location of the xlf file to the compiler.

This can be by passing the following switches:

--i18nFile=./i18n/locale/messages.no.xlf –locale=no

The combined command is as follows:

ngc --i18nFile=./i18n/locale/messages.no.xlf --locale=no --i18nFormat=xlf -p i18n/tsconfig.json && rollup -c i18n/rollup-config.js -o i18n/dist/build-no.js

I also specified a different -o switch. This tells Rollup to output the Norwegian version of the application in a different location.

Application

We now have two different versions of the application. The first one supports English text and the second one supports Norwegian text.

How do you know which version to run?

Multilingual applications must have a concept of language preference. Based on the user's language preference you decided which version to serve at runtime.

The actual implementation of this depends on your tech stack. I have created a sample to illustrate a very simple approach.

My UI is super simple, but it allows the user to toggle a greeting between English and Norwegian.

In this example I am basing the language preference on a query string parameter. Based on the query string I dynamically load the correct application bundle.

My index.html ends up looking like this:

<html> <head> <link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.js"></script> </head> <body> <div class="container"> <a class="btn btn-primary btn-lg" href="i18n.html?language=en">English</a> <a class="btn btn-primary btn-lg" href="i18n.html?language=no">Norwegian</a> <p class="bg-success"> <my-app></my-app> </p> </div> <script> var params = new URLSearchParams(location.search.slice(1)); var language = params.get('language') || 'en'; var script = document.createElement('script'); script.setAttribute('src','i18n/dist/build-' + language + '.js'); document.head.appendChild(script); </script> </body> </html>

I have deployed a live demo of this here

In the demo the conditional loading was done client side. A great alternative would be to generate the src url of the application bundle on the server. Depending on your server technology you could use server rendering to resolve the url of a script tag.