How to Translate an Angular App: Complete Guide

Learn how to localize your Angular app using @angular/localize or ngx-translate. This comprehensive guide covers string translation, pluralization, date formatting, and best practices for An

  • date icon

    Wednesday, Nov 19, 2025

How to Translate an Angular App: Complete Guide

Translating your Angular app is essential for reaching a global audience. Angular provides built-in internationalization (i18n) support through @angular/localize, and there’s also the popular ngx-translate library. This guide will walk you through both approaches for translating an Angular app.

Understanding Angular Localization

Angular supports internationalization through:

  1. @angular/localize - Built-in i18n solution (recommended for production)
  2. ngx-translate - Popular third-party library (easier setup, runtime translations)

We’ll cover both approaches in this guide.

Method 1: Using @angular/localize (Built-in i18n)

Angular’s built-in i18n is the official solution and works at build time.

Step 1: Add Angular i18n Support

ng add @angular/localize

This command adds the necessary dependencies and configuration.

Step 2: Mark Text for Translation

Use the i18n attribute in your templates:

Before:

<h1>Welcome to our app!</h1>

After:

<h1 i18n="@@welcome">Welcome to our app!</h1>

Or with description:

<h1 i18n="Welcome message|@@welcome">Welcome to our app!</h1>

Step 3: Extract Translation Messages

Extract translatable strings to a messages file:

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

This creates messages.xlf file with all translatable strings.

Step 4: Create Translation Files

Create translation files for each language:

src/locale/messages.es.xlf (Spanish):

<?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="welcome" datatype="html">
        <source>Welcome to our app!</source>
        <target>¡Bienvenido a nuestra aplicación!</target>
      </trans-unit>
    </body>
  </file>
</xliff>

src/locale/messages.fr.xlf (French):

<?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="welcome" datatype="html">
        <source>Welcome to our app!</source>
        <target>Bienvenue dans notre application!</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Step 5: Configure angular.json

Update angular.json to include locale configurations:

{
  "projects": {
    "my-app": {
      "i18n": {
        "sourceLocale": "en",
        "locales": {
          "es": {
            "translation": "src/locale/messages.es.xlf"
          },
          "fr": {
            "translation": "src/locale/messages.fr.xlf"
          }
        }
      },
      "architect": {
        "build": {
          "configurations": {
            "es": {
              "localize": ["es"]
            },
            "fr": {
              "localize": ["fr"]
            }
          }
        },
        "serve": {
          "configurations": {
            "es": {
              "browserTarget": "my-app:build:es"
            },
            "fr": {
              "browserTarget": "my-app:build:fr"
            }
          }
        }
      }
    }
  }
}

Step 6: Build for Different Locales

Build the app for each locale:

ng build --configuration=es
ng build --configuration=fr

Step 7: Handle Variables in Translations

Use interpolation:

<p i18n="User greeting|@@userGreeting">Hello, {{ userName }}!</p>

In the XLF file:

<trans-unit id="userGreeting" datatype="html">
  <source>Hello, <x id="INTERPOLATION"/>!</source>
  <target>¡Hola, <x id="INTERPOLATION"/>!</target>
</trans-unit>

Step 8: Pluralization

Use ICU message format:

<span i18n="Item count|@@itemsCount">
  {count, plural, =0 {No items} one {1 item} other {{{count}} items}}
</span>

Method 2: Using ngx-translate (Runtime Translations)

ngx-translate is easier to set up and allows runtime language switching.

Step 1: Install ngx-translate

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

Or with yarn:

yarn add @ngx-translate/core @ngx-translate/http-loader

Step 2: Create Translation Files

Create JSON files for each language:

src/assets/i18n/en.json (English - default):

