IntlPull
Tutorial
20 min read

Internacionalización en Angular: Guía completa de i18n (2026)

Domina Angular i18n con esta guía paso a paso. Aprende a usar @angular/localize, extraer traducciones, manejar la pluralización, lazy load locales y desplegar aplicaciones Angular multilingües.

IntlPull Team
IntlPull Team
03 Feb 2026, 11:44 AM [PST]
On this page
Summary

Domina Angular i18n con esta guía paso a paso. Aprende a usar @angular/localize, extraer traducciones, manejar la pluralización, lazy load locales y desplegar aplicaciones Angular multilingües.

Filosofía i18n de Angular

Estás construyendo una aplicación Angular. Ahora necesitas soportar múltiples idiomas.

Tienes dos opciones:

  1. @angular/localize (solución oficial de Angular)

    • Traducción en tiempo de compilación (diferente compilación por idioma)
    • Mayor rendimiento en tiempo de ejecución
    • Mayor complejidad de compilación
  2. ngx-translate (biblioteca comunitaria)

    • Traducción en tiempo de ejecución (compilación única, cambio dinámico)
    • Despliegue más sencillo
    • Tiempo de ejecución ligeramente más lento

Esta guía cubre @angular/localize (recomendado por el equipo de Angular). Si necesitas un cambio de idioma dinámico en la misma instancia de la aplicación, utiliza ngx-translate en su lugar.


What You'll Build

Al final de este tutorial, tendrás una app Angular que:

  • ✅ Compila paquetes separados por idioma
  • ✅ Utiliza las API nativas de i18n de Angular
  • ✅ Maneja la pluralización correctamente (inglés, español, polaco, árabe, etc.)
  • ✅ Formatea fechas, números y monedas según la configuración regional
  • ✅ Se despliega con enrutamiento basado en la configuración regional (/es/, /es/, /fr/)
  • ✅ Integrates with CI/CD for automated translation

Tech stack:

  • Angular 17+
  • @angular/localize
  • Angular CLI
  • TypeScript

Step 1: Install @angular/localize

If starting a new Angular project:

Terminal
ng new my-app
cd my-app
ng add @angular/localize

This adds @angular/localize to your project and configures angular.json.

For existing projects:

Terminal
ng add @angular/localize

What it does:

  1. Installs @angular/localize package
  2. Adds polyfill to polyfills.ts
  3. Updates angular.json with i18n configuration

Step 2: Mark Strings for Translation

Basic Translation (i18n attribute)

Use the i18n attribute on any HTML element:

HTML
1<!-- Before: Hardcoded English -->
2<h1>Welcome to our app</h1>
3<button>Sign up</button>
4
5<!-- After: Marked for translation -->
6<h1 i18n>Welcome to our app</h1>
7<button i18n>Sign up</button>

That's it. Angular will extract these strings during build.


Attributes (i18n-*)

Translate element attributes:

HTML
1<!-- Translate placeholder -->
2<input
3  type="text"
4  placeholder="Enter your email"
5  i18n-placeholder
6/>
7
8<!-- Translate title -->
9<img
10  src="logo.png"
11  title="Company logo"
12  i18n-title
13/>
14
15<!-- Translate aria-label for accessibility -->
16<button
17  aria-label="Close dialog"
18  i18n-aria-label
19>
2021</button>

Component TypeScript (Template Literals)

For strings in TypeScript, use $localize:

TypeScript
1import { Component } from '@angular/core';
2
3@Component({
4  selector: 'app-root',
5  template: `
6    <h1>{{ title }}</h1>
7  `,
8})
9export class AppComponent {
10  // Mark string for translation
11  title = $localize`Welcome to our app`;
12
13  showMessage() {
14    // Can use in code
15    alert($localize`Settings saved successfully!`);
16  }
17}

Note: $localize is compile-time, so it's replaced at build time with the translated string.


Adding Context (Meaning & Description)

Help translators understand context:

