Internationalization in ADF

Internationalization (abbreviated to i18n) is the process of providing UI messages and captions in different human languages to make them easier for readers of those languages to understand.

ADF provides full support for i18n in apps. The process does require some extra effort in planning and designing the UI but once implemented, it is fairly straightforward to maintain.

Contents

I18n concepts

The main idea behind i18n is to avoid adding natural language text directly into the HTML. Instead, UI messages are represented by short strings known as keys. Keys are not displayed directly; they are used to look up the actual text in a list of predefined messages. A typical key/message pair might look like the following:

"CS_URL_ERROR": "Content Services address doesn't match the URL format"

Separate lists are kept for each language supported by the app, so for German, the same message would be defined as:

"CS_URL_ERROR": "Content Services-Adresse nicht im richtigen URL-Format"

Note that the key is the same in both cases. As long as the UI only ever refers to the keys then changing languages is a simple matter of changing the look-up list.

ADF support for i18n

ADF implements i18n for more than ten languages internally in the display text for components, so you can try out some simple messages without any configuration. The keys are defined in a set of files in the lib/core/i18n folder in the ADF sources.

The files are named according to standard two-letter language codes, so en.json is the look-up list for English, etc. An excerpt from en.json is shown below:

{
  "FORM": {
    "START_FORM": {
      "TITLE": "Start Form"
    },
    "PREVIEW": {
      "IMAGE_NOT_AVAILABLE": "Preview not available"
    },
    "FIELD": {
      "LOCALSTORAGE" : "Local storage",
      "SOURCE": "Select source from ",
      "UPLOAD": "UPLOAD",
      "REQUIRED": "*Required",
      ...

The hierarchical structure is referred to in the UI using the familiar "dot" notation (so FORM.START_FORM.TITLE would be the key for the "Start Form" string here). This is useful for grouping related messages and providing singular and plural versions, among other things.

The Translation service defines the get method to get the translation of a key in the current language. A simple component might contain code like this:

import { Component, OnInit } from '@angular/core';

import { TranslationService } from "@alfresco/adf-core";

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
class HomeComponent implements OnInit {

  constructor(private trans: TranslationService) { }

  translatedText: string = "";

  ngOnInit() {
    this.trans.get("FORM.START_FORM.TITLE").subscribe(translation => {
      this.translatedText = translation;
    });
  }
}

...with very simple corresponding HTML:

{{translatedText}}

In the browser, this is displayed as:

English translation text

English is used by default but you can easily change the language with the use method:

ngOnInit() {
    this.trans.use("de");

    this.trans.get("FORM.START_FORM.TITLE").subscribe(translation => {
      this.translatedText = translation;
    });
  }

The user will now see:

German translation text

Note that an unrecognized key will be returned unchanged as the "translation". If you see strings like "FORM.START_FORM.TITLE" displayed in your app then you should check you are using the key correctly.

Using the translate pipe

Using TranslationService.get is straightforward but it is often more convenient to add translation keys directly into your page's HTML. Use the translate pipe to convert a key in the page directly to the corresponding text. For example, the following will display the "Start Form" text as above but without any code or variables in the component's .ts file:

{{ "FORM.START_FORM.TITLE" | translate }}

Adding and replacing messages

The built-in translations certainly won't cover everything you will need for your app but you can easily replace them with your own lists. This enables you to add new keys and also replace the text of existing keys with your own.

To modify the default translations, you need to create local translation source files (en.json, fr.json, etc) within your application. The local files have the same basic hierarchical key:value structure as the built-in translations. You can add new keys to your local files to extend the default set or override a default translation by redefining an existing key with new message text. The default translations will be used for any keys that you don't explicitly override. For example, your local en.json might look like the following:

{
  "title": "my app",
  "LOGIN": {
     "LABEL": {
        "LOGIN": "Custom Sign In"
     }
  }
}

The Translation service page has full details of how to add custom translations, including the locations of the required files and code samples for enabling the new translations in your app.

Interpolations

Translation messages have support for interpolation (ie, including another string at a specified position within a message). This is very useful for messages whose content can change at runtime. For example, in the built-in en.json there is the CORE.PAGINATION.ITEMS_RANGE key:

  ...
"CORE": {
  ...
  "PAGINATION": {
        "ITEMS_RANGE": "Showing {{ range }} of {{ total }}",
        "ITEMS_PER_PAGE": "Items per page",
          ...
      },
    ...

The sections in curly braces are interpolation variables that you supply at runtime. You can specify them by passing an extra parameter to TranslationService.get; this is an object whose properties have the same names as the interpolation variables in the string:

this.trans.get(
      "CORE.PAGINATION.ITEMS_RANGE",
      {
        range: "1..10",
        total: "122"
      }
    ).subscribe(translation => {
      this.translatedText = translation;
    });

You can use interpolations with the translate pipe in a similar way:

{{ "CORE.PAGINATION.ITEMS_RANGE" | translate: { range: "1..10", total: "122"} }}

How the display language is selected

The locale preference in the user preferences contains the language code that will be used to display the ADF app. Since the user preferences can only be saved by the app when it runs, they will not immediately be available when the app launches for the first time. The app uses the following priorities to determine the locale language for the first launch:

  1. If the locale property is set in app.config.json then this will be used.
  2. If there is no locale property then the browser's language setting will be used instead.
  3. If neither of the first two options is available then English will be used by default.

The table below illustrates how the selection is made:

User Preferencelocale in app.config.jsonBrowser languageDefaultResult
XXXenen
XXjpenjp
Xfrjpenfr
itfrjpenit

The translation service probes the browser culture first, for example en-GB. If the en-GB.json file does not exist, the service falls back to the language id: en.

Once the locale language is determined, it is saved to the user preferences and this saved value will be used from that point on, regardless of the app.config.json and browser settings.

However, you can change the locale user preference from code using the User Preferences service and the updated value will still override any browser or app.config.json settings. ADF also provides a Language Menu component that you can add to a page to let the user set the locale preference easily. The list of available languages is defined in the app.config.json file for the app.

The translate pipe reacts automatically to a change in the locale language and immediately updates the display. However, text added via a variable set using TranslationService.get, as in the example above, will not be updated directly in this way. Instead, you will need to get a new translation and set the variable's value again explicitly from the code.

See the Language Menu component page for further details and usage examples.

Support for i18n within ADF components

Some components allow you to use translation keys in places where you would normally supply your own messages directly. For example, the Data Column component can accept a key instead of normal text to specify the column title. Consult the documentation for a component to see if it has built-in support for i18n.

See also

© 2023 Alfresco Software, Inc. All Rights Reserved.