This tutorial is for Angular developers who want to translate their application into multiple languages. It covers Angular 8 - 15 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, you might take a look at our other tutorials:
This tutorial guides you through the following steps
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 existing project.
Create an empty new project:
ng new translation-demo
The ng client now asks you if you want to add a router and which CSS style / to use. It does not matter which one you choose.
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
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:
Here's the step-by-step guide on how to use NGX-Translate with Angular:
If you've followed the into step, abort the server with CTRL-C.
Enter the following line in the terminal:
npm install @ngx-translate/core @ngx-translate/http-loader
The @ngx-translate/core contains the core routines for the translation: The TranslateService
, the translate
pipe and more.
The @ngx-translate/http-loader loads the translation files dynamically from your webserver.
Start by initializing the TranslateModule in your app.module.ts. The required changes to that file are highlighted in blue:
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, HttpClientModule} from '@angular/common/http';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// ngx-translate and the loader module
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): TranslateHttpLoader {
return new TranslateHttpLoader(http);
}
The HttpLoaderFactory
function 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.css']
})
export class AppComponent {
constructor(private translate: TranslateService) {
translate.setDefaultLang('en');
translate.use('en');
}
}
Add TranslateService
to the constructor parameters to make it available in the component.
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.
Let's also clean up app.component.html with this simple text:
<div>
<h1>Translation demo</h1>
<p>This is a simple demonstration app for ngx-translate</p>
</div>
Reloading the app seems to work, but a look at the browser console shows the following error:
Failed to load resource: the server responded with a status of 404 (Not Found) http://localhost:4200/assets/i18n/en.json
This is because the HttpLoader
tries to load the default language from /assets/i18n/en.json
from the server —
and we've not yet created that file.
Each language is stored in a separate JSON file. Let's create the JSON file for the English translation: assets/i18n/en.json . Use the texts from the app.components.html . Use a translation ID for each text.
ngx-translate can read 2 JSON formats:
flat json
{
"demo.title": "Translation demo",
"demo.text": "This is a simple demonstration app for ngx-translate"
}
or
nested json recommended
{
"demo": {
"title": "Translation demo",
"text": "This is a simple demonstration app for ngx-translate"
}
}
The translations can, in both cases, be accessed using demo.title
and demo.text
.
I prefer the nested (aka namespaced) JSON format because it creates a cleaner structure.
Tools like BabelEdit can display the JSON file as a tree, allowing you to focus on the part of your application you are currently working on.
It's a good practice to create detailed keys — your translators will love the additional context they provide!
E.g. event.confirm-list.title
. This gives you a good overview
in which context the translations are used. This key is used on the events screen in a list that should confirm events.
You also see that event.confirm-list.confirm-button
is used in the same context.
This not only helps you during development but also helps the translator.
The flat format might include keys which can't be used in the nested format.
E.g. the flat version can use demo
and demo.title
which is not possible
with the nested format since demo
can't have a text and children at the same time.
If you want to convert the flat format into the nested format you'll have to clean up these keys.
So: Better use with the nested right from the start.
It is also possible to use the original text as a key for your translation message. I really recommend not doing this!
E.g. in your de.json
file it would look like this:
{
"Translation demo": "Übersetzungsdemo",
"This is a simple demonstration app for ngx-translate": "Dies ist ein einfaches Beispielprojekt für ngx-translate"
}
The problem is that each change in your main language automatically kills your other translation files. E.g, if you
decide to add a "." at the end of "This is a simple demonstration app for ngx-translate." that key is now
missing from the de.json
and is not displayed. At the same time, the old key "This is a simple demonstration app for ngx-translate"
(without the final period) is an unused entry in the file — with a small change that is really hard to spot.
Apart from that, using the translation texts also does not contain any context information. You end up with a list of 100 or more text snippets that provide no information about where they are used.
Now edit the 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
<element [translate]="'id'"></element>
— translation directive, id as attribute value deprecated
<element translate>id</element>
— translation directive, id as a child deprecated
translate requires a value
)So: If possible go with variant 1.
app.component.html:
<div>
<h1>{{ 'demo.title' | translate }}</h1>
<!-- translation: translation pipe -->
<p>{{ 'demo.text' | translate }}</p>
<!-- translation: directive (key as attribute)-->
<p [translate]="'demo.text'"></p>
<!-- translation: directive (key as content of element) -->
<p translate>demo.text</p>
</div>
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.
<!-- translation with parameters: 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>
You can of course pass parameters from your component's .ts file, too.
You also have to extend the en.json with the new demo.greetings
id.
Translation parameters are put between {{
and }}
:
{
"demo": {
"title": "Translation demo",
"text": "This is a simple demonstration app for ngx-translate",
"greeting": "Hello {{name}}!"
}
}
Sometimes you have to translate messages in your code and not only the template.
You can do that with the get()
and instant()
methods of the TranslateService
:
translate.get('demo.greeting', {name: 'John'}).subscribe((res: string) => {
console.log(res);
});
You might wonder, why get()
returns an observable that you have to
subscribe to. This is because ngx-translate handles language switches at runtime. The observable fires if
the language is switched!
If you are 100% sure that your translations are already loaded (e.g. by using static translations instead of
the translation loader) you can also use instant()
.
console.log(translate.instant('demo.greeting', {name: 'John'}));
It's also possible to use HTML formatted messages in your translations.
{
"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.
<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>
.
ngx-translate also supports more complex translation parameters including so-called pluralization and selections. E.g. instead of using
It can automatically choose the right form the following:
This does not work out-of-the box but can be done with a plugin called ngx-translate-messageformat-compiler
.
It uses ICU format (ICU stands for International Components for Unicode) and is a wildly used
format for specifying complex translation texts.
I'll show you how in the Pluralization section later in this tutorial.
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.
But JSON translation files are a completely different story. Why?
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.
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
...
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...
Above is a diff of 2 JSON files in AraxisMerge — which is a really good diff / merge tool btw... But it's simply the wrong tool for the job...
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 and ngx-translate. It comes with a lot of nice features that make your daily work much easier:
Start by selecting the ngx-translate project template:
Now drag & drop your assets/i18n -folder onto the main window.
BabelEdit now asks you for the languages contained in the files — making guesses from the file name.
In the same dialog, click on the Add button again to add a new language. Choose New from the menu that opens here:
In the new dialog select the language you want to add and click on Ok. BabelEdit automatically chooses the file name based on the language code and puts it into the same directory where the other language file already exists.
Finally, set the primary language to en-US — it's required for the automatic translation feature of BabelEdit:
After that close the dialog.
This is not a tutorial about BabelEdit but let me show you one awesome features called PreTranslate — it simply takes all your texts and translates them using Google Translate, DeepL or Bing Translate.
Click Pre-Translate in the menu bar, click Ok in the dialog that opens.
Press Save. BabelEdit asks you to save the project file. Enter angular-demo.babel as file name. This also saves your en.json and de.json files.
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:
<button (click)="useLanguage('en')">en</button>
<button (click)="useLanguage('de')">de</button>
Add the following method to the 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.
The TranslateService
contains 2 methods to receive the language set in the user's browser:
translate.getBrowserLang()
gives
you the language set in the user's browser (en
)translate.getBrowserCultureLang()
which gives you the complete language code such as en-US
or en-GB
.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 @bartholomej/ngx-translate-extract @biesbjerg/ngx-translate-extract-marker
@biesbjerg/ngx-translate-extract
, switching to @bartholomej/ngx-translate-extract
should fix the issue.const text = "node:internal/modules/cjs/loader:979 throw new ERR_REQUIRE_ESM(filename, true); ^ Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/@angular/compiler/fesm2015/compiler.mjs not supported. Instead change the require of .../node_modules/@angular/compiler/fesm2015/compiler.mjs to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (.../@biesbjerg/ngx-translate-extract/dist/parsers/pipe.parser.js:3:20) at Object.<anonymous> (.../@biesbjerg/ngx-translate-extract/dist/cli/cli.js:6:23) at Object.<anonymous> (.../@biesbjerg/ngx-translate-extract/bin/cli.js:3:1) {"{"} code: 'ERR_REQUIRE_ESM'
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
--patterns
parameter to specify other file extensions.--output ./src/assets/i18n/*.json
--clean
--sort
--format namespaced-json
--marker _
_('app.title')
. See below.A simple command now updates your JSON files:
npm run extract-translations
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 { marker } from '@biesbjerg/ngx-translate-extract-marker';
{
let messageBoxContent = marker('messagebox.warning.text');
}
Using marker
might not be the best choice for a name so feel free to rename it to
whatever suits you. I prefer _
. The extract module detects the function name you are
using based on the import.
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
{
let messageBoxContent = _('messagebox.warning.text');
}
You can now use the pipe or directive to display the translated string:
<div>{{ messageBoxContent | translate }}</div>
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.
Or you want to display a dynamic value:
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 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]
// highlight-start
},
// compiler configuration
compiler: {
provide: TranslateCompiler,
useClass: TranslateMessageFormatCompiler
}
// highlight-end
})
],
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:
<div>
<h2>ngx-translate-messageformat-compiler demo</h2>
<h3>Pluralization</h3>
<ul>
<li translate="" [translateParams]="{ count: 1 }">icu.pluralization</li>
<li>{{'icu.pluralization' | translate:{ 'count': 2 } }}</li>
</ul>
<h3>Selection</h3>
<ul>
<li translate [translateParams]="{ 'gender': 'male', 'product': 'BabelEdit' }">icu.select</li>
<li>{{'icu.select' | translate:{ 'gender': 'other', 'product': 'BabelEdit' } }}</li>
</ul>
<button (click)="useLanguage('en')">en</button>
<button (click)="useLanguage('de')">de</button>
</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]="'demo.greeting'" [translateParams]="{name: 'Andreas'}"></p>
<li [translate]="'icu.select'" [translateParams]="{ 'gender': 'female', 'product': 'BabelEdit' }"></li>
Finally, update the en.json files with the new ICU syntax:
{
"demo": {
"greeting": "Hello {name}!",
"text": "This is a simple demonstration app for ngx-translate",
"title": "Translation demo"
},
"icu": {
"pluralization": "There {count, plural, =0{is no apple} one{is one apple} other{there are several apples}}.",
"select": "{gender, select, male{His uses} female{She uses} other{They use }} {product}"
},
"messagebox": {
"warning": {
"text": "Warning!"
}
}
}
and de.json
{
"demo": {
"greeting": "Hallo {name}!",
"text": "Dies ist eine einfache Applikation um die Funktionen von ngx-transalte zu demonstrieren.",
"title": "Übersetzungs-Demo"
},
"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}."
},
"messagebox": {
"warning": {
"text": "Warnung!"
}
}
}
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.
This is why you also have to replace the {{name}}
in demo.greeting
with {name}
.
You can read more about the ICU syntax here: ICU User Guide.
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'll still see a small glitch when you start the application with another language — but this time it's your main language that is displayed and not the translation IDs.
If this is also not acceptable you can either bundle your application with all languages or build separate bundles for each language.
First enable loading of JSON files in your tsconfig.app.json
"compilerOptions": {
...
"resolveJsonModule": true,
"esModuleInterop": true
...
},
Open your app.component.ts and load your default language at the top:
import defaultLanguage from "./../assets/i18n/en.json";
Change the constructor to set the translations from the file:
constructor(private translate: TranslateService) {
translate.setTranslation('en', defaultLanguage);
translate.setDefaultLang('en');
}
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.