AngularJSInternationalization (i18n)

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

Bash
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:

HTML
<!-- 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>

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

Bash
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:

Bash
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:

Bash
cp src/locale/messages.xlf src/locale/messages.de.xlf
cp src/locale/messages.xlf src/locale/messages.fr.xlf

XML
<!-- 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

JSON
{
  "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

Bash
# 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:

HTML
<!-- 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:

TS
// 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' },
  ],
};

HTML
<!-- 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.

Bash
npm install @ngx-translate/core @ngx-translate/http-loader

TS
// 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],
        },
      })
    ),
  ],
};

JSON
// assets/i18n/en.json
{
  "GREETING": "Hello, {{ name }}!",
  "NAV": {
    "HOME": "Home",
    "ABOUT": "About",
    "CONTACT": "Contact"
  },
  "ITEMS": {
    "ONE": "1 item",
    "OTHER": "{{ count }} items"
  }
}

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

TS
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);
  }
}

TS
// 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:

Bash
ng add @jsverse/transloco

HTML
<!-- 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

Tip
Use a pseudo-locale like 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.