How to translate your Angular 7 app with ngx-translate

How to translate your Angular 7 app with ngx-translate

In this tutorial you are going to learn:

  • How to translate your Angular 7 application
  • How to set up ngx-translate 11
  • How to update your translation files with ngx-translate-extract
  • How to edit and maintain multiple JSON files

What you are going to learn in this tutorial

Here's a small outline of the tutorial:

  • How to set up ngx-translate
  • How to update your translation files with ngx-translate-extract
  • How to edit and maintain multiple JSON files

This tutorial uses Angular 7 together with ngx-translate 11.

A older version of the tutorial covering Angular 6 is available from here: How to translate your Angular 6 app with ngx-translate

A newer version with Angular 8-11 is available from here: How to translate your Angular 11 app with ngx-translate

How to set up ngx-translate

Optional: Create a Angular 7 demo project

For this tutorial you'll start with a simple demo application. I assume that you already have basic knowledge of Angular and AngularCLI is already installed on your system. You can of course skip this step and use your own project.

npm install -g @angular/cli
ng new translation-demo
cd translation-demo
ng serve

The demo project should now be available in your web browser under the following url: http://localhost:4200.

How to add ngx-translate your Angular application

npm install @ngx-translate/core @ngx-translate/http-loader rxjs --save

The @ngx-translate/core contains the core routines for the translation: The TranslateService and some pipes.

The @ngx-translate/http-loader loads the translation files from your webserver.

Now you have to init the translation TranslateModule in your app.module.ts :

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';

// import ngx-translate and the http loader
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient, HttpClientModule} from '@angular/common/http';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,

        // configure the imports
        HttpClientModule,
        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [HttpClient]
            }
        })
    ],
    providers: [],
    bootstrap: [AppComponent]
})

export class AppModule {
}

// required for AOT compilation
export function HttpLoaderFactory(http: HttpClient) {
    return new TranslateHttpLoader(http);
}

The HttpLoaderFactory is required for AOT (ahead of time) compilation in your project.

Now switch to app.component.ts :

import {Component} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent {
    constructor(private translate: TranslateService) {
        translate.setDefaultLang('en');
    }
}

First, you have to inject TranslateService in the constructor.

The next step is to set the default language of your application using translate.setDefaultLang('en'). In a real app you can of course load language from the user's settings.

Reloading the app now shows an error in the browser console:

Failed to load resource: the server responded with a status of 404 (Not Found) http://localhost:4200/assets/i18n/en.json

This is because of the http loader which now tries to load the default language (en) from the server.

How to translate your application

The JSON translation file

Each language is stored in a separate .json file. Let's create the JSON file for the English translation: assets/i18n/en.json .

ngx-translate can read 2 JSON formats:

{
  "demo.title": "Translation demo",
  "demo.text": "This is a simple demonstration app for ngx-translate"
}

or

{
    "demo": {
        "title": "Translation demo",
        "text": "This is a simple demonstration app for ngx-translate"
    }
}

This is just a matter of personal taste. The second one (so called namespaced-json format) is more structured and gives a better overview over the translations. It's the format I prefer. It's not important which one you choose — it's easy to convert one into the other later. Please use the second format for this tutorial.

Using translations title and text are identifiers ngx-translate uses to find the translation strings.

Now edit the app.component.html

You have two choices when it comes to adding translations:

  • translation pipe — {{'id' | translate}}

  • translation directive — <element [translate]="'id'"></element>

Both options lead to the same result — it's just a matter of personal taste which one you want to use.

<div>
    <!-- translation: translation pipe -->
    <h1>{{ 'demo.title' | translate }}</h1>

    <!-- translation: directive (key as attribute)-->
    <p [translate]="'demo.text'"></p>

    <!-- translation: directive (key as content of element) -->
    <p translate>demo.text</p>
</div>

Translations with parameters

ngx-translate also supports parameters in translations. They are passed as an object, the keys can be used in the translation strings.

<!-- translation: translation pipe -->
<p>{{ 'demo.greeting' | translate:{'name':'Andreas'} }}</p>

<!-- translation: directive (key as attribute) -->
<p [translate]="'demo.greeting'" [translateParams]="{name: 'Andreas'}"></p>

<!-- translation: directive (key as content of element)-->
<p translate [translateParams]="{name: 'Andreas'}">demo.greeting</p>

And the extended translations file:

{
  "demo": {
    ...
    "greeting": "Hello {{name}}!"
  }
}

Switching languages at runtime

To switch language you'll first have to add a new JSON file for that language. Let's create a German translation for the demo: assets/i18n/de.json

