Skip to main content

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:

bash
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:

bash
mkdir -p src/assets/i18n

Next, create JSON files for each language you want to support. For example:

src/assets/i18n/en.json:

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:

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:

typescript
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:

typescript
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:

html
<h1>{{ 'HOME.TITLE' | translate }}</h1>
<p>{{ 'HOME.DESCRIPTION' | translate }}</p>

With Parameters

For translations with parameters, you can pass an object:

html
<p>{{ 'HOME.GREETING' | translate:{ name: 'John' } }}</p>

Using the TranslateService

You can also programmatically get translations using the TranslateService:

typescript
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:

html
<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:

typescript
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:

typescript
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:

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:

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:

typescript
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:

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:

  1. Bundle Size: The ngx-translate library adds to your bundle size. For very small applications, this overhead might be significant.

  2. 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.

  3. Caching: By default, translation files are cached after loading. This improves performance when switching back to previously loaded languages.

  4. 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

Exercises

  1. Create a simple Angular application with runtime translation support for at least three languages.
  2. Implement a language switcher that remembers the user's language preference using localStorage.
  3. Create a form with validation messages that are properly translated in different languages.
  4. Extend the product catalog example to include product details with more complex translations, including nested translation objects and plurals.
  5. 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! :)