How to Translate Angular Components with ngx-translate
ngx-translate allows you to easily integrate Angular translation functionality into both your component templates and your TypeScript code, making it simple to build multilingual Angular applications.
ngx-translate v18 is built on Angular Signals.
Signals are reactive values you call as a function to read the current value
(e.g. currentLang()). Reads inside templates, computed(), and effect() are tracked
automatically, so the UI updates whenever the underlying signal changes, with no subscriptions
and no manual change detection. If you’re new to Signals, the official Angular guide
is a 5-minute read and covers everything you need to follow the examples below.
Using translations in your component templates is straightforward. You just need to make sure that the
TranslatePipe or TranslateDirective are available in your component for internationalization.
Import the TranslatePipe and TranslateDirective in your component like this:
import {Component} from '@angular/core';import {TranslatePipe, TranslateDirective} from "@ngx-translate/core";
@Component({ selector: 'app-root', standalone: true, imports: [TranslatePipe, TranslateDirective], templateUrl: './app.component.html', styleUrls: ['./app.component.scss']})export class YourComponent { ...}The easiest way to translate text is using the TranslatePipe:
<div>{{ 'app.hello' | translate }}</div>It’s also possible to add parameters like this:
<div>{{ 'app.hello' | translate:{name} }}</div>And in your component define name like this:
name = "Andreas"In the language file, use {{name}} as placeholder:
{ "app.hello": "Hello {{name}}!"}This is how you use the directive:
<div [translate]="'app.hello'" [translateParams]="{name: 'John'}"></div>Bind a key to [ngxTranslate] to let Angular report a missing directive import:
<div [ngxTranslate]="'app.hello'" [translateParams]="{name: 'John'}"></div>Available since v18.1. It uses the same TranslateDirective import and
translateParams input as translate. The translation updates when the bound key,
parameters, or language changes. Existing translate and ngx-translate selectors
remain supported.
With Angular’s normal template validation, [ngxTranslate] raises NG8002 if
TranslateDirective is missing from the component’s imports. The name avoids
HTML’s native translate property. Static attributes such as
ngxTranslate="app.hello" and translate="app.hello" do not trigger this check.
The static ngxTranslate="app.hello" form also translates when the directive is
imported.
*translateBlock is a new structural directive in v18 that exposes a typed t()
function to a template block. It’s the cleanest way to translate several keys in one
place, and the recommended replacement for the deprecated content-as-key pattern:
<ng-container *translateBlock="let t"> <h1>{{ t('app.title') }}</h1> <p>{{ t('app.hello', { name: user.name }) }}</p></ng-container>Add TranslateBlockDirective to your component’s imports array.
Internally t() delegates to TranslateService.instant(). instant() reads the
store’s translations signal, which establishes Angular signal tracking during the
block’s template evaluation — so the block updates automatically when the language
changes or translations are reloaded, with no subscription or ChangeDetectorRef
work on your side.
Multiple keys in one block:
<ng-container *translateBlock="let t"> <h1>{{ t('page.title') }}</h1> <p>{{ t('page.body') }}</p> <button>{{ t('common.save') }}</button> <button>{{ t('common.cancel') }}</button></ng-container>With interpolation parameters:
<ng-container *translateBlock="let t"> <p>{{ t('greeting', { name: user.name }) }}</p> <p>{{ t('items.count', { count: items.length }) }}</p></ng-container>When to choose which form. *translateBlock is the cleanest fit when you have
three or more keys in one block, or when you want to pass params to several keys
without repeating the pipe syntax. For a single key per element, the pipe
({{ 'KEY' | translate }}) or the [translate] attribute directive is shorter.
For more details and the full comparison see
Concepts → Which API should I use?.
The same UI written two ways. With the pipe:
<div class="card"> <h1>{{ 'card.title' | translate }}</h1> <p>{{ 'card.body' | translate:{ name: user.name } }}</p> <button>{{ 'common.save' | translate }}</button> <button>{{ 'common.cancel' | translate }}</button></div>With *translateBlock:
<ng-container *translateBlock="let t"> <div class="card"> <h1>{{ t('card.title') }}</h1> <p>{{ t('card.body', { name: user.name }) }}</p> <button>{{ t('common.save') }}</button> <button>{{ t('common.cancel') }}</button> </div></ng-container>Both produce identical output and identical reactivity. Pick *translateBlock
when you have more than ~2 keys in one place, or when most translation calls
in the block share parameters.
TranslateSlotsComponent inserts your Angular templates into a translated message.
Use it for links, buttons, components, or semantic elements such as <time> that need
Angular bindings or event handlers. The translation controls where each template appears,
and the host element gets no extra wrapper.
Import TranslateSlotsComponent and TranslateSlotDirective, plus anything your slot
content uses. This example uses RouterLink and TranslatePipe:
import { Component } from '@angular/core';import { RouterLink } from '@angular/router';import { _, TranslatePipe, TranslateSlotDirective, TranslateSlotsComponent,} from '@ngx-translate/core';
@Component({ selector: 'app-upload-hint', standalone: true, imports: [TranslateSlotsComponent, TranslateSlotDirective, TranslatePipe, RouterLink], templateUrl: './upload-hint.component.html',})export class UploadHintComponent { readonly uploadHintKey = _('UPLOAD_HINT');}<p [translateSlots]="uploadHintKey" [translateParams]="{ max: 5 }"> <ng-template translateSlot="link"> <a routerLink="/upload">{{ 'UPLOAD_LINK' | translate }}</a> </ng-template></p>{ "UPLOAD_HINT": "Drop up to {{max}} files here or {{link}}.", "UPLOAD_LINK": "select them"}This renders “Drop up to 5 files here or select them.”, with “select them” as an Angular router link. The text and the link label update when the language changes.
- Use
translateSlots="UPLOAD_HINT"for a literal key or bind[translateSlots]for a dynamic key. Don’t combine it withtranslateorngx-translateon the same element. - Define slots as
ng-templatechildren of the host, including inside@ifblocks. Other children aren’t rendered, but Angular still creates them. - Give each slot a unique name. A duplicate name or a slot name also supplied in
translateParamscauses an error, including a collision with a nested parameter. - Every occurrence of a slot placeholder gets its own view. Views keep their state when the language or slot order changes. Removing an occurrence destroys its view.
- Translation text and ordinary parameter values are rendered as text, never as HTML.
A parameter value containing
{{link}}doesn’t insert the slot. - Slots pass through the configured parser as interpolation parameters, so custom parsers and compiled translations work as well.
Key extraction tools don’t recognize translateSlots yet. Mark the key with _()
in TypeScript and bind it with [translateSlots], as in the example above.
See also: Angular template fragments.
You can easily use raw HTML tags within your translations.
{ "app.hello": "Welcome to my Angular application!<br><strong>This is an amazing app which uses the latest technologies!</strong>"}To render them, simply use the innerHTML attribute with the pipe on any element.
<div [innerHTML]="'app.hello' | translate"></div>[innerHTML] should be safe to use because it uses Angular’s DomSanitizer to filter potentially harmful tags like
<script> or <style>.
To use translations in your code, you can access the TranslateService
using Angular’s modern inject() function. This service allows you to load translations, switch languages, and retrieve
translated messages directly within your application logic.
import {Component, inject} from '@angular/core';import {TranslateService, _} from "@ngx-translate/core";
@Component({ selector: 'app-root', standalone: true, templateUrl: './app.component.html', styleUrls: ['./app.component.scss']})export class YourComponent { private translate = inject(TranslateService);
constructor() { this.translate.get(_('app.hello'), {value: 'world'}).subscribe((res: string) => { console.log(res); //=> 'hello world' }); }}The _() function is a marker function. It helps BabelEdit and the ngx-translate-extract
plugin to distinguish between regular string and translation IDs in your app for automated
extraction of translation stings. Simply wrap all IDs with _() - e.g. _('demo.title).
You might wonder why the get() method
uses a subscription to retrieve translations. The reason is that your language
file might not be loaded at the time the get() function is called, so the
update is triggered once the file finishes loading.
Alternatively, you can use stream(),
which works similarly to get() but has the added benefit of firing again
whenever the language changes. This ensures the translation stays up to date
even if the user switches languages.
For cases where you need an immediate, synchronous translation result, you can
use instant(). This method returns
the translation instantly but only works if the translation file is already
loaded or if translations have been manually set using
setTranslation().
In v18, instant() also participates in Angular’s signal tracking. Calling it
inside computed() updates the derived value when the tracked translation state
changes; calling it once outside a reactive context gives a snapshot. See
reactive usage of instant()
for an example and a comparison with TranslateService.translate().
Reacting to the current language with currentLang()
Section titled “Reacting to the current language with currentLang()”In v18 TranslateService.currentLang is a Signal<Language | null>. Call it as a function
to read the active language. It works directly in templates, computed(), and effect(),
so the UI tracks language changes without manual subscriptions:
<p>Current language: {{ translate.currentLang() }}</p>import { Component, computed, effect, inject } from '@angular/core';import { TranslateService } from '@ngx-translate/core';
@Component({ /* ... */ })export class AppComponent { translate = inject(TranslateService);
// Derive a value from the current language isGerman = computed(() => this.translate.currentLang() === 'de');
constructor() { // React to language changes effect(() => { console.log('Language switched to', this.translate.currentLang()); }); }}fallbackLang is the symmetric reactive Signal for the fallback language, with the same
shape: translate.fallbackLang(): Language | null. Use it wherever you’d reach for
currentLang() and want the fallback instead. For a non-reactive snapshot of either,
use getCurrentLang() or
getFallbackLang().
v18 adds a standalone translate() function for use inside an injection context (such as a
component class field). It returns a Signal<Translation> for a single key, or a
Signal<TranslationObject> for an array of keys, and updates automatically on language
changes or translation reloads. No subscription, no cleanup:
import { Component } from '@angular/core';import { translate } from '@ngx-translate/core';
@Component({ /* ... */ })export class MyComponent { greeting = translate('app.hello', { name: 'World' });}Each parameter (key, params, lang) accepts a plain value, an arrow function, or a
Signal. Signal reads inside the function are tracked automatically.
Requesting a translation in a specific language
Section titled “Requesting a translation in a specific language”get(), instant(), stream(), and the standalone translate() function all accept an
optional third lang parameter that bypasses the current/fallback chain. Use it to fetch a
specific language without changing the active one:
// Always returns the German text, regardless of the current languageconst greetingDe = this.translate.instant('app.hello', { name: 'Welt' }, 'de');
this.translate.get('app.hello', { name: 'John' }, 'fr').subscribe(text => { console.log(text); // French});You can construct the translation keys dynamically by using simple string concatenation inside the template:
<ul> @for (language of languages; track language) { <li>{{ ('languages.' + language) | translate }}</li> }</ul>Where languages is an array member of your component:
languages = ['en', 'fr', 'de'];You language file would look like this;
{ "languages": { "en": "English", "fr": "French", "de": "German" }}You can also use the output of the built-in pipes uppercase and lowercase in order to guarantee
that your dynamically generated translation keys are either all uppercase or all lowercase. For example:
<p>{{ 'roles.' + role | lowercase | translate }}</p>role = 'ADMIN';will match the following translation:
{ "roles": { "admin": "Administrator" }}