angular-localize

Last updated: July 31, 2025Author: Jakub Pomykała

Installation

First, add the Angular localize package to your Angular project:

ng add @angular/localize

This command will:

  • Install the @angular/localize package
  • Add necessary imports to your polyfills.ts
  • Update your angular.json configuration

Angular Configuration

Configure your Angular project for internationalization in angular.json:

{
  "projects": {
    "your-app": {
      "i18n": {
        "sourceLocale": "en-US",
        "locales": {
          "pl": {
            "translation": "src/locale/messages.pl.xlf",
            "baseHref": ""
          },
          "es": {
            "translation": "src/locale/messages.es.xlf",
            "baseHref": ""
          },
          "fr": {
            "translation": "src/locale/messages.fr.xlf",
            "baseHref": ""
          }
        }
      },
      "architect": {
        "build": {
          "configurations": {
            "production": {
              "aot": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            },
            "pl": {
              "aot": true,
              "outputPath": "dist/pl/",
              "i18nFile": "src/locale/messages.pl.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "pl"
            },
            "es": {
              "aot": true,
              "outputPath": "dist/es/",
              "i18nFile": "src/locale/messages.es.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "es"
            },
            "fr": {
              "aot": true,
              "outputPath": "dist/fr/",
              "i18nFile": "src/locale/messages.fr.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "fr"
            }
          }
        }
      }
    }
  }
}

Usage Examples

