Skip to content

Tutorial for using xliffmerge with angular cli

Martin Roob edited this page Mar 6, 2017 · 25 revisions

This tutorial shows in a few step, how to use xliffmerge in an Angular App, which is generated with angular-cli.

It shows the basic workflow for preparing an application for translation, translating it, running it, changing it, translating it again, running it again...

Start a new project

If you haven't done it at all, install angular-cli globally (for me, it was version 1.0.0-rc.1):

npm install -g @angular/cli@latest

Then you can create a new project:

ng new sampleapp

cd sampleapp

Prepare the app for translations

For this tutorial we assume, that your preferred language is not English, but something else, e.g. German. So you want to write your application in German and translate it to English later. This is not a hard requirement, of course you can use English as the default. I just decided to do it this way, because it is the way I do it.

So as a first step we change our application to German and additionally mark all text, that needs translation, with an i18n-Attribute. For details have a look at the Angular Cookbook about Internationalization.

Our `src/app/app.component.html now looks like this:

<h1 i18n>Meine erste I18N-Anwendung</h1>
<p i18n>Anwendung läuft!</p>

Try running it (ng serve or npm run start, then open http:\\localhost:4200 in a browser), fix the test cases, if you want (they test for 'app works!' and will fail after the change).

It looks like

Meine erste I18N-Anwendung

Anwendung läuft!

Generate a messages.xlf file

After the initial changes are made, the application now contains some i18n marked stuff and so you can extract this stuff for translation.

angular-cli has a task for this (since 1.0.0-rc-0, i guess), so you can just type

ng xi18n --output-path src/i18n --locale de

Instead of typing, it is better to add a script to your package.json, that does it for you. Then you don't have to remember the command to type. So just add the following to your package.json scripts section:

{
  [...]
  "scripts": {
    [...]
    "extract-i18n": "ng xi18n --output-path src/i18n --locale de"
  }
  [...]
}

Then you can type

npm run extract-i18n

Some words about the parameters used in the command.

--output-path spefifies the path, where the generated file will be saved. We prefer to have it under src/i18n.

--locale specifies the language, that is used in the templates (the default language). It is supported starting with Angular 4. In prior versions it is ignored and the generator always assumes your templates are written in English.

After running the command you will find a newly generated file src/i18n/messages.xlf. If you open it with a text editor, you will see, that it is an XML file containing

...
      <trans-unit id="a8f10794864e49b16224b22faaf4a86229b6c53d" datatype="html">
        <source>Meine erste I18N-Anwendung</source>
        <target/>
      </trans-unit>
      <trans-unit id="1189e6770476cd4eef2d4462a98add152b80961e" datatype="html">
        <source>Anwendung läuft!</source>
        <target/>
      </trans-unit>
...

In the next step you have to

  • create a copy of this file for every language, you want to support
  • translate it.

This is, where xliffmerge comes into play.

Generate language specific files

Change the script you added in the last section to your package.json (add the stuff && xliffmerge...):

{
  [...]
  "scripts": {
    [...]
    "extract-i18n": "ng xi18n --output-path src/i18n --locale de && xliffmerge --profile xliffmerge.json de en"
  }
  [...]
}

xliffmerge.json is a small configuration file. Create it with the following content to tell xliffmerge where the files are located. (Details about the configuration options are contained in the usage section at the home page).

{
  "xliffmergeOptions": {
    "srcDir": "src/i18n",
    "genDir": "src/i18n"
  }
}

Now run the extraction process once again:

npm run extract-i18n

It will output some warnings:

WARNING: master says to have source-language="en", should be "de" (your defaultLanguage)
WARNING: changed master source-language="en" to "de"
WARNING: please translate file "src/i18n/messages.en.xlf" to target-language="en

THe warnings tell you, that xliffmergehas corrected the language from en to de (our default language) and that you have to translate the English file.

You will now find 3 files under src/i18n

  1. messages.xlf the master file containing all the messages found in your app.

  2. messages.de.xlf the German version.

  3. messages.en.xlf the (up to now untranslated) English version.

If you open the files with a text editor, you will see

  • messages.xlf is the file Angular created for you. The <target/> elements are empty (no translation), the attribute source-language="de" was corrected by xliffmerge.
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="de" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="a8f10794864e49b16224b22faaf4a86229b6c53d" datatype="html">
        <source>Meine erste I18N-Anwendung</source>
        <target/>
...
  • messages.de.xlf contains a copy, but the <target> elements contain the same values as the source elements. There is an additional attribute state="final".
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="de" datatype="plaintext" original="ng2.template" target-language="de">
    <body>
      <trans-unit id="a8f10794864e49b16224b22faaf4a86229b6c53d" datatype="html">
        <source>Meine erste I18N-Anwendung</source>
        <target state="final">Meine erste I18N-Anwendung</target>
      </trans-unit>
      <trans-unit id="1189e6770476cd4eef2d4462a98add152b80961e" datatype="html">
        <source>Anwendung läuft!</source>
        <target state="final">Anwendung läuft!</target>
      </trans-unit>
    </body>
  </file>
</xliff>
  • messages.en.xlf is nearly the same, but the target-language is set to en and all <target> elements have an attribute state="new". This marks the fact, that you have to translate it by your own.
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="de" datatype="plaintext" original="ng2.template" target-language="en">
    <body>
      <trans-unit id="a8f10794864e49b16224b22faaf4a86229b6c53d" datatype="html">
        <source>Meine erste I18N-Anwendung</source>
        <target state="new">Meine erste I18N-Anwendung</target>
      </trans-unit>
      <trans-unit id="1189e6770476cd4eef2d4462a98add152b80961e" datatype="html">
        <source>Anwendung läuft!</source>
        <target state="new">Anwendung läuft!</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Translate your messages files

Next you have to translate messages.en.xlf by yourself. Use a translation tool, if you want. At the end, you just replace the target elements with the translated version and you change the state attribute to state="translated".

<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="de" datatype="plaintext" original="ng2.template" target-language="en">
    <body>
      <trans-unit id="a8f10794864e49b16224b22faaf4a86229b6c53d" datatype="html">
        <source>Meine erste I18N-Anwendung</source>
        <target state="translated">My first I18N application</target>
      </trans-unit>
      <trans-unit id="1189e6770476cd4eef2d4462a98add152b80961e" datatype="html">
        <source>Anwendung läuft!</source>
        <target state="translated">app works!</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Do not forget to put the translated files under configuration management, they should all be considered as source files.

Run your English version of the app

Add the following script to your package.json

{
  [...]
  "scripts": {
    [...]
    "extract-i18n": ...,
    "start-en": "ng serve --aot --i18n-file=src/i18n/messages.en.xlf --locale=en --i18n-format=xlf"
  }
  [...]
}

Run

npm run start-en

You now will see the English version of your app:

My first I18N application

app works!

Congratulations!

Change your app and do it again

In the next steps you want to add new features to your app and that includes adding some new messages.

For example change src/app/app.component.html to

...
<h1 i18n>Meine erste I18N-Anwendung</h1>
<p i18n>Anwendung läuft!</p>
<p i18n="Beschreibung der Anwendung">Diese Anwendung ist eine reine Demonstration und hat keine wirklich nutzbare Funktion.</p>
...

We just added another paragraph. And we used a description value for the i18n attribute.

Now extract the messages.xlf once again:

npm run extract-i18n

There will be some warnings again:

WARNING: master says to have source-language="en", should be "de" (your defaultLanguage)
WARNING: changed master source-language="en" to "de"
WARNING: merged 1 trans-units from master to "de"
WARNING: merged 1 trans-units from master to "en"
WARNING: please translate file "src/i18n/messages.en.xlf" to target-language="en

The first 2 are already known, the generated once again destroyed your source-language setting.

The next 2 warnings show you, that there are new messages merged in (only 1 here).

Have a look at the newly generated xlf files (if they are under version control, you can directly look at the changes done), especially the English one:

<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="de" datatype="plaintext" original="ng2.template" target-language="en">
    <body>
      <trans-unit id="a8f10794864e49b16224b22faaf4a86229b6c53d" datatype="html">
        <source>Meine erste I18N-Anwendung</source>
        <target state="translated">My first I18N application</target>
      </trans-unit>
      <trans-unit id="1189e6770476cd4eef2d4462a98add152b80961e" datatype="html">
        <source>Anwendung läuft!</source>
        <target state="translated">app works!</target>
      </trans-unit>
    <trans-unit id="fb74bbbdf6190dc80f48622910221fd07ae0c70d" datatype="html">
        <source>Diese Anwendung ist eine reine Demonstration und hat keine wirklich nutzbare Funktion.</source>
        <target state="new">Diese Anwendung ist eine reine Demonstration und hat keine wirklich nutzbare Funktion.</target>
        <note priority="1" from="description">Beschreibung der Anwendung</note>
      </trans-unit></body>
  </file>
</xliff>

You see

  • the old parts are just there, the already done parts of the translation are untouched.
  • the new parts contain the German original as translation value and are marked with state="new"
  • the description attribute is contained as a <note> element. Translation tool will show it to support the translator.

In the next step you can send the file to your translator to do the work with the new parts (or you can do that by your own). If this should take some time, you can just run the application with the partly translated file, which is better than no version at all.

This is the incompletely translated versions output:

My first I18N application

app works!

Diese Anwendung ist eine reine Demonstration und hat keine wirklich nutzbare Funktion.

After the translation is done, just run the new version.

npm run start-en

OK, you got it:

My first I18N application

app works!

This application is just for demonstration purposes. It really has no value.