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:
- @angular/localize - Built-in i18n solution (recommended for production)
- 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:
- @angular/localize (Built-in) - Production-ready, build-time translations
- 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: