Internationalization (i18n) in Angular
Internationalization (i18n) is the process of preparing your Angular application to support multiple languages
and locales. Angular provides a built-in i18n system through the @angular/localize package,
as well as support for popular third-party solutions like ngx-translate and transloco.
In this guide we cover Angular's built-in i18n, plus the widely-used ngx-translate library.
Built-in Angular i18n
Angular's built-in i18n extracts translation strings at build time and produces one bundle per locale. This approach gives the best runtime performance since no translation library is loaded.
Step 1: Add Localize Package
ng add @angular/localize
This adds @angular/localize to your project and updates angular.json and tsconfig.json.
Step 2: Mark Text for Translation
Use the i18n attribute (or $localize tag) to mark strings for extraction:
<!-- Basic text -->
<h1 i18n>Hello, World!</h1>
<!-- With description (helps translators) -->
<p i18n="Greeting on home page">Welcome to our store</p>
<!-- With meaning and description -->
<button i18n="action|Button to submit the form">Submit</button>
<!-- Attribute translation -->
<img [src]="logo" i18n-alt alt="Company logo" />
<!-- Interpolation -->
<p i18n>Hello, {{ username }}!</p>// In TypeScript — use $localize tag
import '@angular/localize/init';
export class AppComponent {
greeting = $localize`Hello from TypeScript!`;
getWelcome(name: string): string {
return $localize`Welcome, ${name}:name:!`;
}
}Step 3: Extract Translation Messages
ng extract-i18n --output-path src/locale
Extracting messages... Extraction complete. Messages written to src/locale/messages.xlf
This creates messages.xlf in XLIFF format. You can also extract to XLIFF 2.0 or JSON:
ng extract-i18n --format xlf2 --output-path src/locale ng extract-i18n --format json --output-path src/locale
Step 4: Create Translation Files
Copy messages.xlf and create locale-specific files:
cp src/locale/messages.xlf src/locale/messages.de.xlf cp src/locale/messages.xlf src/locale/messages.fr.xlf
<!-- src/locale/messages.de.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" target-language="de" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="greeting" datatype="html">
<source>Hello, World!</source>
<target>Hallo, Welt!</target>
</trans-unit>
<trans-unit id="welcome-store" datatype="html">
<source>Welcome to our store</source>
<target>Willkommen in unserem Geschäft</target>
</trans-unit>
</body>
</file>
</xliff>Step 5: Configure angular.json for Multiple Locales
{
"projects": {
"my-app": {
"i18n": {
"sourceLocale": "en-US",
"locales": {
"de": {
"translation": "src/locale/messages.de.xlf",
"baseHref": "/de/"
},
"fr": {
"translation": "src/locale/messages.fr.xlf",
"baseHref": "/fr/"
}
}
},
"architect": {
"build": {
"options": {
"localize": true
}
},
"serve": {
"configurations": {
"de": {
"buildTarget": "my-app:build:development,de"
}
}
}
}
}
}
}Building and Serving Locales
# Build all locales ng build --localize # Serve a specific locale during development ng serve --configuration=de # Build outputs: # dist/my-app/en-US/ # dist/my-app/de/ # dist/my-app/fr/
Pluralization and Select
Angular i18n supports ICU message format for plurals and selects:
<!-- Pluralization -->
<p i18n>
{itemCount, plural,
=0 {No items in cart}
=1 {1 item in cart}
other {{{ itemCount }} items in cart}
}
</p>
<!-- Select (gender, status, etc.) -->
<p i18n>
{gender, select,
male {He is a developer}
female {She is a developer}
other {They are a developer}
}
</p>
<!-- Nested plural + select -->
<p i18n>
{gender, select,
male {He has {count, plural, =1 {1 message} other {{{count}} messages}}}
female {She has {count, plural, =1 {1 message} other {{{count}} messages}}}
other {They have {count, plural, =1 {1 message} other {{{count}} messages}}}
}
</p>Date, Number, and Currency Pipes
Angular's built-in pipes are locale-aware when you configure LOCALE_ID:
// app.config.ts
import { ApplicationConfig, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
registerLocaleData(localeDe);
export const appConfig: ApplicationConfig = {
providers: [
{ provide: LOCALE_ID, useValue: 'de-DE' },
],
};<!-- With de-DE locale -->
<p>{{ 1234567.89 | number }}</p> <!-- 1.234.567,89 -->
<p>{{ 1234567.89 | currency:'EUR' }}</p> <!-- 1.234.567,89 € -->
<p>{{ today | date:'fullDate' }}</p> <!-- Dienstag, 1. Juli 2026 -->ngx-translate (Runtime Translation)
For runtime language switching without rebuilding, use ngx-translate — the most popular Angular translation library. It loads translations dynamically from JSON files.
npm install @ngx-translate/core @ngx-translate/http-loader
// app.config.ts
import { provideHttpClient } from '@angular/common/http';
import { importProvidersFrom } from '@angular/core';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient } from '@angular/common/http';
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
importProvidersFrom(
TranslateModule.forRoot({
defaultLanguage: 'en',
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient],
},
})
),
],
};// assets/i18n/en.json
{
"GREETING": "Hello, {{ name }}!",
"NAV": {
"HOME": "Home",
"ABOUT": "About",
"CONTACT": "Contact"
},
"ITEMS": {
"ONE": "1 item",
"OTHER": "{{ count }} items"
}
}// assets/i18n/de.json
{
"GREETING": "Hallo, {{ name }}!",
"NAV": {
"HOME": "Startseite",
"ABOUT": "Über uns",
"CONTACT": "Kontakt"
},
"ITEMS": {
"ONE": "1 Artikel",
"OTHER": "{{ count }} Artikel"
}
}Using ngx-translate in Components
import { Component } from '@angular/core';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
@Component({
standalone: true,
imports: [TranslateModule],
template: `
<h1>{{ 'GREETING' | translate: { name: username } }}</h1>
<nav>
<a>{{ 'NAV.HOME' | translate }}</a>
<a>{{ 'NAV.ABOUT' | translate }}</a>
</nav>
<button (click)="switchLang('de')">Deutsch</button>
<button (click)="switchLang('en')">English</button>
`,
})
export class AppComponent {
username = 'Alice';
constructor(private translate: TranslateService) {
translate.setDefaultLang('en');
translate.use('en');
}
switchLang(lang: string) {
this.translate.use(lang);
}
}// Using TranslateService in TypeScript
import { Component, inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({ standalone: true, template: '' })
export class SomeComponent {
private translate = inject(TranslateService);
showAlert() {
// Get translation as Observable
this.translate.get('GREETING', { name: 'Bob' }).subscribe(msg => {
alert(msg); // "Hello, Bob!"
});
}
getInstant(): string {
// Synchronous (only works after translations are loaded)
return this.translate.instant('NAV.HOME');
}
}Transloco (Modern Alternative)
Transloco is a modern, tree-shakeable alternative to ngx-translate with better TypeScript support:
ng add @jsverse/transloco
<!-- Using transloco pipe -->
<h1>{{ 'greeting' | transloco }}</h1>
<!-- Using structural directive -->
<ng-container *transloco="let t">
<h1>{{ t('greeting') }}</h1>
<p>{{ t('welcome', { name: username }) }}</p>
</ng-container>Choosing Between i18n Approaches
Feature | Built-in @angular/localize | ngx-translate | Transloco |
|---|---|---|---|
Language switching | Requires page reload | Runtime, no reload | Runtime, no reload |
Bundle size | One bundle per locale | One bundle + JSON files | One bundle + JSON files |
Performance | Best (compile-time) | Good | Good |
ICU plural support | Yes | Limited | Yes |
TypeScript safety | Limited | Limited | Excellent |
SSR support | Yes | Yes | Yes |
Best for | Static sites, high traffic | Dynamic apps | New projects |
Best Practices
Use meaningful i18n IDs (@Meaning|Description) to help translators understand context
Never concatenate translated strings — use ICU format for plurals and interpolation
Keep translation keys organized with namespaced prefixes (NAV.HOME, AUTH.LOGIN)
Provide context comments for translators about where/how text appears
Register locale data (registerLocaleData) for all locales you support
Use the date, number, and currency pipes — they are locale-aware automatically
Test translations with a pseudo-locale to catch hard-coded strings
en-x-pseudo during development to automatically detect strings that were not marked for translation — they stay in English while translated strings get replaced with accented characters.