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 production out 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/DecimalPipe with 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.