Soluling home   Document home

Angular Localization and Internationalization

Angular Localization and Internationalization

Angular (Wikipedia) is a very popular framework to build single page applications. Soluling localization tool and service support Angular. The following chapters describe the Angular localization step by step.

Angular's online documentation contains a useful I18N topic. Read it first.

The end of the document contains links to get the full source code of the samples. After reading this document, we recommend reading a tutorial about how to use Soluling.

You need Angular 5 or later to have all the necessary features for the template localization. You need Angular 10.1 or later to get all features for the complete application localization. That's is why we recommend using Angular 10.1.5 or later. The following table shows how each Angular versions support localization.

Version Localize templates
Extract template strings
Extract additional attributes Localize source
code strings
Extract source
code strings
Load translations
on runtime
2-4
yes
-
-
-
-
5-8
yes
yes
-
-
-
9-10
yes
yes
yes
-
Use Soluling API
10.1 or later
yes
yes
yes
yes
Use Soluling API

If you use Angular 9 or later add @angular/localizer package to the project. This will turn on Angular's localization features.

ng add @angular/localize

Process

Angular applications are written in TypeScript and compiled to JavaScript files that run on a browser. Angular applications contain text in two places: in templates and source code. To localize them, you have to use a special markup in templates, and the localize function in source code.

Internationalize templates

Most of the strings that you want to localize are in templates. Templates are a piece of HTML such as the following line

<p>This is a sample</p>

To localize any element, you need to add the i18n attribute.

<p i18n>This is a sample</p>

i18n is an Angular specific attribute that is only used by Angular's development tools. On runtime, the i18n attribute is just ignored. Adding the i18n attribute makes the element value localizable. If you want to localize text that is in an attribute, you need to use an i18n-attributename attribute. For example:

<input i18n-placeholder placeholder="Type something" />

Place i18n attribute into the deepest element that contains a single meaningful text. For example, if you have

<div>
  <p>This is a sample</p>
  <p>This is another sample</p>
</div>

Place i18n attributes in each p element.

<div>
  <p i18n>This is a sample</p>
  <p i18n>This is another sample</p>
</div>

Do not place it in the div element.

<div i18n>
  <p>This is a sample</p>
  <p>This is another sample</p>
</div>

Although the above is valid in Angular, it is not recommended because the two p elements are two different strings. If you add i18n into div, the resource that gets extracted gets unnecessarily complicated to translate because it will have p tags embedded.

You can optionally set the id value to an i18n attribute.

Comments

You can attach a comment to a string. The syntax is:

<p i18n="This is a sample comment">This is a sample</p>

The above sample contains "This is a sample comment" as a comment value.

Meanings

In addition to comments, you can also include a meaning value. The syntax is:

<p i18n="sample|">This is a sample</p>

The above sample contains "sample" as a meaning value.

You can have both meaning and context.

<p i18n="sample|This is a sample comment">This is a sample</p>

A meaning is useful for two situations. The first one is that if you have the same strings two or more times in your application (even from different template or source code file), the extractor tool extracts only one instance of the string. By having only one string in the resource file, it can be translated only in one way. If two different instances require different translations, it cannot be done. This is where meaning helps. If you add a different meaning value, the extractor tool will extract separate strings even if two strings are the same. The second situation where you can use meanings is to provide a better context. Read more about context.

Interpolated strings

Interpolated strings are very common in Angular. For example, the following sample injects the name-value inside the hello pattern.

<p>Hello {{name}}</p>

Whenever the string you want to localize is not a static string but an interpolation pattern, you should add a comment that describes what the variables that are injected in are.

<p i18n="name: The name of the user">Hello {{name}}</p>

The above sample contains a comment: "name: The name of the user." Its purpose is to describe the name variable. The translator will see the comment along with the string. Without a comment the transltor will see something like this.

Select JSON

It might be difficult to enter a right translation because the translation does not know if name is a person, place or something else. With a comment she will see this.

Select JSON

The comment might help her to enter a better translation.

Pipes

Angular uses pipes to convert a non-textual value such as integers ad time to strings. For example, the following markup converts a date to a string by using the date pipe.

<p>Day of birth is {{birthDay | date}}</p>

The above markup uses the long date-time format, such as October 10th, 2003. If you want to use a short format, add a date format.

<p>Day of birth is {{birthDay | date: 'shortDate'}}</p>

If you want to convert the time instead of date use shortTime format.

<p>Start: {{now | date: 'shortTime'}}</p>

As with other interpolated strings, you should also add a comment.

<p i18n="now: Start time">Start: {{now | date: 'shortTime'}}</p>

Just like dates you use pipes to convert number into strings. Use the number pipe.

<p i18n="value: Value as a string">Value is {{value | number}}</p>

To convert a float number into a percent, use the percent pipe.

