Docs
/
Angular
Chapter 19

19 — Internationalization (i18n)

Two Approaches

@angular/localize (Built-in)ngx-translate
TypeBuild-time i18nRuntime i18n
BundleOne build per localeSingle build, load JSON at runtime
PerformanceFaster (static)Slightly slower (dynamic)
Switch languageRequires page reloadInstant switch
Best forLarge apps, SEO, static contentDynamic content, single deployment

Option 1: @angular/localize (Built-Time)

ng add @angular/localize

Mark Strings for Translation

<!-- i18n attribute -->
<h1 i18n="Page title|Welcome heading">Welcome to our app</h1>

<!-- With description and meaning -->
<p i18n="user greeting|Greeting shown after login">Hello, {{ user.name }}!</p>

<!-- i18n on attributes -->
<img [src]="logo" i18n-alt="Logo alt text" alt="Company logo">
<input i18n-placeholder placeholder="Search...">

<!-- ICU expressions for plurals -->
<p i18n>
  { count, plural,
    =0 { No items }
    =1 { 1 item }
    other { {{ count }} items }
  }
</p>

<!-- ICU for select (gender, role, etc.) -->
<p i18n>
  { role, select,
    admin { Administrator }
    editor { Editor }
    other { User }
  }
</p>

Extract and Translate

# Extract all i18n strings to XLIFF file
ng extract-i18n --output-path src/locale

# Creates: src/locale/messages.xlf
<!-- src/locale/messages.es.xlf (Spanish translation) -->
<trans-unit id="..." datatype="html">
  <source>Welcome to our app</source>
  <target>Bienvenido a nuestra aplicación</target>
</trans-unit>

Build Per Locale

// angular.json
"i18n": {
  "sourceLocale": "en",
  "locales": {
    "es": "src/locale/messages.es.xlf",
    "fr": "src/locale/messages.fr.xlf"
  }
},
"build": {
  "configurations": {
    "es": { "localize": ["es"] },
    "fr": { "localize": ["fr"] },
    "production": { "localize": true }   // Build ALL locales
  }
}
ng build --configuration=es       # Spanish build only
ng build --localize               # All locale builds

Option 2: ngx-translate (Runtime)

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

Setup

// app.config.ts
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

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

export const appConfig: ApplicationConfig = {
  providers: [
    importProvidersFrom(
      TranslateModule.forRoot({
        defaultLanguage: 'en',
        loader: {
          provide: TranslateLoader,
          useFactory: HttpLoaderFactory,
          deps: [HttpClient],
        },
      }),
    ),
  ],
};

Translation Files

// assets/i18n/en.json
{
  "WELCOME": "Welcome to our app",
  "GREETING": "Hello, {{ name }}!",
  "NAV": {
    "HOME": "Home",
    "ABOUT": "About",
    "CONTACT": "Contact"
  },
  "ITEMS": {
    "NONE": "No items",
    "ONE": "1 item",
    "MANY": "{{ count }} items"
  }
}
// assets/i18n/es.json
{
  "WELCOME": "Bienvenido a nuestra aplicación",
  "GREETING": "¡Hola, {{ name }}!",
  "NAV": {
    "HOME": "Inicio",
    "ABOUT": "Acerca de",
    "CONTACT": "Contacto"
  }
}

Using Translations

import { TranslateModule, TranslateService } from '@ngx-translate/core';

@Component({
  imports: [TranslateModule],
  template: `
    <!-- Pipe (most common) -->
    <h1>{{ 'WELCOME' | translate }}</h1>
    <p>{{ 'GREETING' | translate:{ name: user.name } }}</p>

    <!-- Directive -->
    <span [translate]="'NAV.HOME'"></span>

    <!-- Nested keys -->
    <a>{{ 'NAV.ABOUT' | translate }}</a>

    <!-- Language switcher -->
    <select (change)="switchLang($any($event.target).value)">
      @for (lang of langs; track lang) {
        <option [value]="lang">{{ lang | uppercase }}</option>
      }
    </select>
  `,
})
export class AppComponent {
  private translate = inject(TranslateService);
  langs = ['en', 'es', 'fr'];

  constructor() {
    this.translate.setDefaultLang('en');
    const browserLang = this.translate.getBrowserLang();
    this.translate.use(browserLang?.match(/en|es|fr/) ? browserLang : 'en');
  }

  switchLang(lang: string) {
    this.translate.use(lang);   // Instant switch — no reload
  }
}

In TypeScript (Service)

private translate = inject(TranslateService);

showMessage() {
  this.translate.get('GREETING', { name: 'Alice' }).subscribe(msg => {
    alert(msg);   // "Hello, Alice!" or "¡Hola, Alice!"
  });

  // Synchronous (if translation is already loaded)
  const msg = this.translate.instant('WELCOME');
}

RTL (Right-to-Left) Support

// Detect and apply direction
@Component({
  template: `<div [dir]="direction()">...</div>`,
})
export class AppComponent {
  private translate = inject(TranslateService);

  direction = computed(() => {
    const rtlLangs = ['ar', 'he', 'fa', 'ur'];
    return rtlLangs.includes(this.translate.currentLang) ? 'rtl' : 'ltr';
  });
}
// Use logical properties for RTL support
.card {
  margin-inline-start: 1rem;    // instead of margin-left
  padding-inline-end: 1rem;     // instead of padding-right
  text-align: start;            // instead of text-align: left
  border-inline-start: 2px solid blue;
}

// RTL-specific overrides
[dir='rtl'] {
  .icon-arrow { transform: scaleX(-1); }
}

Date/Number/Currency Localization

import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';

registerLocaleData(localeEs);
registerLocaleData(localeFr);

// Provide locale
providers: [
  { provide: LOCALE_ID, useValue: 'es' },
]
<!-- Automatically uses the registered locale -->
{{ today | date:'longDate' }}      <!-- 15 de enero de 2024 -->
{{ price | currency:'EUR' }}       <!-- 1.499,99 € -->
{{ ratio | percent }}              <!-- 85 % -->

Key Takeaways

  • Use @angular/localize for build-time i18n — best performance and SEO
  • Use ngx-translate for runtime language switching without reloading
  • Use ICU expressions for plurals and gender-based text
  • Use CSS logical properties (margin-inline-start) for RTL support
  • Register locale data for proper date/number/currency formatting
  • Store user's language preference in localStorage or user profile
  • Autodetect browser language on first visit: navigator.language