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
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
Android Localization: Complete Guide to Expanding Your App to New Markets
date icon

Saturday, Dec 13, 2025

Android Localization: Complete Guide to Expanding Your App to New Markets

Android runs on many devices, in many regions. To reach the majority of users, make sure your app handles text, audio fi

Read More
Call to action background

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