Angular2 i18n native support

Changelog

2017-02-23

  • Upgrade to angular 2.4.8 and webpack 2.2.1
  • Production sources compression
  • i18n npm tasks added

Overview

Angular2 offers i18n native support. See following angular blog post. They don't provide much info, so let's see how to do it...

Project

Source code

Source code for following article can be found in GitHub:
https://github.com/alber999/angular2-webpack-starter

It's an Angular 2.4.8 webpack starter pack with native i18n support plus custom i18n management tasks

Environment

  • node 6.9.2
  • npm 3.10.9
  • angular 2.4.8
  • compiler-cli 2.4.8
  • webpack 2.2.1
  • karma 1.5.0
  • typescript 2.1.5
  • gulp 3.9.1

Installation

npm install  

HTML Translation markup

Add i18n attribute to any HTML tag in your component templates and its content will be considered to be translated

<h1 i18n>WELCOME</h1>  

This is just a simple example, to see more features see: https://github.com/StephenFluin/i18n-sample

Extract i18n messages from templates

i18n labels can be extracted from templates thanks to ng-xi18n task included in compiler-cli. To run this task execute following command from project root:

node_modules/.bin/ng-xi18n  

We can create an npm task in package.json to avoid long command above

"scripts": {
    "i18n:extract": "ng-xi18n",
    ...

Then run

npm run i18n:extract  

You have to be careful when writing HTML i18n tags content since ng-xi18n works generating a hash id from HTML tag content without trimming. Therefore these two tags will have different hash ids and will be different entries.

<h1 i18n>WELCOME</h1>

<h1 i18n>  
WELCOME  
</h1>  

In my opinion Angular team should fix this behavior by trimming tag content.

Once you run ng-xi18n a messages.xlf XLIFF file will be generated in project root directory. Sample:

<?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="ae89a08ab9c77434ca7b8b116498317ecac8f2d9" datatype="html">
       <source>WELCOME</source>
       <target/>
     </trans-unit>
     <trans-unit id="ecad2c91a275d1ef0b2c85f9cedc80fe2e62e976" datatype="html">
       <source>
        WELCOME
    </source>
       <target/>
     </trans-unit>
   </body>
 </file>
</xliff>  

You can see above different entries for WELCOME label because of the lack of trimming.

Create custom language i18n XLF files

In following example we want to create translation for "es" and "en"

npm run i18n:create "es, en"  

It will create following untranslated files (messages.xlf copies). Only i18n labels, no translations obviously

src/resources/i18n/messages.es.xlf  
src/resources/i18n/messages.en.xlf  

Then complete translation for each i18n label in those files

Update translations

When you change i18n labels (add, remove, rename) in html templates, you can generate i18n translations XLIFF file again (untranslated).

npm run i18n:extract  

Merge new translations with former ones:

npm run i18n:update  
  • Existent i18n labels and translations in use will be kept
  • Unused labels will be removed
  • New labels will be added

Then, update translation targets for new labels in language XLIFF files:

src/resources/i18n/messages.es.xlf  
src/resources/i18n/messages.en.xlf  

Compile translations

Now is time to generate compiled TS translation files:

npm run i18n:compile  

TS version will be generated from messages.en.xlf. It will be messages.en.ts

export const TRANSLATION_EN = `<?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="ae89a08ab9c77434ca7b8b116498317ecac8f2d9" datatype="html">
       <source>WELCOME</source>
       <target>Welcome message 1!</target>
     </trans-unit>
     <trans-unit id="ecad2c91a275d1ef0b2c85f9cedc80fe2e62e976" datatype="html">
       <source>
        WELCOME
    </source>
       <target>Welcome message 2!</target>
     </trans-unit>
   </body>
 </file>
</xliff>`;  

Set current language

To configure current language, set Angular2 providers in main.ts file:

import {TRANSLATION_EN} from "./resources/i18n/messages.en";  
... 

platformBrowserDynamic().bootstrapModule(  
    AppModule,
    {
        providers: [
            {provide: TRANSLATIONS, useValue: TRANSLATION_EN},
            {provide: TRANSLATIONS_FORMAT, useValue: "xlf"},
            {provide: LOCALE_ID, useValue: "en"}
        ]
    });

This is just a sample. Of course you can implement you own logic.

Run application

npm start  

Then open: http://docker.savethecode.angular2:8080/

Tests

npm run test  

Coverage tests

npm run test:coverage  

You can find coverage reports in coverage directory

Production sources

compression-webpack-plugin is used to compress final production sources. For example, vendor.js size in dev mode is more than 8 MB, in prod env, without compression 1 MB, with compression 230 KB

npm run build  

You can find production sources in dist directory

For testing purposes, thanks to http-server we can run following task to start in prod env with production sources:

npm run start:prod  

The trick here is to edit dist/index.html and add to polyfills and vendor js files the extension .gz. See sample:

<!DOCTYPE html>  
<html>  
<head>  
    <base href="/">
    <title>SaveTheCode Angular2 Webpack Starter</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="../public/images/favicon.ico" rel="shortcut icon" type="image/x-icon">
<link href="/app.46eeea679591594be0c5.css" rel="stylesheet"></head>  
<body>  
<my-app>Loading...</my-app>  
<script type="text/javascript" src="/polyfills.46eeea679591594be0c5.js.gz"></script>  
<script type="text/javascript" src="/vendor.46eeea679591594be0c5.js.gz"></script>  
<script type="text/javascript" src="/app.46eeea679591594be0c5.js"></script>  
</body>  
</html>  

Tips

Compiled files dir

If you want to avoid messing with JS compiled files, configure outDir in tsconfig.json pointing out to a temporary directory:

{
  "compilerOptions": {
    ...
    "outDir": "./tmp"
  }
}

Ignore XLF files

To avoid XLIFF files on compilation, add following entry in webpack.common.js:

module: {  
        rules: [
            ...
            {
                test: /\.xlf$/,
                loader: 'ignore-loader'
            }
        ]
    }

Can be translation changed at runtime?

Unfortunately this feature is not available. Translations are loaded at bootstrap, not at runtime, so you cannot change language unless you re-bootstrap again by reloading page. I18n support is still tagged as "experimental" in angular2 core. It will be improved in next releases hopefully