Basic Text Translation

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1 i18n="@@homepage.title">Welcome to our website</h1>
    <p i18n="@@homepage.description">This is a multilingual Angular application</p>
  `
})
export class AppComponent {
  title = 'my-angular-app';
}

Component with Variables

// product.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-product',
  template: `
    <div class="product-card">
      <h3>{{ product.name }}</h3>
      <p i18n="@@product.price">Price: {{ product.price | currency:'USD':'symbol':'1.2-2' }}</p>
      <p i18n="@@product.available">Available since: {{ product.availableDate | date:'mediumDate' }}</p>
      <p i18n="@@product.count" [attr.data-count]="product.count">
        You have {{ product.count }} {product.count, plural, =0 {items} =1 {item} other {items}} in your cart
      </p>
    </div>
  `
})
export class ProductComponent {
  @Input() product = {
    name: 'Sample Product',
    price: 29.99,
    availableDate: new Date(),
    count: 0
  };
}
// navigation.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-navigation',
  template: `
    <nav>
      <a routerLink="/" i18n="@@navigation.home">Home</a>
      <a routerLink="/about" i18n="@@navigation.about">About</a>
      <a routerLink="/contact" i18n="@@navigation.contact">Contact</a>
    </nav>
  `
})
export class NavigationComponent {}

Language Switcher

// language-switcher.component.ts
import { Component, Inject, LOCALE_ID } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({
  selector: 'app-language-switcher',
  template: `
    <div class="language-switcher">
      <button *ngFor="let locale of locales"
              [class.active]="locale.code === currentLocale"
              (click)="switchLanguage(locale.code)">
        {{ locale.name }}
      </button>
    </div>
  `
})
export class LanguageSwitcherComponent {
  currentLocale: string;

  locales = [
    { code: 'en-US', name: 'English' },
    { code: 'pl', name: 'Polski' },
    { code: 'es', name: 'Español' },
    { code: 'fr', name: 'Français' }
  ];

  constructor(@Inject(LOCALE_ID) locale: string, @Inject(DOCUMENT) private document: Document) {
    this.currentLocale = locale;
  }

  switchLanguage(locale: string) {
    const url = this.document.location.origin;
    this.document.location.href = `${url}/${locale}`;
  }
}

Service with Translations

// notification.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class NotificationService {

  getWelcomeMessage(userName: string): string {
    return $localize`:@@welcome.message:Welcome back, ${userName}!`;
  }

  getItemCountMessage(count: number): string {
    return $localize`:@@cart.items:You have ${count}:INTERPOLATION: {count, plural, =0 {items} =1 {item} other {items}} in your cart`;
  }
}

Pipe with Localization

// relative-time.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'relativeTime'
})
export class RelativeTimePipe implements PipeTransform {

  transform(date: Date): string {
    const now = new Date();
    const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);

    if (diffInSeconds < 60) {
      return $localize`:@@time.justNow:Just now`;
    } else if (diffInSeconds < 3600) {
      const minutes = Math.floor(diffInSeconds / 60);
      return $localize`:@@time.minutesAgo:${minutes}:INTERPOLATION: minutes ago`;
    } else if (diffInSeconds < 86400) {
      const hours = Math.floor(diffInSeconds / 3600);
      return $localize`:@@time.hoursAgo:${hours}:INTERPOLATION: hours ago`;
    } else {
      const days = Math.floor(diffInSeconds / 86400);
      return $localize`:@@time.daysAgo:${days}:INTERPOLATION: days ago`;
    }
  }
}

Translation Extraction and Build

Extract translatable text from your application:

# Extract messages to XLIFF format
ng extract-i18n

# Build for specific locale
ng build --configuration=pl

# Build for all locales
ng build --localize

SimpleLocalize Configuration

To manage translations with SimpleLocalize, you need to set up the CLI tool.

# macOS / Linux / Windows (WSL) curl -s https://get.simplelocalize.io/2.9/install | bash # Windows (PowerShell) . { iwr -useb https://get.simplelocalize.io/2.9/install-windows } | iex; # npm npm install @simplelocalize/cli
# macOS / Linux / Windows (WSL)
curl -s https://get.simplelocalize.io/2.9/install | bash

# Windows (PowerShell)
. { iwr -useb https://get.simplelocalize.io/2.9/install-windows } | iex;

# npm
npm install @simplelocalize/cli

Create simplelocalize.yml in your project root:

apiKey: YOUR_PROJECT_API_KEY

uploadFormat: xliff
uploadPath: ./src/locale/messages.xlf
uploadLanguageKey: en
uploadOptions:
  - REPLACE_TRANSLATION_IF_FOUND

downloadPath: ./src/locale/messages.{lang}.xlf
downloadLanguageKeys: ['pl', 'es', 'fr']
downloadFormat: xliff

Work with the CLI to manage your translations:

# Extract translatable text
ng extract-i18n

# Upload extracted messages to SimpleLocalize
simplelocalize upload

# Download translations for all languages
simplelocalize download

# Build for all locales
ng build --localize

Deployment Strategy

Multiple Build Approach

# Build for all locales (creates separate bundles)
ng build --localize

# Deploy each locale to different paths
# dist/en-US/ -> /
# dist/pl/ -> /pl/
# dist/es/ -> /es/
# dist/fr/ -> /fr/

Server Configuration (nginx example)

server {
    listen 80;
    server_name example.com;

    # Default to English
    location / {
        root /var/www/dist/en-US;
        try_files $uri $uri/ /index.html;
    }

    # Polish version
    location /pl/ {
        alias /var/www/dist/pl/;
        try_files $uri $uri/ /pl/index.html;
    }

    # Spanish version
    location /es/ {
        alias /var/www/dist/es/;
        try_files $uri $uri/ /es/index.html;
    }

    # French version
    location /fr/ {
        alias /var/www/dist/fr/;
        try_files $uri $uri/ /fr/index.html;
    }
}

Key Features

  • Official Angular Solution: Built and maintained by the Angular team
  • Compile-time Optimization: Translations are processed at build time for optimal performance
  • AOT Compilation: Full Ahead-of-Time compilation support
  • Tree Shaking: Unused translations are removed from the bundle
  • ICU Message Format: Support for pluralization, gender, and complex formatting
  • Separate Bundles: Each locale gets its own optimized bundle

Best Practices

1. Use Meaningful IDs

// Good
<p i18n="@@user.welcome.message">Welcome, {{ userName }}!</p>

// Avoid
<p i18n>Welcome, {{ userName }}!</p>

2. Provide Context

// Include description and meaning
<button i18n="@@button.save|Save button in the form">Save</button>

3. Handle Pluralization

// Use ICU expressions for plurals
<span i18n="@@item.count">
  {count, plural, =0 {no items} =1 {one item} other {{{count}} items}}
</span>

Resources

Was this helpful?