{
    "demo": {
        "title": "Übersetzungs-Demo",
        "text": "Dies ist eine einfache Applikation um die Funktionen von ngx-transalte zu demonstrieren.",
        "greeting": "Hallo {{name}}!"
    }
}

Reloading the app will not show any differences — this is because you've set the default language to en in app.component.ts .

Make the following changes to the app.component.html to add a simple language switcher:

<button (click)="useLanguage('en')">en</button>
<button (click)="useLanguage('de')">de</button>

Add the following method to the app.component.ts

useLanguage(language: string) {
    this.translate.use(language);
}

Working with JSON translation files: A pain in the *****

You are a programmer, and you are right — editing a single JSON file is easy. We all do it all day long: package.json, composer.json — all fine. I sometimes forget to add or remove a comma but this is not an issue. JSON translation files are a completely different story. Why?

1. You have to keep multiple files in sync

Each change that you make to a translation ID — like adding, removing or renaming — has to be done on all language files. Yes: en.json, it.json, fr.json, de.json,... all need the same treatment.

2. You end up with differences

Sooner or later you'll end up with differences in the language files — no matter how disciplined you are. Just one small edit here... and you forget about updating fr.json and de.json...

3. You try to fix it with a diff / compare tool

Now you try to use some diff tool to find out what the differences are: Which IDs are in which file? What is missing? And guess what: You diff tools shows tons of changes...

Comparing JSON translation files in AraxisMerge

Above is a diff of 2 JSON files in AraxisMerge — which is a really good diff / merge tool. But it's simply the wrong tool for the job...

The way better solution is using a specialized editor for translation files:

Editing your JSON translation files with BabelEdit

Editing JSON files with BabelEdit

Things are easy as long as you are only working with a single translation file. But as soon as you add a second language it becomes quite hard work to maintain both files. Not to speak of 5 or more languages.

To get started with BabelEdit download it from here:

BabelEdit is a professional translation editor that works with the common web development frameworks including Angular 7 and ngx-translate. It comes with a lot of nice features that make your daily work much easier:

  • See all translations in parallel — stop switching between json files all the time

  • Spell checking for each language

  • Auto-fill translations from google translate

  • Import / Export to and from Excel / CSV / Google Spread Sheets for easy exchange with your translators

Start by selecting the ngx-translate project template:

Create an Angualar translation project with BabelEdit

Now drag & drop your assets/i18n -folder onto the main window.

Using BabelEdit to translate an Angular application using ngx-translate

BabelEdit now asks you for the languages contained in the files — making guesses from the file name. After confirming the languages start working in BabelEdit:

Using BabelEdit to translate an Angular application using ngx-translate

This is why we've created BabelEdit. It's a translation editor that can open multiple JSON files at once to work on them at the same time. Editing both translations from our example looks like this:

The left side contains a tree view with your translation ids, the right side the translations. The Approved checkbox allows you to mark translations as final. This additional information is stored in a BabelEdit project file (extension .babel).

The editor automatically reloads the files after they have been updated — e.g. from a new run of ngx-translate-extract.