{
  "app": {
    "welcome": "Welcome to our app!",
    "title": "My Angular App"
  },
  "button": {
    "submit": "Submit",
    "cancel": "Cancel",
    "delete": "Delete"
  },
  "error": {
    "network": "Network error. Please try again.",
    "notFound": "Page not found"
  },
  "items": {
    "count": "{count, plural, =0 {No items} one {# item} other {# items}}"
  },
  "user": {
    "greeting": "Hello, {{name}}! You have {{count}} messages.",
    "greeting_zero": "Hello, {{name}}! You have no messages.",
    "greeting_one": "Hello, {{name}}! You have {{count}} message.",
    "greeting_other": "Hello, {{name}}! You have {{count}} messages."
  }
}

src/assets/i18n/es.json (Spanish):

{
  "app": {
    "welcome": "¡Bienvenido a nuestra aplicación!",
    "title": "Mi Aplicación Angular"
  },
  "button": {
    "submit": "Enviar",
    "cancel": "Cancelar",
    "delete": "Eliminar"
  },
  "error": {
    "network": "Error de red. Por favor, inténtalo de nuevo.",
    "notFound": "Página no encontrada"
  },
  "items": {
    "count": "{count, plural, =0 {No hay elementos} one {# elemento} other {# elementos}}"
  },
  "user": {
    "greeting": "¡Hola, {{name}}! Tienes {{count}} mensajes.",
    "greeting_zero": "¡Hola, {{name}}! No tienes mensajes.",
    "greeting_one": "¡Hola, {{name}}! Tienes {{count}} mensaje.",
    "greeting_other": "¡Hola, {{name}}! Tienes {{count}} mensajes."
  }
}

src/assets/i18n/fr.json (French):

{
  "app": {
    "welcome": "Bienvenue dans notre application!",
    "title": "Mon Application Angular"
  },
  "button": {
    "submit": "Soumettre",
    "cancel": "Annuler",
    "delete": "Supprimer"
  },
  "error": {
    "network": "Erreur réseau. Veuillez réessayer.",
    "notFound": "Page non trouvée"
  },
  "items": {
    "count": "{count, plural, =0 {Aucun élément} one {# élément} other {# éléments}}"
  },
  "user": {
    "greeting": "Bonjour, {{name}}! Vous avez {{count}} messages.",
    "greeting_zero": "Bonjour, {{name}}! Vous n'avez aucun message.",
    "greeting_one": "Bonjour, {{name}}! Vous avez {{count}} message.",
    "greeting_other": "Bonjour, {{name}}! Vous avez {{count}} messages."
  }
}

Step 3: Configure App Module

src/app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

import { AppComponent } from './app.component';

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      },
      defaultLanguage: 'en'
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 4: Use Translations in Templates

Use the translate pipe:

Before:

<h1>Welcome to our app!</h1>

After:

<h1>{{ 'app.welcome' | translate }}</h1>

Step 5: Use Translations in Components

Use the TranslateService:

src/app/app.component.ts:

import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-app';

  constructor(private translate: TranslateService) {
    // Set default language
    translate.setDefaultLang('en');
    
    // Use browser language if available
    const browserLang = translate.getBrowserLang();
    translate.use(browserLang?.match(/en|es|fr/) ? browserLang : 'en');
  }
}

Step 6: String Interpolation with Variables

Pass variables to translations:

Template:

<p>{{ 'user.greeting' | translate: { name: userName, count: messageCount } }}</p>

Component:

import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-user-greeting',
  template: `
    <p>{{ greeting }}</p>
  `
})
export class UserGreetingComponent {
  userName = 'John';
  messageCount = 5;
  greeting = '';

  constructor(private translate: TranslateService) {
    this.translate.get('user.greeting', {
      name: this.userName,
      count: this.messageCount
    }).subscribe((res: string) => {
      this.greeting = res;
    });
  }
}

Step 7: Pluralization

Use ICU message format:

translation.json:

{
  "items": {
    "count": "{count, plural, =0 {No items} one {# item} other {# items}}"
  }
}

Template:

<p>{{ 'items.count' | translate: { count: itemCount } }}</p>

Component:

itemCount = 5;

Step 8: Format Dates

Use Angular’s DatePipe with locale:

app.module.ts:

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

registerLocaleData(localeEs);
registerLocaleData(localeFr);

