How to Translate Your Angular App with NGX-Translate

Andreas Löw
Last updated:
GitHub
How to Translate Your Angular App with NGX-Translate

Who is this tutorial for?

This tutorial is for Angular developers who want to translate their application into multiple languages. It covers Angular 17 - 18 together with the corresponding ngx-translate versions.

If you are, for some reason, still using an older Angular version, please take a look at the following tutorials:

If you are not sure which translation method to use with your Angular application, check out our other tutorials, e.g. How to translate Angular apps: @angular/localize and xlf

How to Use NGX-Translate with Angular

This tutorial guides you through the following steps

Optional: Create an Angular Project

For this tutorial you'll start with a simple demo application. I assume that you already have basic knowledge of Angular... You can, of course, skip this step and use your existing project.

Create an empty new project:

npx -p @angular/cli ng new translation-demo --style=scss --routing=false --ssr=false

This creates a new Angular project in a directory called translation-demo. The additional options have the following meaning:

  • --style=scss: We use SCSS for styling the app. This is an extension to plain CSS files.
  • --routing=false: We don't use a router for this simple application
  • --ssr=false: We don't use server side rendering

I do not install the ng command globally. Why? I have several projects with different Angular versions and other frameworks on my computer. Adding everything as global really is a pain with all the dependencies, different node version, etc.

The only drawback is that you have to call npm run-script ng instead of just ng - but I think that this is acceptable. On a Mac or Linux system you can set an alias - with this, you can call ng directly while you inside your project folder:

alias ng="npm run-script ng"

Finally, start the new project:

cd translation-demo
ng serve

Open your browser and visit the following URL: http://localhost:4200. You should see something similar to this:

Your first Angular application
Default Angular Application at the first start.

Here's the step-by-step guide on how to use NGX-Translate with Angular:

Step 1: Add ngx-translate to your Angular Application

Enter the following line in the terminal:

npm install @ngx-translate/core @ngx-translate/http-loader @colsen1991/ngx-translate-extract-marker

The @ngx-translate/core contains the core routines for the translation: The TranslateService, the TranslatePipe and more.

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

The @colsen1991/ngx-translate-extract-marker contains a marker function that you can use to easily find and extract translation IDs from your code.

Step 2: Set Up the TranslateModule and TranslateService

For the installation, we have to decide which project type you use. You can determine this by checking if the file src/app/app.module.ts exists. In the clean project we created by default, this file is missing and it's using the standalone components.

Installation with Standalone Components (Default)

Let's start by initialising ngx-translate in the src/app/app.config.ts:

src/app/app.config.ts
import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from "@angular/core";
import {provideHttpClient} from "@angular/common/http";
import {TranslateModule, TranslateLoader} from "@ngx-translate/core";
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient} from '@angular/common/http';

const httpLoaderFactory: (http: HttpClient) => TranslateHttpLoader = (http: HttpClient) =>
    new TranslateHttpLoader(http, './i18n/', '.json');

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideHttpClient(),
    importProvidersFrom([TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: httpLoaderFactory,
        deps: [HttpClient],
      },
    })])
  ],
};

TranslateModule.forRoot() initialises the TranslateService and according to the configuration provided. ngx-translate is very modular, and you can use plugins to enhance it.

In this case, we use TranslateHttpLoader to load the JSON files containing the translations at runtime.

src/app/app.component.ts
import {Component} from "@angular/core";
import {TranslateModule} from "@ngx-translate/core";

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TranslateModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'translation-demo';
}

To use ngx-translate in your components, you have to import the TranslateModule simply by adding it to the imports: section of the component.

You can now skip the next section - this part is only required if you use an app.module.ts.

Installation with NgModule

Update the content of the src/app/app.module.ts like in the listing below. This imports and initialises ngx-translate.

src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

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, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [HttpClient]
            }
        })
    ],
    providers: [provideHttpClient(withInterceptorsFromDi())],
    bootstrap: [AppComponent]
})
export class AppModule { }

// required for AOT compilation
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
    return new TranslateHttpLoader(http, './i18n/', '.json');
}

TranslateModule.forRoot() initialises the TranslateService and according to the configuration provided. ngx-translate is very modular, and you can use plugins to enhance it.