HTML
1<!-- Add meaning and description -->
2<h1 i18n="site header|Welcome message for homepage@@homeTitle">
3  Welcome to our app
4</h1>
5
6<!-- Format: [meaning]|[description]@@[id] -->
  • Meaning: Category or context ("site header")
  • Description: Explanation ("Welcome message for homepage")
  • ID: Unique identifier (@@homeTitle). Recommended for tracking

Why use IDs?

  • Prevents duplicate extractions
  • Allows changing source text without breaking translations
  • Makes updates clearer

Example with ID:

HTML
1<button i18n="@@signupButton">Sign up</button>
2
3<!-- Later, you change the text: -->
4<button i18n="@@signupButton">Create account</button>

Translators see an update to signupButton, not a new string.


Step 3: Extract Translation Strings

Run Angular's extraction tool:

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

This creates:

src/locale/
  └── messages.xlf  (or .json, .xmb depending on format)

Default format is XLIFF (.xlf). It looks like:

XML
1<?xml version="1.0" encoding="UTF-8" ?>
2<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
3  <file source-language="en" datatype="plaintext">
4    <body>
5      <trans-unit id="homeTitle" datatype="html">
6        <source>Welcome to our app</source>
7        <context-group purpose="location">
8          <context context-type="sourcefile">src/app/app.component.html</context>
9        </context-group>
10        <note priority="1" from="description">Welcome message for homepage</note>
11        <note priority="1" from="meaning">site header</note>
12      </trans-unit>
13    </body>
14  </file>
15</xliff>

Alternative Formats

JSON (easier to read):

Terminal
ng extract-i18n --format json

Output:

JSON
1{
2  "locale": "en",
3  "translations": {
4    "homeTitle": {
5      "translation": "Welcome to our app",
6      "description": "Welcome message for homepage"
7    }
8  }
9}

We recommend JSON for easier human editing and TMS integration.


Step 4: Translate the Extracted Files

Manual Translation (Small Projects)

Copy the base file for each language:

Terminal
cp src/locale/messages.xlf src/locale/messages.es.xlf
cp src/locale/messages.xlf src/locale/messages.fr.xlf

Edit messages.es.xlf:

XML
1<trans-unit id="homeTitle">
2  <source>Welcome to our app</source>
3  <target>Bienvenido a nuestra aplicación</target>
4</trans-unit>

For JSON format:

JSON
1{
2  "locale": "es",
3  "translations": {
4    "homeTitle": {
5      "translation": "Bienvenido a nuestra aplicación"
6    }
7  }
8}

Automated Translation (IntlPull)

For production apps, use a TMS:

Terminal
npm install -D @intlpullhq/cli

Configure (.intlpull.json):

JSON
1{
2  "projectId": "proj_abc123",
3  "sourceLanguage": "en",
4  "targetLanguages": ["es", "fr", "de"],
5  "format": "xlf",
6  "outputDir": "src/locale"
7}

Workflow:

Terminal
1# 1. Extract
2ng extract-i18n --output-path src/locale
3
4# 2. Upload to IntlPull for translation
5npx @intlpullhq/cli upload
6
7# 3. Download translations
8npx @intlpullhq/cli download
9
10# 4. Build with translations
11ng build --localize

Result: Automated translation pipeline in CI/CD.


Step 5: Configure angular.json for Multiple Locales

Edit angular.json:

JSON
1{
2  "projects": {
3    "my-app": {
4      "i18n": {
5        "sourceLocale": "en",
6        "locales": {
7          "es": {
8            "translation": "src/locale/messages.es.xlf",
9            "baseHref": "/es/"
10          },
11          "fr": {
12            "translation": "src/locale/messages.fr.xlf",
13            "baseHref": "/fr/"
14          },
15          "de": {
16            "translation": "src/locale/messages.de.xlf",
17            "baseHref": "/de/"
18          }
19        }
20      },
21      "architect": {
22        "build": {
23          "configurations": {
24            "production": {
25              "localize": true
26            },
27            "es": {
28              "localize": ["es"]
29            },
30            "fr": {
31              "localize": ["fr"]
32            }
33          }
34        },
35        "serve": {
36          "configurations": {
37            "es": {
38              "browserTarget": "my-app:build:development,es"
39            },
40            "fr": {
41              "browserTarget": "my-app:build:development,fr"
42            }
43          }
44        }
45      }
46    }
47  }
48}