Template:

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

Component:

import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-date-display',
  template: `
    <p>{{ date | date:'fullDate' }}</p>
  `
})
export class DateDisplayComponent {
  date = new Date();

  constructor(private translate: TranslateService) {
    // Set locale based on current language
    this.translate.onLangChange.subscribe((event) => {
      // Update locale for date formatting
      // This requires additional setup with LOCALE_ID
    });
  }
}

Step 9: Format Numbers

Use Angular’s DecimalPipe and CurrencyPipe:

Template:

<p>Number: {{ number | number }}</p>
<p>Currency: {{ amount | currency:'USD' }}</p>

Step 10: Change Language Programmatically

Create a language switcher:

src/app/components/language-switcher/language-switcher.component.ts:

import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-language-switcher',
  template: `
    <select (change)="changeLanguage($event)">
      <option value="en" [selected]="currentLang === 'en'">English</option>
      <option value="es" [selected]="currentLang === 'es'">Español</option>
      <option value="fr" [selected]="currentLang === 'fr'">Français</option>
    </select>
  `
})
export class LanguageSwitcherComponent {
  currentLang: string;

  constructor(private translate: TranslateService) {
    this.currentLang = translate.currentLang || translate.defaultLang;
  }

  changeLanguage(event: any) {
    const lang = event.target.value;
    this.translate.use(lang);
    this.currentLang = lang;
    localStorage.setItem('language', lang);
  }
}

Step 11: Language Detection and Persistence

Load saved language on app start:

app.component.ts:

import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  constructor(private translate: TranslateService) {
    translate.setDefaultLang('en');
  }

  ngOnInit() {
    // Try to get saved language
    const savedLang = localStorage.getItem('language');
    if (savedLang) {
      this.translate.use(savedLang);
    } else {
      // Use browser language
      const browserLang = this.translate.getBrowserLang();
      this.translate.use(browserLang?.match(/en|es|fr/) ? browserLang : 'en');
    }
  }
}

Best Practices

1. Use Descriptive Translation Keys

Bad:

{
  "msg1": "Submit"
}

Good:

{
  "button": {
    "submit": "Submit"
  }
}

2. Organize by Feature

{
  "auth": {
    "login": "Login",
    "logout": "Logout"
  },
  "dashboard": {
    "title": "Dashboard"
  }
}

3. Use Lazy Loading for Feature Modules

feature.module.ts:

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

@NgModule({
  imports: [
    TranslateModule.forChild() // Use forChild in feature modules
  ]
})
export class FeatureModule { }

4. Handle Missing Translations

this.translate.get('some.key', { defaultValue: 'Default text' })
  .subscribe((text: string) => {
    // Use text
  });

5. Use Translation Service in Services

import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  constructor(private translate: TranslateService) {}

  showError() {
    this.translate.get('error.network').subscribe((message: string) => {
      // Show error notification
    });
  }
}

6. Test String Lengths

Some languages are longer than others. Design your UI to accommodate:

  • German and Finnish: 30-50% longer than English
  • Asian languages: May need more vertical space

Common Pitfalls

1. Forgetting to Import TranslateModule

Always import TranslateModule in modules that use translations:

imports: [TranslateModule]

2. Not Providing HttpClient

For TranslateHttpLoader, you need HttpClientModule:

imports: [HttpClientModule]

3. Hardcoding Format Strings

Bad:

const price = `$${amount.toFixed(2)}`;

Good:

{{ amount | currency:'USD' }}

4. Not Handling Async Translations

Translations load asynchronously. Use observables:

this.translate.get('key').subscribe((text: string) => {
  // Use text
});

Advanced: Custom Translate Loader

Create a custom loader for different data sources:

import { Observable } from 'rxjs';
import { TranslateLoader } from '@ngx-translate/core';

export class CustomTranslateLoader implements TranslateLoader {
  getTranslation(lang: string): Observable<any> {
    // Load translations from custom source
    return this.http.get(`/api/translations/${lang}`);
  }
}

