cyberangles guide

Internationalizing Your Angular App: A Complete Guide

In today’s global digital landscape, building applications that resonate with users worldwide is no longer optional—it’s a necessity. **Internationalization (i18n)**—the process of designing apps to adapt to multiple languages, regions, and cultural norms—ensures your Angular application feels native to users regardless of their location. Angular provides robust built-in tools for i18n, eliminating the need for third-party libraries in most cases. This guide will walk you through every step of internationalizing your Angular app, from marking text for translation to handling advanced scenarios like pluralization and right-to-left (RTL) languages. By the end, you’ll be able to ship a globally accessible app that delights users in any locale.

Table of Contents

  1. Prerequisites
  2. What is Angular i18n?
  3. Setting Up Angular i18n
  4. Marking Text for Translation
  5. Extracting Translations
  6. Managing Translation Files
  7. Configuring Angular for Multiple Locales
  8. Runtime vs. Compile-Time Internationalization
  9. Advanced Translation Scenarios
  10. Handling Right-to-Left (RTL) Languages
  11. Testing Internationalized Apps
  12. Best Practices for Angular i18n
  13. Conclusion
  14. References

Prerequisites

Before diving in, ensure you have:

  • Node.js (v14+ recommended) and npm installed.
  • Angular CLI (v12+ recommended): Install with npm install -g @angular/cli.
  • A basic Angular app (new or existing). Create one with ng new my-i18n-app if starting fresh.

What is Angular i18n?

Angular i18n is a framework-native solution for adapting apps to global audiences. It uses the XLIFF (XML Localization Interchange File Format) standard for translation files and supports:

  • Translation of static text in templates.
  • Locale-specific formatting (dates, numbers, currencies).
  • Pluralization, gender, and dynamic content via ICU (International Components for Unicode) expressions.
  • Compile-time or runtime language switching.

Angular’s i18n tooling is built on @angular/localize, a library that handles translation extraction, compilation, and rendering.

Setting Up Angular i18n

Angular’s i18n is enabled by default in new apps, but verify you have the required dependencies:

  1. Ensure @angular/localize is installed:
    Most Angular CLI apps include this, but if not, install it with:

    npm install @angular/localize --save  
  2. Import @angular/localize/init (for runtime use, optional):
    If you need runtime translation (e.g., dynamic language switching), add this import to your polyfills.ts:

    import '@angular/localize/init';  

Marking Text for Translation

The first step is to identify text in your templates that needs translation using the i18n attribute.

Basic Text Translation

Add the i18n attribute to any HTML element with static text:

<!-- src/app/app.component.html -->  
<h1 i18n>Welcome to My App</h1>  
<p i18n>This is a sample paragraph that will be translated.</p>  

Adding Context for Translators

To help translators, add a description and meaning to the i18n attribute (format: i18n="meaning|description@@id"):

  • meaning: Distinguishes identical text with different contexts (e.g., “table” as furniture vs. data table).
  • description: Notes for translators (e.g., “Greeting on the homepage”).
  • id: A unique identifier (optional, auto-generated if omitted).

Example:

<button i18n="User dashboard|Button to submit form@@submitButton">Submit</button>  

Inline Text vs. Attributes

Translate element attributes (e.g., alt, title) using i18n-attribute:

<img src="logo.png" i18n-alt="Company logo@@logoAlt" alt="Company Logo">  

Extracting Translations

Once text is marked, extract it into a source translation file using the Angular CLI:

  1. Run the extraction command:

    ng extract-i18n --output-path src/locale  

    This generates a messages.xlf file in src/locale/. The --output-path flag specifies where to save the file.

  2. Understanding the XLIFF File:
    messages.xlf is the source locale file (default: en-US). It contains <trans-unit> elements for each marked text, with:

    • <source>: The original text (e.g., “Welcome to My App”).
    • <target>: Empty initially (to be filled by translators).
    • id: The unique identifier (auto-generated or your custom @@id).

    Sample messages.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" datatype="plaintext" original="ng2.template">  
        <body>  
          <trans-unit id="welcomeHeader" datatype="html">  
            <source>Welcome to My App</source>  
            <context-group purpose="location">  
              <context context-type="sourcefile">src/app/app.component.html</context>  
              <context context-type="linenumber">1</context>  
            </context-group>  
          </trans-unit>  
        </body>  
      </file>  
    </xliff>  