In this case, we use TranslateHttpLoader to load the JSON files containing the translations at runtime.

Common Parts of the Installation

From here, the use of ngx-translate is almost identical. I'll only show the standalone code and add comments where the differences in the files are.

If you use NgModules, the lines with // <--- standalone only should not be part of your code!

Now switch to app.component.ts :

src/app/app.component.ts
import { Component } from '@angular/core';
import {TranslateModule} from "@ngx-translate/core";   // <--- standalone only
import {TranslateService} from "@ngx-translate/core";

@Component({
  selector: 'app-root',
  standalone: true,                                    // <--- standalone only
  imports: [TranslateModule],                          // <--- standalone only
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  constructor(private translate: TranslateService) {
    this.translate.addLangs(['de', 'en']);
    this.translate.setDefaultLang('en');
    this.translate.use('en');
  }
}

Add TranslateService to the constructor parameters to make it available in the component.

Add the languages you want to support using `translate.addLangs(['de', 'en']) — in this demo we use English and German. Feel free to use your preferred language - just make sure to use the matching language code.

Set the default language of your application using translate.setDefaultLang('en'). The default language is the fall-back language, that is used if a translation can not be found.

Set the current language to English by calling translate.use('en'). You'll learn more about switch languages at runtime later in this tutorial.

This is not required - but makes the app look better... so paste this into the src/app/app.component.scss:

src/app/app.component.scss
div
{
  font-family: Arial, Helvetica, sans-serif;
  margin: 0;
  padding: 1rem 2rem;
}

p, .translated
{
  background-color: #d4f3ff;
  padding:0.5rem 1rem;
}

button {
  background-color: #008CBA;
  border: none;
  color: white;
  padding: 0.25rem 0.5rem;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin-left: 1rem;
  border-radius: 0.5rem;
  transition: background-color 0.3s ease;

  &:hover {
    background-color: #005f73;
  }
}

.title {
  background-color: #bde8f8;
  padding: 0.5rem 2rem 1.5rem 2rem;
  position: sticky;
  top: 0;
  z-index: 1000;
}

Step 3: Use translations in your templates

Now edit the src/app/app.component.html and replace the static texts with references to the messages in the en.json file.

You have 3 choices when it comes to adding translations. All options should lead to the same result — but that's not true in practice:

  • {{'id' | translate}} — translation pipe recommended
    • works in all use-cases
    • acceptable readability
    • also supports other pipes — e.g. making the translation string uppercase,...
  • <element [translate]="'id'"></element> — translation directive, id as attribute value deprecated
    • does (currently) not work with the more complex translation texts — see our section on Pluralization
  • <element translate>id</element> — translation directive, id as a child deprecated
    • shows errors/warnings in some IDEs (e.g. PhpStorm: translate requires a value )
    • quite unreadable syntax when adding parameters

So: If possible go with variant 1.

src/app/app.component.html
<div class="title">
    <h1>{{ 'demo.title' | translate }}</h1>
</div>
<div>
    <h2>Simple translations without parameters</h2>

    <p>{{ 'demo.simple.text-with-pipe' | translate }}</p>

    <p [translate]="'demo.simple.text-as-attribute'"></p>

    <p translate>demo.simple.text-as-content</p>
</div>

This file now contains 4 translation IDs: demo.title, demo.simple.text-with-pipe, demo.simple.text-with-attribute and demo.simple.text-with-content. The dots separate the parts of an ID. This creates a tree structure which gives you a great way to organize your IDs - e.g. by the screen or component they are used in.

  • demo
    • title
    • simple
      • text-as-attribute
      • text-as-content
      • text-with-pipe

Let's now translate the files. I'll show you how to use translations with parameters and inside your code later.

Step 4: Create your JSON translation files

Start with empty files

Each language is stored in a separate files. Let's create the translation files for English and German:

  • public/i18n/de.json
  • public/i18n/en.json

You can leave the files empty or use the following content which makes them valid JSON files:

public/i18n/de.json,en.json
{}

Usually, you would have to add the IDs and create the structure from above - but there are simpler ways to do that.

Get BabelEdit

It's possible to edit these files manually - but using a specialized editor for the job will save you a lot of hassle later. For this, download BabelEdit.

BabelEdit is available for Windows, macOS and Linux. It's a translation editor running locally on your computer - no subscription required.

After the installation, drag & drop the i18n folder onto BabelEdit:

Drag and drop the i18n folder onto BabelEdit
Drag and drop the i18n folder onto BabelEdit

It automatically detects ngx-translate and configures the project for you. It also detects the 2 language files and configures them with the languages.

Configure languages for your ngx-translate project
Configure the languages for your ngx-translate project

Feel free to use en-GB or instead of en-US in your project. Click Ok to accept the setup.

Next, click Add ID in the toolbar or press +n (macOS) or CTRL+n (Windows).

You can now enter the translation ID manually - e.g. demo.title - but a way more convenient way is using the extraction tool in BabelEdit. Click the Extract from source code... button in the lower left edge of the dialog:

Extract translation IDs from the source code
Extract translation IDs form the source

Next, you should see this dialog:

Adding extracted IDs to your translation files.
Adding extracted IDs to your translation files.

The left side of this dialog shows you the translation IDs BabelEdit found in your files. On the right side at the top, you see the files and lines, in which the ID was spotted. Click on one to see the code block in the lower right panel.

Use the buttons at the bottom to add the currently selected ID or all IDs. You can also choose to ignore some or all of the IDs in case BabelEdit displays IDs which are not meant for translation.

If nothing shows up at all, a possible reason is that your source root is not set. You can do this in the toolbar using the Configuration button. Point the source root to the root folder if your Angular application.

In this project, all IDs should be fine - so click on Add all. This creates entries in all 3 translation files at once - your translation files can never get out of sync. You also see that it creates a tree structure. With this, you can easily group your translations in different contexts. E.g. main.title, settings.account.user_name and so on.

Let's now enter the text in english:

IDtext
demo.simple.text-as-attributeA simple text passing the ID as attribute.
demo.simple.text-as-contentA simple test using the ID as content.
demo.simple.text-with-pipeA simple text using the pipe.
demo.titleTranslation Demo

Step 5: Translate Your App to Other Languages

Translating single messages

BabelEdit requires what we call the primary language - that is the language used as a base the translations. Click on Configure... in the yellow dialog box at the bottom the center area.

Select your primary language for translations
Select en-US as primary language

After the configuration, another box opens: Display machine translations? - select Enable. This enables Google Translate, DeepL and Bing translation services inside BabelEdit that will speed up your development process!

In the field for en-US enter:

  • en-US: App Translation Demo

Switch to the de-DE field. Click on the German translation in the lower panel or press +1 on a Mac or CTRL+1 on Windows to use the translation Google provided. You can also switch to other services such as OpenAI, DeepL or Microsoft depending on your preferences.

Using machine translation in ngx-translate
Press CTRL/CMD+1 to accept the machine translation

Translating many messages at once

Repeating this process for all languages takes quite some time - so let's speed it up. For this, use BabelEdit's Pre-Translate dialog to translate to all languages automatically - it's available from the toolbar. You can also use it if you've already entered many translations in your primary language and want.

Translate to multiple languages with 2 clicks
Pre-Translate: Translate to multiple languages with 2 clicks...

Saving the translated files and the BabelEdit project

Finally, press Save. This saves the project configuration in a .babel project file and also updates the JSON files in your project.

The .babel file allows you to continue working on a project later without re-configuring everything.

When reloading the app, you should now see the messages in English - but you can't switch to German yet.

Step 6: Switching Languages at Runtime

Adding a Language Selector

Let's now update the UI to switch between the language files you've just created:

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:

src/app/app.component.html
<div class="title">
    <h1>{{ 'demo.title' | translate }}</h1>
    <span>Switch languages at runtime: </span>
    <button (click)="useLanguage('en')">en</button>
    <button (click)="useLanguage('de')">de</button>
</div>

Add the following method to the app.component.ts

src/app/app.component.ts
useLanguage(language: string): void {
    this.translate.use(language);
}

As you might remember, you've used setDefaultLang('en') in the constructor. The default language is the fall-back that is used if a translation is not present in your selected language.

So if you suddenly see English text even if you've selected another language, the reason is usually that your translation files are out-of-sync, and you've not yet translated that string to the other language.

ngx-translate: Angular i18n with switching languages at runtime
ngx-translate: Angular i18n with switching languages at runtime

Getting the Browser Default Language

The TranslateService contains 2 methods to receive the language set in the user's browser:

src/app/app.component.ts
constructor(private translate: TranslateService) {
    this.translate.addLangs(['de', 'en']);
    this.translate.setDefaultLang('en');
    this.translate.use(this.translate.getBrowserLang() || "en");
}

This switches to the language set in your browser / computer by default. If the language can't be determined, it switches to English. If the language is not supported - e.g. fr , ngx-translate will fall back to default language;

Step 7: Complex Translations: Parameters & Translations in Code

Using Parameters in Your Translations

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

  • {{'id' | translate: {parameter:value} }}
  • <element [translate]="'id'" [translateParams]="{parameter:value}"></element>
  • <element translate [translateParams]="{parameter:value}">id</element>

In your app.component.html, it looks like this.

src/app/app.component.html
<h2>Translations with parameters</h2>

<p>{{ 'demo.interpolation.pipe-with-parameters' | translate:{name} }}</p>

<p [translate]="'demo.interpolation.attribute-with-parameters'" [translateParams]="{name}"></p>

<p translate [translateParams]="{name}">demo.interpolation.id-as-content-with-parameters</p>

Also add the name property to the app.component.ts:

src/app/app.component.ts
export class AppComponent {
    title = 'translation-demo';
    name = "Andreas";

Switch back to BabelEdit and use the Add ID with the extractor like we did before.

IDtext
demo.interpolation.attribute-with-parametersHello {{name}}, how are you?
demo.interpolation.id-as-content-with-parametersHey {{name}}, how are things?
demo.interpolation.pipe-with-parametersHello {{name}}, hope you're doing well!

The parameters are put between {{ and }}. So \{\{name}} is replaced with the value of the property name in the component.

Use the Pre-translate functionality to create the translation for the other language. Press Save to update the JSON files.

Using Translations in Your Components (TypeScript Code)

Sometimes you have to translate messages in your code and not only the template. To do so, start by adding a marker function to you code. This helps us to extract the translation IDs form the code later, making management of your translations way easier:

src/app/app.component.ts
import { marker as _ } from '@colsen1991/ngx-translate-extract-marker';

When using BabelEdit, you currently have to map that marker to _ if you want to extract translations from code.

ngx-translate has 2 main functions to use translations in your code: get() and instant()

src/app/app.component.ts
translate.get(_('demo.interpolation.pipe-with-parameters'), {name: 'John'}).subscribe((text: string) => {
  console.log(`using get(): ${text}`);
});

In the console log of your browser, you should see:

using get(): Hallo John, ich hoffe, es geht Ihnen gut!

We just re-use one of the existing IDs (demo.interpolation.pipe-with-parameters) to spare you adding a new ID...

You might wonder, why get() returns an observable that you have to subscribe to. This is because ngx-translate translation file might not have been loaded by ngx-translate. The observable completes as soon as the translation is available.

If you are absolutely sure that the translation file is already loaded - e.g. because you use compiled your translations in the code directly - or you use some mechanics to wait for the load to finish before really launching your app - you can use instant(). This works well - e.g. in popup dialogs. Instant returns a string instead of the promise.

src/app/app.component.ts
const text = translate.instant(_('demo.interpolation.pipe-with-parameters'), {name: 'John'})
console.log(`using instant(): ${text}`);

In the console log, you should see:

using instant(): demo.interpolation.pipe-with-parameters

This is - as explained above - the result that you get if the file is not yet loaded.

Inside the promise of the get() we can be sure that it's already resolved, so this works:

src/app/app.component.ts
translate.get(_('demo.interpolation.pipe-with-parameters'), {name: 'John'}).subscribe((text: string) => {
  console.log(`using get(): ${text}`);

  // translations are alreaedy loaded - you can now use instant()
  const text2 = translate.instant(_('demo.interpolation.pipe-with-parameters'), {name: 'John'})
  console.log(`using instant() inside the promise: ${text2}`);
});

Switching languages has no influence on the console and does not trigger a new output. If you want to update the text on the fly on a language change (e.g. when using it in your toolbars or menus of your app) use stream():

src/app/app.component.ts
import {Subscription} from "rxjs";
...

export class AppComponent implements OnDestroy {
  name = "Andreas";
  private subscription: Subscription;
  constructor(private translate: TranslateService) {
    ...
    this.subscription = translate.stream(_('demo.interpolation.pipe-with-parameters'), {name: 'John'}).subscribe((text: string) => {
      console.log(`using stream(): ${text}`);
    });
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
  ...
}

You have to unsubscribe() on destruction of the component to prevent memory leaks in your application.

Step 7: Static Loader (Optional)

ngx-translate is modular and can be tailored to your needs in many ways - e.g. by using an optional loader.

If your app is not too big, and you don't have too many languages, you could switch from the TranslateHttpLoader to our StaticTranslationLoader. This one is not really a loader - it simply embeds all language files into the app so that all of them are available right at startup. This is quite convenient since you can now use instant() in many places without using the subscriptions like you have to do with get().

The disadvantage is of course that your app bundle is increased by the size of all translation files.

src/app/static-translations-loader.ts
import { TranslateLoader } from '@ngx-translate/core';
import { Observable, of } from 'rxjs';

import * as TranslationsDE from '../../public/i18n/de.json';
import * as TranslationsEN from '../../public/i18n/en.json';

interface Translation {
    [key: string]: string | Translation;
}

const TRANSLATIONS: Translation = {
    en: TranslationsEN,
    de: TranslationsDE
};

export class StaticTranslationLoader implements TranslateLoader {
    public getTranslation(lang: string): Observable<Translation|string> {
        const translation = TRANSLATIONS[lang];
        if (translation) {
            return of(translation);
        } else {
            console.error(`Unknown language: ${lang}`);
            return of({});
        }
    }
}

To use it, update the TranslateModule.forRoot() in your src/app/app.module.ts or src/app/app.config.ts:

src/app/app.module.ts,src/app/app.config.ts
import {StaticTranslationLoader} from "./static-translations-loader";

...

    TranslateModule.forRoot({
        loader: {
            provide: TranslateLoader,
            useClass: StaticTranslationLoader,
        },

Step 8: Testing

For testing, you have 2 choices:

  • without a language file loaded
  • with a language file loaded

If you don't load a language file during the test, ngx-translate will fall back to display the translation IDs. This is fine for testing the application logic. However, some functionalities can't be tested like the use of parameters.

Language Independent Tests

For testing the app without loading a language file, modify the src/app/app.component.spec.ts like this.

This is the version when using standalone components:

src/app/app.component.spec.ts
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {} from 'jasmine';
import {TranslateModule} from "@ngx-translate/core";

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        AppComponent,
        TranslateModule.forRoot({})
      ],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it('should render title', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('h1')?.textContent).toContain('demo.title');
  });
});

If you are using NgModules, apply these changes:

src/app/app.component.spec.ts
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {} from 'jasmine';
import {TranslateModule} from "@ngx-translate/core";
import {AppModule} from "./app.module";

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        AppModule,
        TranslateModule.forRoot({})
      ]
    }).compileComponents();
  });

Language Dependent Tests

To simplify the tests, you can use the StaticTranslationLoader I introduced in the previous chapter. You don't have to use it for the app itself - but it helps a lot with testing.

However, what it does not allow you to test is the state of your application, in which no translation file is loaded yet.

This is again the version for standalone components:

src/app/app.component.i18n.spec.ts
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {} from 'jasmine';
import {TranslateLoader, TranslateModule, TranslateService} from "@ngx-translate/core";
import {StaticTranslationLoader} from "./static-translations-loader";

describe('AppComponent Localized', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        AppComponent,
        TranslateModule.forRoot({
          loader: {
            provide: TranslateLoader,
            useClass: StaticTranslationLoader,
          },
        })],
      providers: []
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it('should render title in english (default)', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('h1')?.textContent).toContain('Translation Demo');
  });

  it('should render title in german', () => {
    const fixture = TestBed.createComponent(AppComponent);

    const translateService = TestBed.inject(TranslateService);
    translateService.use('de');

    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('h1')?.textContent).toContain('Übersetzungs Demo');
  });
});

... and the changes required for NgModules:

src/app/app.component.spec.ts
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {} from 'jasmine';
import {TranslateModule} from "@ngx-translate/core";
import {AppModule} from "./app.module";

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        AppModule,
         TranslateModule.forRoot({
          loader: {
            provide: TranslateLoader,
            useClass: StaticTranslationLoader,
          },
        })],
      ]
    }).compileComponents();
  });

Step 9: Pluralization in Angular in ngx-translate Using ICU formatted messages

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/core

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

src/app/app.module.ts
// import ngx-translate-messageformat-compiler
import {TranslateMessageFormatCompiler} from 'ngx-translate-messageformat-compiler';

...

        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [HttpClient]
            // highlight-start
            },

            // compiler configuration
            compiler: {
                provide: TranslateCompiler,
                useClass: TranslateMessageFormatCompiler
            }
            // highlight-end
        })
    ],

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

<div class="title">
    <h1>ngx-translate message format compiler demo</h1>

    <span>Switch languages at runtime: </span>
    <button (click)="useLanguage('en')">en</button>
    <button (click)="useLanguage('de')">de</button>
</div>

<div>
    <h3>Pluralization</h3>

    <label for="count-select">Select count: </label>
    <select id="count-select" [(ngModel)]="count" (change)="onCountChange($event)">
        <option value="0">0</option>
        <option value="1">1</option>
        <option value="5">5</option>
    </select>

    <p id="icuPluralization" translate="" [translateParams]="{count}">icu.pluralization</p>
    <p>{{'icu.pluralization' | translate:{ count } }}</p>

    <h3>Selection</h3>

    <label for="gender-select">Select Gender: </label>
        <select id="gender-select" [(ngModel)]="gender" (change)="onGenderChange($event)">
        <option value="male">Male</option>
        <option value="female">Female</option>
        <option value="other">Other</option>
    </select>

    <p id="icuSelect" translate [translateParams]="{ gender, 'product': 'BabelEdit' }">icu.select</p>
    <p>{{'icu.select' | translate:{ gender, 'product': 'BabelEdit' } }}</p>
</div>

For some reason not obvious to me, this following syntax does not work with the message compiler. Or better said: It works but gives an error in the browser console.

<p [translate]="'icu.pluralization'" [translateParams]="{count}"></p>

Finally, update the public/i18n/en.json files with the new ICU syntax:

public/i18n/en.json
{
	"icu": {
		"pluralization": "There {count, plural, =0{is no apple} one{is one apple} other{there are several apples}}.",
		"select": "{gender, select, male{He uses} female{She uses} other{They use }} {product}"
	}
}

and public/i18n/de.json

public/i18n/de.json
{
	"icu": {
		"pluralization": "Da {count, plural, =0{ist kein Apfel} one{ist ein Apfel} other{sind mehrere Äpfel}}.",
		"select": "{gender, select, male{Er verwendet} female{Sie verwendet} other{Sie verwenden }} {product}."
	}
}

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.

Step 10: How to Fix Glitches When Using TranslateLoader

The translation files are loaded after the application is initialized, this is why you can see the translation message IDs for a short time.

The easiest way to avoid this is to add your main language as static data to your application. You can do this using the StaticTranslationLoader introduced above. Another option is to use setTranslation() in your app.

First enable loading of JSON files in your tsconfig.app.json

tsconfig.app.json
"compilerOptions": {
    ...
    "resolveJsonModule": true,
    "esModuleInterop": true
    ...
},

Open your app.component.ts and load your default language at the top using an import.

Change the constructor to set the translations from the file:

src/app/app.component.ts
import defaultLanguage from '../../public/i18n/en.json';
...

constructor(private translate: TranslateService) {
    translate.setTranslation('en', defaultLanguage);
    translate.setDefaultLang('en');
}

Step 11: Using HTML Formatted Messages

It's also possible to use HTML formatted messages in your translations.

E.g.

  • demo.paragraph: Try <strong>BabelEdit</strong>! This translation editor is made for <strong>ngx-translate</strong>!

To render them, simply use the innerHTML attribute with the pipe on any element.

src/app/app.component.html
<div [innerHTML]="'demo.paragraph' | translate"></div>

[innerHTML] should be safe to use because it uses Angular's DomSanitizer to filter potentially harmful tags like <script> or <style>.

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.