Advanced: Translation Caching

Cache translations to reduce HTTP requests:

import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Injectable()
export class TranslationCacheService {
  private cache = new Map<string, any>();

  constructor(private translate: TranslateService) {
    this.translate.onLangChange.subscribe(() => {
      this.cache.clear();
    });
  }

  getTranslation(lang: string): Observable<any> {
    if (this.cache.has(lang)) {
      return of(this.cache.get(lang));
    }
    
    return this.http.get(`/assets/i18n/${lang}.json`).pipe(
      tap(translations => this.cache.set(lang, translations))
    );
  }
}

Advanced: RTL Support

Handle right-to-left languages:

import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  template: '<div [dir]="textDirection"><router-outlet></router-outlet></div>'
})
export class AppComponent implements OnInit {
  textDirection = 'ltr';

  constructor(private translate: TranslateService) {}

  ngOnInit() {
    this.translate.onLangChange.subscribe((event) => {
      const isRTL = ['ar', 'he', 'fa'].includes(event.lang);
      this.textDirection = isRTL ? 'rtl' : 'ltr';
      document.documentElement.dir = this.textDirection;
      document.documentElement.lang = event.lang;
    });
  }
}

Advanced: TypeScript Support

Create type definitions for translations:

src/types/translations.d.ts:

export interface Translations {
  app: {
    welcome: string;
    title: string;
  };
  button: {
    submit: string;
    cancel: string;
    delete: string;
  };
  // ... other translation types
}

Using with Angular Universal (SSR)

For server-side rendering:

import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { makeStateKey, TransferState } from '@angular/platform-browser';

export class TranslateBrowserLoader implements TranslateLoader {
  constructor(
    private http: HttpClient,
    private transferState: TransferState
  ) {}

  getTranslation(lang: string): Observable<any> {
    const key = makeStateKey<number>(`transfer-translate-${lang}`);
    const data = this.transferState.get(key, null);

    if (data) {
      return of(data);
    } else {
      return this.http.get(`/assets/i18n/${lang}.json`).pipe(
        tap(data => this.transferState.set(key, data))
      );
    }
  }
}

Conclusion

Localizing your Angular app can be done through two main approaches:

  1. @angular/localize (Built-in) - Production-ready, build-time translations
  2. ngx-translate - Easier setup, runtime translations, more flexible

Choose the method that best fits your requirements. By following these practices, you’ll create an app that provides a native experience for users worldwide, significantly expanding your potential user base.

Streamline Your Angular Localization Workflow

Managing translations for multiple languages can become complex as your app grows. Consider using a translation management platform to:

  • Collaborate with translators
  • Keep translations in sync with your codebase
  • Automate the translation workflow
  • Maintain consistency across all languages
  • Generate translation files automatically
  • Integrate with your CI/CD pipeline

Ready to take your Angular app global? Explore AZbox’s localization platform and streamline your translation workflow:

View AZbox Plans and Pricing

Blog

Latest Posts

Discover our latest articles and updates.

Why Translations Have Always Been a Problem in Software Development
date icon

Sunday, Dec 21, 2025

Why Translations Have Always Been a Problem in Software Development

For decades, software developers have struggled with translations and localization. What should be a straightforward pro

Read More
Cómo Traducir una App Flutter con Azbox: Guía Completa
date icon

Saturday, Dec 20, 2025

Cómo Traducir una App Flutter con Azbox: Guía Completa

Traducir tu app Flutter es esencial para llegar a una audiencia global. Azbox proporciona un potente SDK de Flutter que

Read More
How to Translate a Flutter App with Azbox: Complete Guide
date icon

Saturday, Dec 20, 2025

How to Translate a Flutter App with Azbox: Complete Guide

Translating your Flutter app is essential for reaching a global audience. Azbox provides a powerful Flutter SDK that sim

Read More
cta-image

Start Global Growth Today

Join hundreds of successful companies already using AZbox to reach customers worldwide. Start with a free trial, no credit card required.

Get Started - It's Free