Angular Runtime Translation
Introduction
Angular's internationalization (i18n) features provide two main approaches for translating applications: compile-time translations and runtime translations. While compile-time translations offer performance benefits, they require separate builds for each language and a page reload when switching languages. Runtime translation, on the other hand, allows users to dynamically switch languages without reloading the page, providing a more seamless user experience.
In this guide, we'll explore how to implement runtime translations in Angular applications using the popular ngx-translate
library, which allows for dynamic language switching with minimal configuration.
Prerequisites
Before we dive in, make sure you have:
- Basic knowledge of Angular
- An existing Angular project or a new one created with
ng new my-app
- Node.js and npm installed on your machine
Setting Up Runtime Translation
Step 1: Install ngx-translate
First, let's install the required packages:
npm install @ngx-translate/core @ngx-translate/http-loader --save
The @ngx-translate/core
package contains the main translation functionality, while @ngx-translate/http-loader
allows loading translation files from your assets folder.
Step 2: Create Translation Files
Create a folder called i18n
inside your src/assets
directory to store your translation files:
mkdir -p src/assets/i18n
Next, create JSON files for each language you want to support. For example:
src/assets/i18n/en.json
:
{
"HOME": {
"TITLE": "Welcome to the App",
"GREETING": "Hello, {{name}}!",
"DESCRIPTION": "This is a sample application with runtime translation"
},
"BUTTONS": {
"SAVE": "Save",
"CANCEL": "Cancel",
"SUBMIT": "Submit"
}
}
src/assets/i18n/es.json
:
{
"HOME": {
"TITLE": "Bienvenido a la Aplicación",
"GREETING": "¡Hola, {{name}}!",
"DESCRIPTION": "Esta es una aplicación de ejemplo con traducción en tiempo de ejecución"
},
"BUTTONS": {
"SAVE": "Guardar",
"CANCEL": "Cancelar",
"SUBMIT": "Enviar"
}
}
Step 3: Configure TranslateModule
Open your app.module.ts
file and configure the translation services:
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: Initialize the TranslateService
In your app component (app.component.ts
), initialize the translation service:
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 {
constructor(private translate: TranslateService) {
// Set default language
this.translate.setDefaultLang('en');
// Use the browser language if available, otherwise use English
const browserLang = this.translate.getBrowserLang();
this.translate.use(browserLang?.match(/en|es/) ? browserLang : 'en');
}
switchLanguage(lang: string) {
this.translate.use(lang);
}
}
Using Translations in Templates
You can use translations in your templates in several ways:
Using the Translate Pipe
The simplest way is to use the translate
pipe:
<h1>{{ 'HOME.TITLE' | translate }}</h1>
<p>{{ 'HOME.DESCRIPTION' | translate }}</p>
With Parameters
For translations with parameters, you can pass an object:
<p>{{ 'HOME.GREETING' | translate:{ name: 'John' } }}</p>
Using the TranslateService
You can also programmatically get translations using the TranslateService
:
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-welcome',
template: `<h1>{{ title }}</h1>`
})
export class WelcomeComponent implements OnInit {
title: string = '';
constructor(private translate: TranslateService) {}
ngOnInit() {
this.translate.get('HOME.TITLE').subscribe((res: string) => {
this.title = res;
});
}
}
Language Switcher Component
Let's create a simple language switcher component:
<div class="language-switcher">
<button (click)="switchLanguage('en')">English</button>
<button (click)="switchLanguage('es')">Español</button>
</div>
<div class="content">
<h1>{{ 'HOME.TITLE' | translate }}</h1>
<p>{{ 'HOME.GREETING' | translate:{ name: userName } }}</p>
<p>{{ 'HOME.DESCRIPTION' | translate }}</p>
<div class="actions">
<button>{{ 'BUTTONS.SAVE' | translate }}</button>
<button>{{ 'BUTTONS.CANCEL' | translate }}</button>
</div>
</div>
Advanced Usage
Lazy Loaded Modules
For larger applications with lazy-loaded modules, you should import the TranslateModule
using forChild()
in each feature module:
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { FeatureComponent } from './feature.component';
@NgModule({
declarations: [FeatureComponent],
imports: [
// Import TranslateModule with forChild()
TranslateModule.forChild()
]
})
export class FeatureModule { }
Language Detection and Persistence
For a better user experience, you might want to detect the user's language and persist their choice:
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(private translate: TranslateService) {
// Languages supported by your app
const supportedLangs = ['en', 'es'];
// Try to get the stored language preference
const storedLang = localStorage.getItem('preferredLanguage');
// Set default language
this.translate.setDefaultLang('en');
// Use stored language if available and supported
if (storedLang && supportedLangs.includes(storedLang)) {
this.translate.use(storedLang);
} else {
// Otherwise try to use browser language
const browserLang = this.translate.getBrowserLang();
const lang = browserLang && supportedLangs.includes(browserLang) ? browserLang : 'en';
this.translate.use(lang);
localStorage.setItem('preferredLanguage', lang);
}
}
switchLanguage(lang: string) {
this.translate.use(lang);
localStorage.setItem('preferredLanguage', lang);
}
}
Complete Example: Multilingual Product Catalog
Let's create a more practical example of a simple product catalog with runtime translation:
Translation Files
src/assets/i18n/en.json
:
{
"CATALOG": {
"TITLE": "Product Catalog",
"SEARCH": "Search products",
"FILTER_BY": "Filter by category",
"SORT_BY": "Sort by",
"PRICE_LOW": "Price: Low to High",
"PRICE_HIGH": "Price: High to Low",
"NO_RESULTS": "No products found"
},
"PRODUCT": {
"ADD_TO_CART": "Add to Cart",
"VIEW_DETAILS": "View Details",
"IN_STOCK": "In Stock",
"OUT_OF_STOCK": "Out of Stock",
"REVIEWS": "Reviews",
"PRICE": "Price"
}
}
src/assets/i18n/es.json
:
{
"CATALOG": {
"TITLE": "Catálogo de Productos",
"SEARCH": "Buscar productos",
"FILTER_BY": "Filtrar por categoría",
"SORT_BY": "Ordenar por",
"PRICE_LOW": "Precio: De menor a mayor",
"PRICE_HIGH": "Precio: De mayor a menor",
"NO_RESULTS": "No se encontraron productos"
},
"PRODUCT": {
"ADD_TO_CART": "Añadir al Carrito",
"VIEW_DETAILS": "Ver Detalles",
"IN_STOCK": "En Stock",
"OUT_OF_STOCK": "Agotado",
"REVIEWS": "Reseñas",
"PRICE": "Precio"
}
}
Product Catalog Component
product-catalog.component.ts
:
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
image: string;
}
@Component({
selector: 'app-product-catalog',
templateUrl: './product-catalog.component.html',
styleUrls: ['./product-catalog.component.css']
})
export class ProductCatalogComponent implements OnInit {
products: Product[] = [
{
id: 1,
name: 'Laptop',
price: 999.99,
category: 'Electronics',
inStock: true,
image: 'assets/laptop.jpg'
},
{
id: 2,
name: 'Smartphone',
price: 599.99,
category: 'Electronics',
inStock: true,
image: 'assets/smartphone.jpg'
},
{
id: 3,
name: 'Headphones',
price: 149.99,
category: 'Audio',
inStock: false,
image: 'assets/headphones.jpg'
}
];
searchTerm: string = '';
selectedCategory: string = '';
categories: string[] = [];
constructor(public translate: TranslateService) {}
ngOnInit() {
// Extract unique categories
this.categories = [...new Set(this.products.map(product => product.category))];
}
switchLanguage(lang: string) {
this.translate.use(lang);
localStorage.setItem('preferredLanguage', lang);
}
filterProducts() {
let filteredProducts = [...this.products];
if (this.searchTerm) {
const term = this.searchTerm.toLowerCase();
filteredProducts = filteredProducts.filter(p =>
p.name.toLowerCase().includes(term) ||
p.category.toLowerCase().includes(term)
);
}
if (this.selectedCategory) {
filteredProducts = filteredProducts.filter(p =>
p.category === this.selectedCategory
);
}
return filteredProducts;
}
}
product-catalog.component.html
:
<div class="catalog-container">
<div class="language-switcher">
<button (click)="switchLanguage('en')">English</button>
<button (click)="switchLanguage('es')">Español</button>
</div>
<h1>{{ 'CATALOG.TITLE' | translate }}</h1>
<div class="filters">
<input
type="text"
[(ngModel)]="searchTerm"
[placeholder]="'CATALOG.SEARCH' | translate">
<select [(ngModel)]="selectedCategory">
<option value="">{{ 'CATALOG.FILTER_BY' | translate }}</option>
<option *ngFor="let category of categories" [value]="category">
{{ category }}
</option>
</select>
</div>
<div class="products" *ngIf="filterProducts().length > 0; else noProducts">
<div class="product-card" *ngFor="let product of filterProducts()">
<img [src]="product.image" [alt]="product.name">
<h3>{{ product.name }}</h3>
<p>{{ 'PRODUCT.PRICE' | translate }}: {{ product.price | currency }}</p>
<p [ngClass]="product.inStock ? 'in-stock' : 'out-of-stock'">
{{ (product.inStock ? 'PRODUCT.IN_STOCK' : 'PRODUCT.OUT_OF_STOCK') | translate }}
</p>
<div class="product-actions">
<button [disabled]="!product.inStock">
{{ 'PRODUCT.ADD_TO_CART' | translate }}
</button>
<button>{{ 'PRODUCT.VIEW_DETAILS' | translate }}</button>
</div>
</div>
</div>
<ng-template #noProducts>
<p class="no-results">{{ 'CATALOG.NO_RESULTS' | translate }}</p>
</ng-template>
</div>
Performance Considerations
When using runtime translation, keep these performance considerations in mind:
-
Bundle Size: The
ngx-translate
library adds to your bundle size. For very small applications, this overhead might be significant. -
Initial Load: Translation files are loaded asynchronously when the application starts or when a language is changed. For large translation files, this might cause a brief delay.
-
Caching: By default, translation files are cached after loading. This improves performance when switching back to previously loaded languages.
-
Lazy Loading: For applications with many translations, consider splitting translation files by feature or module and loading them lazily.
Summary
Runtime translation in Angular provides a flexible way to support multiple languages in your application without requiring page reloads. Using the ngx-translate
library, you can:
- Load translation files dynamically
- Switch languages on-the-fly
- Use translations with parameters
- Access translations both in templates and in component code
While it adds some overhead compared to Angular's built-in compile-time i18n, runtime translation offers a more seamless user experience and simplifies the build process since you only need to maintain one build for all languages.
Additional Resources
- ngx-translate Documentation
- Angular Internationalization Guide
- Best Practices for Multilingual Angular Apps
Exercises
- Create a simple Angular application with runtime translation support for at least three languages.
- Implement a language switcher that remembers the user's language preference using localStorage.
- Create a form with validation messages that are properly translated in different languages.
- Extend the product catalog example to include product details with more complex translations, including nested translation objects and plurals.
- Research and implement a way to handle pluralization in your translations (hint: look at the ngx-translate documentation for handling plurals).
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)