Localize an Angular app: complete guide with Localingos
Angular ships with its own @angular/localize system, but most teams prefer @ngx-translate/core for runtime locale switching (Angular's built-in requires per-locale builds). This guide uses ngx-translate paired with Localingos for the translation pipeline — runtime language switching, lazy-loaded locale files, and automated CI sync.
Step 1 — Install
npm install @ngx-translate/core @ngx-translate/http-loader
npm install -g @localingos/cli
localingos login
Step 2 — Wire ngx-translate
src/app/app.config.ts:
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
}
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
importProvidersFrom(
TranslateModule.forRoot({
defaultLanguage: 'en',
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
})
),
],
};
src/app/app.component.ts — initialize on app start:
import { Component, inject, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
private translate = inject(TranslateService);
ngOnInit() {
const saved = localStorage.getItem('locale');
const browser = navigator.language?.split('-')[0];
const initial = saved || browser || 'en';
this.translate.use(initial);
}
}
Step 3 — Source of truth
src/assets/i18n/en.json:
{
"welcome": "Welcome, {{name}}",
"cart": "{count, plural, =0 {empty cart} one {1 item} other {{{count}} items}}"
}
ngx-translate supports ICU MessageFormat via the optional @ngx-translate/messageformat-compiler package — recommended if you need plural rules.
Step 4 — Configure Localingos
localingos.json:
{
"projectId": "your-project-id",
"source": { "path": "src/assets/i18n/en.json", "locale": "en" },
"targets": {
"path": "src/assets/i18n/{locale}.json",
"locales": ["es", "de", "fr", "ja", "pt-BR", "zh-CN", "ko"]
},
"placeholders": ["{{variable}}"],
"format": "icu"
}
localingos sync
Translations land in src/assets/i18n/. These ship as static assets, fetched on demand by TranslateHttpLoader.
Step 5 — Use in components
Template:
<header>
<h1>{{ 'welcome' | translate: { name: userName } }}</h1>
<p>{{ 'cart' | translate: { count: itemCount } }}</p>
</header>
Component class (if you need to translate in TS code):
import { inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
export class CartComponent {
private translate = inject(TranslateService);
get warningMessage() {
return this.translate.instant('cart.warning', { count: this.itemCount });
}
}
Step 6 — Language switcher
import { Component, inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-language-switcher',
template: `
<select [value]="translate.currentLang" (change)="switch($event)">
<option value="en">English</option>
<option value="es">Español</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
</select>
`,
})
export class LanguageSwitcher {
translate = inject(TranslateService);
switch(e: Event) {
const locale = (e.target as HTMLSelectElement).value;
this.translate.use(locale);
localStorage.setItem('locale', locale);
}
}
Step 7 — Lazy loading
TranslateHttpLoader already loads on demand — this.translate.use('es') fetches /assets/i18n/es.json only the first time that locale is requested. After the first fetch it's cached client-side.
For build-time bundle splitting (rare — most Angular apps prefer runtime loading), you can wire the loader to dynamically import locale modules instead.
Step 8 — Automate sync in CI
# .github/workflows/i18n.yml
name: i18n-sync
on:
push: { branches: [main], paths: ['src/assets/i18n/en.json'] }
jobs:
sync:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g @localingos/cli
- run: localingos sync
env: { LOCALINGOS_API_KEY: '${{ secrets.LOCALINGOS_API_KEY }}' }
- uses: peter-evans/create-pull-request@v6
with:
branch: i18n/auto-sync
title: 'chore(i18n): sync translations'
commit-message: 'chore(i18n): sync translations'
Production checklist
- AOT-compile compatible. ngx-translate works with
ng build --configuration productionout of the box. - SSR (Angular Universal). Use
TranslateModule.forChild()with a server-side loader that reads JSON from disk instead of HTTP. Otherwise SSR requests trying to fetch their own translations cause loops. - RTL. Bind
[dir]on<html>:dir = ['ar','he','fa'].includes(locale) ? 'rtl' : 'ltr'. - Date/number formatting. Use Angular's built-in
DatePipe/DecimalPipewith locale ID — works alongside ngx-translate, not in conflict with it.
Wrap up
Your Angular app supports 56 locales with runtime switching, lazy-loaded translation files, and CI-driven sync. Adding a locale is one entry in localingos.json plus an <option> in the switcher.
Free tier — 5,000 words covers most Angular SPA string corpora.