What this does:

  • Defines supported locales
  • Sets baseHref for each (URLs: /en/, /es/, /fr/)
  • Configures build to compile separate bundles

Step 6: Build and Serve

Development (Single Locale)

Run app in specific locale:

Terminal
ng serve --configuration=es

Opens app in Spanish at http://localhost:4200/


Production (All Locales)

Build all locales:

Terminal
ng build --localize

Output:

dist/my-app/
  ├── en/           (English build)
  │   ├── index.html
  │   ├── main.js
  │   └── ...
  ├── es/           (Spanish build)
  │   ├── index.html
  │   ├── main.js
  │   └── ...
  ├── fr/           (French build)
  │   └── ...

Each folder is a complete app build, translated.


Deploy

Static hosting (Vercel, Netlify, Firebase):

Deploy dist/my-app/ folder. Server routes:

  • /en/*dist/my-app/en/
  • /es/*dist/my-app/es/
  • /fr/*dist/my-app/fr/

Nginx example:

NGINX
1server {
2  listen 80;
3
4  location /en/ {
5    alias /var/www/my-app/en/;
6    try_files $uri $uri/ /en/index.html;
7  }
8
9  location /es/ {
10    alias /var/www/my-app/es/;
11    try_files $uri $uri/ /es/index.html;
12  }
13
14  location /fr/ {
15    alias /var/www/my-app/fr/;
16    try_files $uri $uri/ /fr/index.html;
17  }
18
19  # Redirect root to default language
20  location = / {
21    return 302 /en/;
22  }
23}

Step 7: Pluralization & ICU Messages

Pluralization

Different languages have different plural rules:

  • English: 1 item, 2 items (2 forms)
  • Spanish: same (2 forms)
  • Polish: 1, 2-4, 5+ (3 forms)
  • Arabic: 0, 1, 2, 3-10, 11-99, 100+ (6 forms!)

Angular's ICU syntax handles this:

HTML
1<span i18n>
2  {count, plural,
3    =0 {No items}
4    =1 {One item}
5    other {{{count}} items}
6  }
7</span>

In component:

TypeScript
export class CartComponent {
  count = 5;
}

Output:

  • count = 0: "No items"
  • count = 1: "One item"
  • count = 5: "5 items"

Translation (Spanish):

XML
1<trans-unit id="...">
2  <source>{count, plural, =0 {No items} =1 {One item} other {{{count}} items}}</source>
3  <target>{count, plural, =0 {Sin artículos} =1 {Un artículo} other {{{count}} artículos}}</target>
4</trans-unit>

Gender (Select)

HTML
1<span i18n>
2  {gender, select,
3    male {He liked your post}
4    female {She liked your post}
5    other {They liked your post}
6  }
7</span>

Component:

TypeScript
export class NotificationComponent {
  gender = 'female'; // or 'male', 'other'
}

Interpolation

Pass variables into translations:

HTML
<p i18n>
  Hello {{ userName }}, you have {{ messageCount }} new messages.
</p>

Translation:

XML
<source>Hello {{ userName }}, you have {{ messageCount }} new messages.</source>
<target>Hola {{ userName }}, tienes {{ messageCount }} mensajes nuevos.</target>

Variables ({{ userName }}) remain unchanged. Translators don't touch them.


Step 8: Format Dates, Numbers, Currencies

Angular provides built-in pipes:

Date Formatting

HTML
<p>{{ releaseDate | date:'fullDate' }}</p>

Automatic locale adaptation:

  • English: "Monday, January 15, 2026"
  • Spanish: "lunes, 15 de enero de 2026"
  • German: "Montag, 15. Januar 2026"

Provide locale in app:

TypeScript
1import { LOCALE_ID } from '@angular/core';
2import { registerLocaleData } from '@angular/common';
3import localeEs from '@angular/common/locales/es';
4import localeFr from '@angular/common/locales/fr';
5
6registerLocaleData(localeEs);
7registerLocaleData(localeFr);
8
9@NgModule({
10  providers: [
11    { provide: LOCALE_ID, useValue: 'es' } // or dynamically detect
12  ]
13})
14export class AppModule {}

Number Formatting

HTML
<p>{{ price | number:'1.2-2' }}</p>

Output:

  • English: "1,234.56"
  • German: "1.234,56"
  • French: "1 234,56"

Currency Formatting

HTML
<p>{{ price | currency:'USD' }}</p>

Output:

  • English: "$1,234.56"
  • Spanish (Spain): "1.234,56 US$"

Use locale-specific currency:

HTML
<p>{{ price | currency:currencyCode }}</p>
TypeScript
export class ProductComponent {
  currencyCode = 'EUR'; // or 'USD', 'GBP', etc.
}

Step 9: Runtime Locale Detection

Problem: Angular's @angular/localize is compile-time, so you need to serve the right build.

Solution: Detect user's locale and redirect to correct build.

Detect from URL

User visits /es/products → load Spanish build.

This is automatic if you configure baseHref in angular.json.


Detect from Browser

If user visits root /, redirect based on browser language:

index.html:

HTML
1<script>
2  // Detect browser language
3  const lang = navigator.language.split('-')[0]; // 'en', 'es', 'fr', etc.
4  const supportedLocales = ['en', 'es', 'fr', 'de'];
5
6  // Redirect to correct locale
7  if (supportedLocales.includes(lang)) {
8    window.location.href = `/${lang}/`;
9  } else {
10    window.location.href = '/en/'; // Default
11  }
12</script>

Use server to redirect based on Accept-Language header:

Nginx example:

NGINX
1location = / {
2  # Detect language from Accept-Language header
3  set $lang "en";
4  if ($http_accept_language ~* "^es") {
5    set $lang "es";
6  }
7  if ($http_accept_language ~* "^fr") {
8    set $lang "fr";
9  }
10
11  return 302 /$lang/;
12}

Step 10: Language Switcher

Create a language switcher component:

TypeScript
1import { Component } from '@angular/core';
2
3@Component({
4  selector: 'app-language-switcher',
5  template: `
6    <select (change)="changeLanguage($event)">
7      <option value="en">English</option>
8      <option value="es">Español</option>
9      <option value="fr">Français</option>
10      <option value="de">Deutsch</option>
11    </select>
12  `,
13})
14export class LanguageSwitcherComponent {
15  changeLanguage(event: Event) {
16    const select = event.target as HTMLSelectElement;
17    const locale = select.value;
18
19    // Redirect to new locale
20    const currentPath = window.location.pathname.split('/').slice(2).join('/');
21    window.location.href = `/${locale}/${currentPath}`;
22  }
23}

What it does:

  • User selects "Español"
  • Redirects from /en/products to /es/products
  • Angular serves Spanish build

Advanced: CI/CD Integration

GitHub Actions Example

.github/workflows/i18n-deploy.yml:

YAML
1name: Build & Deploy with i18n
2
3on:
4  push:
5    branches: [main]
6
7jobs:
8  build-deploy:
9    runs-on: ubuntu-latest
10    steps:
11      - uses: actions/checkout@v3
12
13      - name: Setup Node
14        uses: actions/setup-node@v3
15        with:
16          node-version: '18'
17
18      - name: Install dependencies
19        run: npm ci
20
21      - name: Extract i18n strings
22        run: ng extract-i18n --output-path src/locale
23
24      - name: Upload to IntlPull
25        run: npx @intlpullhq/cli upload
26        env:
27          INTLPULL_API_KEY: ${{ secrets.INTLPULL_API_KEY }}
28
29      - name: Download translations
30        run: npx @intlpullhq/cli download
31
32      - name: Build all locales
33        run: ng build --localize --configuration production
34
35      - name: Deploy to Vercel
36        uses: amondnet/vercel-action@v20
37        with:
38          vercel-token: ${{ secrets.VERCEL_TOKEN }}
39          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
40          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
41          working-directory: ./dist/my-app

Result: Every commit triggers translation sync + deployment.


ngx-translate Alternative (Runtime Switching)

If you need dynamic language switching (change language without page reload), use ngx-translate:

Setup

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

Configure

TypeScript
1import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
2import { TranslateHttpLoader } from '@ngx-translate/http-loader';
3import { HttpClient } from '@angular/common/http';
4
5export function HttpLoaderFactory(http: HttpClient) {
6  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
7}
8
9@NgModule({
10  imports: [
11    TranslateModule.forRoot({
12      loader: {
13        provide: TranslateLoader,
14        useFactory: HttpLoaderFactory,
15        deps: [HttpClient]
16      }
17    })
18  ]
19})
20export class AppModule {}

Usage

HTML
<h1>{{ 'WELCOME' | translate }}</h1>
<button (click)="changeLanguage('es')">Español</button>
TypeScript
1import { TranslateService } from '@ngx-translate/core';
2
3export class AppComponent {
4  constructor(private translate: TranslateService) {
5    translate.setDefaultLang('en');
6  }
7
8  changeLanguage(lang: string) {
9    this.translate.use(lang);
10  }
11}

Translation files:

assets/i18n/
  ├── en.json
  ├── es.json
  └── fr.json

en.json:

JSON
1{
2  "WELCOME": "Welcome to our app",
3  "SIGNUP": "Sign up"
4}

Comparison: @angular/localize vs ngx-translate

Feature@angular/localizengx-translate
TypeCompile-timeRuntime
Build sizeSeparate bundle per languageSingle bundle
Language switchingPage reload requiredInstant (no reload)
PerformanceFastest (compile-time)Slightly slower (runtime)
DeploymentMultiple buildsSingle build
OfficialYes (Angular team)No (community)
Use caseLarge apps, static contentApps needing dynamic switching

Recommendation:

  • Use @angular/localize for most apps (official, faster)
  • Use ngx-translate if you need in-app language switching without reloads

Production Optimization

Lazy Load Locales

If using ngx-translate, load translations on-demand:

TypeScript
this.translate.use('es'); // Triggers HTTP request to load es.json

AOT Compilation

Always build with AOT for production:

Terminal
ng build --localize --configuration production

Benefits:

  • Smaller bundle sizes
  • Faster rendering
  • Compile-time error detection

Checklist: Is Your Angular App i18n-Ready?

  • ✅ All user-facing strings use i18n or $localize
  • ✅ Plurals use ICU message format
  • ✅ Dates/numbers use Angular pipes (auto-locale formatting)
  • angular.json configured with all target locales
  • ✅ Translations extracted and stored in src/locale/
  • ✅ Production builds compiled per locale (ng build --localize)
  • ✅ Server configured to serve correct locale bundle
  • ✅ Language switcher implemented
  • ✅ CI/CD syncs translations automatically

Next Steps

  1. Add i18n to existing app: Run ng add @angular/localize
  2. Mark strings: Add i18n attributes throughout app
  3. Extract: ng extract-i18n
  4. Translate: Use IntlPull or manual translation
  5. Configure: Update angular.json with locales
  6. Build: ng build --localize
  7. Deploy: Configure server routing

Want automated translation workflow? Try IntlPull. It includes CI/CD templates and production examples for Angular.


Further Reading

Angular's built-in i18n is powerful once you understand it. Start localizing today and reach global users.

Tags
angular
angular-i18n
localization
internationalization
ngx-translate
@angular/localize
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.