Managing Translation Files

For each target locale (e.g., French, Spanish), create a copy of messages.xlf and translate the <target> elements.

Step 1: Create Locale-Specific Files

Duplicate messages.xlf for each locale (e.g., messages.fr.xlf for French, messages.es.xlf for Spanish) in src/locale/.

Step 2: Translate the Text

Edit the <target> elements in each locale file. For messages.fr.xlf:

<trans-unit id="welcomeHeader" datatype="html">  
  <source>Welcome to My App</source>  
  <target>Bienvenue dans mon application</target> <!-- French translation -->  
</trans-unit>  

Tools for Translation

Manually editing XLIFF files is error-prone. Use tools to simplify:

  • Poedit: Open-source XLIFF editor (supports translation memory).
  • XLIFF Translator: Online tool (https://xlifftool.com/).
  • Angular i18n Editor: VS Code extension for editing XLIFF files.

Configuring Angular for Multiple Locales

Update your app to build separate bundles for each locale by modifying angular.json.

Step 1: Update angular.json

Add a locales section under your project’s i18n options. Define:

  • sourceLocale: Your app’s default language (e.g., en-US).
  • locales: A map of locale codes to their translation files.

Example angular.json snippet:

{  
  "projects": {  
    "my-i18n-app": {  
      "i18n": {  
        "sourceLocale": "en-US",  
        "locales": {  
          "fr": "src/locale/messages.fr.xlf",   // French  
          "es": "src/locale/messages.es.xlf"    // Spanish  
        }  
      },  
      "architect": {  
        "build": {  
          "configurations": {  
            "fr": {  
              "localize": ["fr"],  
              "outputPath": "dist/my-i18n-app/fr"  // Output for French  
            },  
            "es": {  
              "localize": ["es"],  
              "outputPath": "dist/my-i18n-app/es"  // Output for Spanish  
            }  
          }  
        }  
      }  
    }  
  }  
}  

Step 2: Build for a Specific Locale

Build your app for French with:

ng build --configuration=fr  

This generates a French-only bundle in dist/my-i18n-app/fr. Serve it locally with:

npx serve dist/my-i18n-app/fr  

Runtime vs. Compile-Time Internationalization

Angular supports two i18n approaches:

Compile-Time (Default)

  • How it works: Generates separate bundles for each locale during build.
  • Pros: Smaller bundle sizes, faster load times, better performance.
  • Cons: No dynamic language switching (requires a page reload).

Use this for most apps (e.g., marketing sites, dashboards with fixed locales).

Runtime

  • How it works: Loads translations dynamically at runtime, allowing in-app language switching.
  • Pros: Switch languages without reloading; single bundle.
  • Cons: Larger initial bundle, slightly slower rendering.

Enable runtime i18n with @angular/localize and $localize:

  1. Load translations at runtime:

    // src/app/app.component.ts  
    import { loadTranslations } from '@angular/localize';  
    import frTranslations from '../locale/messages.fr.xlf';  
    
    loadTranslations(frTranslations); // Load French translations dynamically  
  2. Switch languages on demand:
    Use a service to load translations for a new locale and trigger a re-render.

Third-Party Libraries (Legacy)

Older apps may use ngx-translate (now deprecated in favor of Angular’s native i18n). Prefer @angular/localize for new projects.

Advanced Translation Scenarios

Pluralization with ICU Expressions

Use ICU syntax to handle plurals, gender, and dynamic content. Example:

<p i18n>  
  {count, plural,  
    =0 {No messages}  
    =1 {1 message}  
    other {{{count}} messages}  
  }  
</p>  

Here, count is a component property. Translators will see the ICU structure and adapt it to the target language (e.g., Arabic has dual forms).

Date, Number, and Currency Formatting

Angular’s built-in pipes support locale-specific formatting:

  • DatePipe: {{ today | date:'long':'':'fr' }} (French date: “12 août 2024”).
  • CurrencyPipe: {{ price | currency:'EUR':'symbol':'':'fr' }} (French euro: “10,50 €”).
  • DecimalPipe: {{ rating | number:'1.2-2':'es' }} (Spanish decimal: “4,50”).

Gender and Select Statements

Use select for gender or categories:

<p i18n>  
  {user.gender, select,  
    male {His name is {{user.name}}}  
    female {Her name is {{user.name}}}  
    other {Their name is {{user.name}}}  
  }  
</p>  

Handling Right-to-Left (RTL) Languages

Languages like Arabic, Hebrew, or Persian require RTL layout. Angular simplifies this:

1. Enable RTL in angular.json

Add an RTL configuration in angular.json:

"configurations": {  
  "ar-rtl": {  
    "localize": ["ar"],  
    "outputPath": "dist/my-i18n-app/ar",  
    "i18nMissingTranslation": "error",  
    "rtl": true  // Enables RTL processing  
  }  
}  

2. Dynamically Set Direction

Add dir attribute to your root element and update it based on the locale:

<!-- src/index.html -->  
<html [dir]="isRtl ? 'rtl' : 'ltr'">  

In your component:

isRtl = this.localeService.currentLocale === 'ar'; // Check current locale  

3. RTL Styling

Use Angular’s RTL-aware CSS mixins or :host-context for locale-specific styles:

/* src/app/app.component.scss */  
:host-context([dir="rtl"]) .header {  
  padding-left: 0;  
  padding-right: 16px;  
}  

Testing Internationalized Apps

1. Unit Testing with Locales

Use Angular’s LOCALE_ID token to test components with specific locales:

// src/app/app.component.spec.ts  
import { LOCALE_ID } from '@angular/core';  

describe('AppComponent', () => {  
  beforeEach(async () => {  
    await TestBed.configureTestingModule({  
      declarations: [AppComponent],  
      providers: [{ provide: LOCALE_ID, useValue: 'fr' }] // Test in French  
    }).compileComponents();  
  });  

  it('should render translated title', () => {  
    const fixture = TestBed.createComponent(AppComponent);  
    fixture.detectChanges();  
    const compiled = fixture.nativeElement as HTMLElement;  
    expect(compiled.querySelector('h1')?.textContent).toContain('Bienvenue dans mon application');  
  });  
});  

2. End-to-End Testing

Use Cypress or Playwright to test locale-specific builds:

// cypress/e2e/fr.spec.cy.ts  
describe('French locale', () => {  
  it('displays French text', () => {  
    cy.visit('/fr'); // Visit the French build  
    cy.contains('Bienvenue dans mon application').should('exist');  
  });  
});  

Best Practices for Angular i18n

1. Keep Translations DRY

Reuse translations with unique ids to avoid duplication:

<!-- Reuse the same translation -->  
<p i18n="@@common.greeting">Hello</p>  
<button i18n="@@common.greeting">Hello</button>  

2. Avoid Hardcoded Text

Never hardcode text in components or services. Use $localize for TypeScript strings:

// src/app/app.component.ts  
import { $localize } from '@angular/localize';  

title = $localize`:@@app.title:My i18n App`;  

3. Automate Extraction

Add extraction to your CI/CD pipeline to catch untranslated text early:

ng extract-i18n --output-path src/locale --format xlf2  

4. Optimize Bundle Size

  • Use compile-time i18n for smaller bundles.
  • Lazy-load non-critical locales.

5. Test Locale-Specific Formatting

Verify dates, numbers, and currencies render correctly for target locales (e.g., 1,000.50 in en-US vs 1 000,50 in fr-FR).

Conclusion

Internationalizing your Angular app unlocks global audiences and improves user experience. By following this guide, you’ve learned to:

  • Mark text for translation with i18n.
  • Extract and manage XLIFF translation files.
  • Configure Angular for multiple locales.
  • Handle advanced scenarios like pluralization and RTL.

Angular’s built-in i18n tools simplify the process, whether you need compile-time or runtime translation. Start small (translate key pages) and expand to full localization!

References