<p i18n="share: Share percent as a string">Share is {{share | percent}}</p>

Angular templates can use a special markup that supports plurals and genders.

Internationalize source code

Note! You will need Angular 9 or later to do this. Furthermore, you will need Angular 10.1 or later to extract strings from source code (.ts) files.

The following lines describe the syntax to mark source code strings for localization.

If you want to localize a string in your source code, use the $localize template literal for it. For example, if you have

name = "This is a sample";

change it to

name = $localize`This is a sample`;

Note that template literals use backtick (`) character instead of double or single quotes. Like with i18n attribute you can pass comment and meaning values. The comment syntax is:

name = $localize`:comment:This is a sample`;

The meaning syntax is:

name = $localize`:meaning|:This is a sample`;

The combined meaning-comment syntax is:

name = $localize`:meaning|comment:This is a sample`;

Use the extract tool to create a resource file

Once you have marked the string that you want to localize, it is time to extract them into a resource file. Angular supports four resource file formats: XLIFF 2.0, XLIFF 1.2, XMB/XTB, and JSON. Soluling supports them all. However, we recommend using XLIFF 2.0 if you compile language specific applications, and JSON if you use runtime translation loading.

XLIFF 2.0

To extract strings into a XLIFF 2.0 file use the following command:

ng extract-i18n --format=xlf2

Note! If you use Angular older than 10.1, drop the --ivy option.

This creates src\messages.xlf file. If you want to place it for example into src\locale directory add output-path option.

ng extract-i18n --format=xlf2 --output-path src/locale

You can make a shortcut by adding the following line into package.json.

{
  ...
  "scripts":
  {
    "i18n": "ng extract-i18n --format=xlf2 --output-path src/locale",
    ...
  },
  ...
}

Now you can run the extract tool by typing from the command line:

npm run i18n

Add the created messages.xlf to a Soluling project. Soluling will create the localizer XLIFF files (messages.*.xlf).

XLIFF 1.2

To extract strings into a XLIFF 1.2 file use the following command:

ng extract-i18n --format=xlf

Add created messages.xlf to Soluling project. Soluling will create messages.*.xlf files

XMB/XTB

This resource format is used with CLDR (Wikipedia). Specification is found here. Even it is part of CLDR no other platform but Angular uses it. To extract strings into a XMB file use the following command:

ng extract-i18n --format=xmb

Add created messages.xmb to Soluling project. Soluling will create messages.*.xtb files. Note! The original file uses .xmb file extension but the localized file use .xtb.

JSON

To extract strings into a JSON file use two commands. First, build the project.

npm run build

Then add the following value to packages.json.

{
  ...
  "scripts":
  {
    "i18nj": "node_modules/.bin/localize-extract -s dist/**/*.js -f json -o src/locale/messages.json",
    ...
  },
  ...
}

Note! On Windows you need to put the full path to the localize-extract.

Use the following command:

npm run i18nj

Add created messages.json to Soluling project. Soluling will create messages.*.json files.

Note! The extract tool of Angular 10.1 or later extracts the source code string. If you use Angular 9 or 10, the workaround is to create a dummy component or hidden element and add the same strings there. It will make the extraction tool to extract the strings. If you use Angular 8 or older, the source code string cannot be localized.

Create localized resource files

When you have extracted the Angular resource file (.xlf, .xmb or .json). It is time to create the localized resources files. You can use various ways to create localized files, but using Soluling is the easiest way to create them. If you use Soluling, follow the instructions show later in this document.

Building, running and deploying the localized application

Once you have localized resource files (either .xlf, .xtb or .json), you can build and run the application in a language you want.

You need to compile the application into each language. To build or run, you have to add a language-specific configuration that specifies the original language and the resource file for each supported language. Add the following parts into the angular.json file.

{
  ...

  "projects": {
    "my-application": {
      ...
      "i18n": {
"sourceLocale": "en-US",
"locales": {
"fi": "src/locale/messages.fi.xlf",
"de": "src/locale/messages.de.xlf",
"ja": "src/locale/messages.ja.xlf"
}
},
... } } }

Now when building, provide --localize option to build the language versions too.

ng build --prod --localize

The compiled applications are written into the dist directory. For example, dist\de directory contains the German application and the dist\ja directory contains the Japanese application.

If you want to build a single language, you need to add a language-specific part to angular.json.

{
  ...
  "projects": {
    "my-application": {
    ...
    "architect": {
      "build": {
      ...
        "configurations": {
          ...
          "fi": {
            "localize": ["fi"],
          },
          "de": {
            "localize": ["de"],
          },
          "ja": {
            "localize": ["ja"],
          }
        }
      }
      ...
      "serve": {
      ...
        "configurations": {
          ...
          "fi": {
            "browserTarget": "my-application:build:fi" 
          },
          "de": {
            "browserTarget": "my-application:build:de" 
          },
          "ja": {
            "browserTarget": "my-application:build:ja" 
          }
        }
      }
    }
  }
}

Then you can use the configuration in the serve and build commands.

ng serve -c=fi

Runtime translation loading

The default way to localize Angular application is the one described above. It means that there will be one compilation, one deployment, and one URL for each language. However, Angular 9 made it possible to have only one compilation and deployment. The standard build can serve any number of languages. However, even the runtime loading is enabled, the Angular does not contain API to load the translations. Fortunately, Soluling has implemented the code to load the resource file.

Read about the runtime translation loading from the documentation in GitHub. The source code is also available in the <data-dir>\Library\Angular directory.

Multi-patterns such as plurals, genders and select

Angular has a plural enabled markup. Use the following syntax:

<p>{cars, plural, one {I have {{cars}} car.} other {I have {{cars}} cars.} }</p>

Angular also has a gender enabled markup. Use the following syntax:

<p>{gender, select, male {{{name}} drives his car.} female {{{name}} drive her cars.} }</p>

Angular also has a select enabled markup. Use the following syntax:

<p>{sport, select, soccer {{{name}} is the best soccer player.} hockey {{{name}} is the best ice hockey player} basketball {{{name}} is the best basketball player.} }</p>

Angular allows you to chain patterns. For example, the following sample uses gender enabled markup where each part contains a plural enabled markup.

<p>I have {ski, plural, one {{{ski}} ski} other {{{ski}} skis}} and {car, plural, one {{{car}} car} other {{{car}} cars}}.</p>

Angular allows you to nest patterns. For example, the following sample uses gender enabled markup where each part contains a plural enabled markup.

<p>{gender, select, male {{dogs, plural, =0 {{{name}} will not bring his dog} one {{{name}} will bring his {{dogs}} dog} other {{{name}} will bring his {{dogs}} dogs}} }
 female {{dogs, plural, =0 {{{name}} will not bring her dog} one {{{name}} will bring her {{dogs}} dog} other {{{name}} will bring her {{dogs}} dogs}} }
 other {{dogs, plural, =0 {Nobody will bring a dog} one {Somebody will bring {{dogs}} dog} other {Somebody will bring {{dogs}} dogs}} }}</p>

Even this seems tempting to use, we advice not to use it if you can avoid it. The reason is that the markup is extremely complex. Soluling cannot use the multi pattern editor because it can only handle multiple placeholder patterns if they are chained. Try to use chained patterns instead of nesting. Nesting produces a tree like a structure that has a lot of redundant data. If you use a nested pattern, your translator will see the complete strings as a single entity. It is very challenging to translate correctly without the support of a multi pattern editor.

See <data-dir>\Samples\Angular\Patterns\Plural sample to see how to use plural enabled messages.
See <data-dir>\Samples\Angular\Patterns\Multi sample to see how to use plural enabled messages that use two plural enabled patterns in a single string.
See <data-dir>\Samples\Angular\Patterns\Gender sample to see how to use gender enabled messages.
See <data-dir>\Samples\Angular\Patterns\Select sample to see how to use select enabled messages.
See <data-dir>\Samples\Angular\Patterns\Nested sample to see how to use nested patterns.

This was the localization tool independent part of the Angular localization. The next chapter shows how to use Soluling to localize the extracted resource files.

Context

Angular's extract tool scans templates and source code to find elements marked with i18n. If found, it extracts the string and adds it to the resource file. For each item in the resource file, the extract tool assigns an id. Let's have an example. If we want the localize a text in a p element, we need to add the i18n attribute.

<p i18n>Sample</p>

It will make the extract tool to generate the following XLIFF 2.0

<unit id="1238358838717941284">
  <notes>
    <note category="location">app/app.component.ts:2</note>
  </notes>
  <segment>
    <source>Sample</source>
  </segment>
</unit>

The id attribute contains the id. It is calculated from the text value (i.e., Sample). If we change the text, the id will change.

<p i18n>sample</p>

Now the extracted item will be

<unit id="8659872080098884181">
  <notes>
    <note category="location">app/app.component.ts:2</note>
  </notes>
  <segment>
    <source>sample</source>
  </segment>
</unit>

Changing one character in the string made the complete id to change. If we had existing translations for the item, they would be lost because both context id and original value changed at the same time. Another problem is that if we have the same string in two or more locations, only one item will be extracted. This will prevent us from translating the second instance in a different way. How can we avoid these problems?

One solution is to give our context id. This can be using the @@ notation with the i18n attribute.

<p i18n="@@myContext">sample</p>

This will make the following resource item.

<unit id="myContext">
  <notes>
    <note category="location">app/app.component.ts:2</note>
  </notes>
  <segment>
    <source>Sample</source>
  </segment>
</unit>

The advantage of the solution is that the id does not change if we change the text value. This is useful, for example, when fixing a typo or making a minor change. The id only changes if we change the value in the i18n attribute. The disadvantage if the solution is that the id must be unique among all ids in our application. If the same id is reused, the second string does not extract but maps to the first one. This might cause several problems. Another solution is not to use the id but generate context id from location and text value. Unfortunately, this will be pretty similar to the id attribute solution: if the value changes, a new row will be created. A better solution is to provide a meaning value.

<p i18n="myContext|">sample</p>

Everything before | is considered as a meaning value that Soluling can use as a context value. You can also give a description (.i.e. comment) value by adding the description text after |.

<p i18n="sample|This is a comment>This is a sample</p>

The advantage of using meaning is that if we add a meaning value, the extract tool will generate a separate item even if the same string appears somewhere else in the application. This lets us translate the string in a different way.

If you forget the pipe character from i18n attribute value, then the value is considered as a description.

<p i18n="This is a comment>This is a sample</p>
<p i18n="sample>This is a sample</p>

Both above strings have a description value but no meaning value. Remember to include the pipe character!

If you use meanings, the extract tool will still generate the id, but there will be a meaning value too. Look at the following XLIFF sample. It has an id attribute, but it also has meaning and location values. If meaning values are used, Soluling uses a combination of location and meaning to get the string context. This is why any meaning value needs to be unique only among the file it is used. If you have more than one file, each file can reuse a meaning value.

<unit id="1238358838717941284">
  <notes>
    <note category="meaning">myContext</note>
    <note category="location">app/app.component.ts:2</note>
  </notes>
  <segment>
    <source>Sample</source>
  </segment>
</unit>

Soluling generates app/app.component.myContext context from above XLIFF data.

Adding a meaning value into all i18n attributes might be too difficult. In most cases, just adding i18n without any value is enough. Only if you have a string collision, you can add meaning. If the string requires any explanation, add a comment.

What are the benefits of using Soluling?

Soluling brings you many advantages that are not found in other localization tools.

At the moment, Soluling is Windows only, but we will release a web-based version in the future.

To start with Soluling, create a new Soluling project for your Angular resource file. If your file is a .xlf file, the following dialog will be shown.

Select XLIFF

Select Angular resource file and click OK.

If your file is a .json file, the following dialog will be shown.

Select JSON

Select Angular resource file and click OK.

When you create a new Soluling project, you must tell Soluling to use meaning and locations instead of extract tool generated ids. Set the Context method to Meaning value in Project Wizard.

Project wizard meaning

You can also set to from Source dialog's Options sheet.

Source dialog meaning

Translate the project and finally build it to create the localized resource files.

Fallback languages

Angular does not have a fallback resource concept. The only fallback is the original string: if the resource file does not have a value for a string, then the original string is used. However, there is no real fallback feature in Angular. This is why the fallback can be done in Soluling during the build process. To turn the fallback feature on set Localized element value that is written when there is no translation value to Fallback value in Write options sheet.

Fallback option

See <data-dir>\Samples\Angular\Fallback sample to see how to use fallback languages.

3rd party i18n libraries for Angular

Angualar has two 3rd party localization libraries:

They both originate from AngularJS that did not have any official localization solution. If you are not using 3rd party localization solutions, do not start using them. They are deprecated. If you are already using these libraries, it is recommended to switch to the official solution gradually. The official I18N has several advantages over these 3rd party solutions. If you want to keep using them, Soluling supports them, and you can use Soluling to localize their resource files.

Samples

GitHub and <data-dir>\Samples\Angular contains following Angular samples:

GitHub/Directory Description Notes
Simple A localized Angular application.
Try this first!
Runtime A localized Angular application using runtime language loading.
 
Patterns Shows how to create grammatically correct multi patterns such as plural, gender and select enabled messages. Contains following projects:
Plural Shows how to use plurals.
Multi Shows how to use two plural enabled patterns in a single string.
Nested Even this shows how to use nested patterns you should not use nested patterns because Soluling cannot use pattern editor with them.
Gender Shows how to use genders.
Select Shows how to use select values. This samples also shows how to localize strings in TypeScript code.
 
Ids Shows different ways in Soluling to get strings context.  
Fallback An application that uses fallback.  
Sport Localized application that consumes a multilingual Sport API. Learn more about the application from here.

Samples are written in Angular using Angular-CLI, Visual Studio Code, and TypeScript.

Configuring Angular Localization

You create a resource file localization project by adding an Angular resource file (e.g., D:\Sample\Sample\sample.xlf or D:\Sample\Sample\sample.xmb) into your Soluling project. You can configure how to localize your Angular resource file by selecting the item in the project tree, right-clicking, and choosing the Options menu. A source dialog appears that lets you edit the options. This source uses the following option sheets.