BabelEdit currently used the flat JSON file format as default (because it's default in ngx-translate extract). If you use the namespaced-json format from our setup you have to change the format in the settings:

Choosing namespaced-json format for ngx-translate

You can also specify copy templates. Use these to copy a translation id from the left panel in a format that you can directly insert into your source code.

Update JSON files with ngx-translate-extract

Keeping the JSON files, and your app in sync might become a challenge for more complex applications.

The good thing: Kim Biesbjerg created a tool called ngx-translate-extract. It scans your Angular app for the use of translations and adds new translations to your JSON files.

Start by adding it to your project:

npm install @biesbjerg/ngx-translate-extract --save-dev

You can add the following lines to your package.json to make using the tool more convenient:

"scripts": {
    "extract-translations": "ngx-translate-extract --input ./src --output ./src/assets/i18n/*.json --clean --sort --format namespaced-json --marker _"
}

Let's take a look at the parameters:

  • **--input ./src **
    Sets the source directory in which to look for translations. The default is to scan all html and ts files. You can use an additional--patterns parameter to specify other file extensions.

  • --output ./src/assets/i18n/*.json
    This specifies which files to update. The example here updates all existing language files in your ./src/assets/i18n folder that end with json . To add new languages simply add a new (empty) file to the translation folder. You can also list individual files if you prefer being more precise about what to update.

  • --clean
    This option removes all translations that are not found in the source files. Usually it's a good idea to enable this to keep files consistent.

  • --sort
    Sorts the JSON files.

  • --format namespaced-json
    Creates the JSON files with the nested object structure as you used in this tutorial.

  • --marker _
    ngx-translate-extract can search your TypeScript files for strings to translate. You have to surround the strings with a marker function e.g. _('app.title'). See below.

A simple command now updates your JSON files:

npm run extract-translations

Using translatable strings in your TypeScript files

Sometimes you have to add translatable strings to your TypeScript code. ngx-translate-extract needs a way to distinguish between these strings and all the other strings in your application.

This is where the marker function comes into play. The function itself does nothing — it only passes the string as a result.

import {_} from '@biesbjerg/ngx-translate-extract/dist/utils/utils';

{
    &hellip;
    var messageBoxContent = _('messagebox.warning.text');
    &hellip;
}

You might get the following error message:

ERROR in node_modules/@biesbjerg/ngx-translate-extract/dist/cli/cli.d.ts(1,23): error TS2688: Cannot find type definition file for 'yargs'.

This is because your IDE might have inserted@biesbjerg/ngx-translate-extract instead of@biesbjerg/ngx-translate-extract/dist/utils/utils. Simply fix the import statement to get rid of the error.

You can now use the pipe or directive to display the translated string:

<div>{{ messageBoxContent | translate }}</div>

How to use a different marker function instead of _()

In some cases using _() as a marker function might lead to conflicts with other modules. The solution is to create your own function — e.g. TRANSLATE(). The function's only job is to return the string that's passed to it as parameter:

export function TRANSLATE(str: string) {
    return str;
}

This is how you use the new translate marker:

`var messageBoxContent = TRANSLATE('messagebox.warning.text');`

Also update your extract-translations command in package.json with --match TRANSLATE.

How to work with pluralization in Angular 7 and ngx-translate

Sometimes it's not enough to simply add a value to your translations. There are cases where parts or event the whole sentence has to change.

Think about the following situation: You want to display the number of images a user has uploaded.

  • No image uploaded yet.

  • One image uploaded.

  • 123 images uploaded.

Or you want to display a dynamic value:

  • My favorite color is green.

  • My favorite color is red.

  • My favorite color is blue.

The ngx-translate-messageformat-compiler is what you need now! It parses messages using the ICU syntax.

Install the plugin using the following commands:

npm install ngx-translate-messageformat-compiler messageformat@2.0.2 --save

Next you have to tell ngx-translate to use the message format compiler for rendering the translated messages in app.module.ts :

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';

// import ngx-translate and the http loader
import {TranslateCompiler, TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient, HttpClientModule} from '@angular/common/http';

// import ngx-translate-messageformat-compiler
import {TranslateMessageFormatCompiler} from 'ngx-translate-messageformat-compiler';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,

        // configure the imports
        HttpClientModule,
        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [HttpClient]
            },

            // compiler configuration
            compiler: {
                provide: TranslateCompiler,
                useClass: TranslateMessageFormatCompiler
            }
        })
    ],
    providers: [],
    bootstrap: [AppComponent]
})

export class AppModule {
}

// required for AOT compilation
export function HttpLoaderFactory(http: HttpClient) {
    return new TranslateHttpLoader(http);
}

Update app.component.html to render the new demo messages:

<h2>ngx-translate-messageformat-compiler demo</h2>

    <li translate [translateParams]="{ count: 0 }">pluralization.things</li>
    <li translate [translateParams]="{ count: 1 }">pluralization.things</li>- {{'pluralization.things' | translate:"{ count: 2 }"}}

    <li translate [translateParams]="{ gender: 'female', name: 'Sarah' }">pluralization.people</li>
    <li translate [translateParams]="{ gender: 'male', name: 'Peter' }">pluralization.people</li>- {{'pluralization.people' | translate:"{ name: 'Sarah + Peter' }"}}

<button class="btn btn-primary" (click)="useLanguage('en')">en</button>
<button class="btn btn-primary" (click)="useLanguage('de')">de</button>

Finally, update the .json files with the new ICU syntax:

{
    "pluralization": {
        "things": "There {count, plural, =0{is} one{is} other{are}} {count, plural, =0{} one{a} other{several}} {count, plural, =0{nothing} one{thing} other{things}}",
        "people": "{gender, select, male{His name is} female{Her name is} other{Their names are }} {name}"
    }
}

Please note that the ICU templates used by the message parser use single braces {} to enclose parameters — in contrast to the template format of ngx-translate.

You can read more about the ICU syntax here: ICU User Guide.

Conclusion

With ngx-translate it's easy to create a multilingual version of your Angular app.

Use ngx-translate-extract to keep your translation files up-to-date.

Finally, BabelEdit helps you to mange and edit your translations.