Skip to content
Angular ng libraries 5 min read

Transloco & ngx-translate

Angular ships a powerful built-in i18n system, but it compiles a separate bundle per locale and switching languages means a full page reload. When you need to swap languages at runtime — without rebuilding or redeploying — a library-based approach is the pragmatic choice. Transloco and ngx-translate are the two leading runtime i18n libraries for Angular: both load JSON translation files on demand and let users flip locales instantly. This page shows how to set each up, when to reach for one over the other, and the patterns that keep translations maintainable.

Built-in i18n vs runtime libraries

Angular’s native @angular/localize extracts strings at build time and produces one fully-translated bundle per language. That yields the smallest, fastest bundles and is great for SEO-driven marketing sites where a locale lives at its own URL. The trade-off is that you cannot change language in the browser without navigating to a different build.

Runtime libraries keep a single bundle and fetch translation dictionaries (usually JSON) over HTTP, resolving keys in templates and code as the app runs.

AspectBuilt-in @angular/localizeTransloco / ngx-translate
Switch language at runtimeNo (reload/new build)Yes, instant
Bundle strategyOne bundle per localeSingle bundle, lazy JSON
Translation sourceCompile-time XLIFF/XMBRuntime JSON files
Best forSEO sites, max performanceDashboards, SPAs, dynamic locales

Transloco is the more actively maintained, signal-friendly option in 2026 and is the recommended choice for new standalone apps. ngx-translate remains widely used in existing codebases and recently regained active maintenance.

Setting up Transloco

Install via the schematic, which wires up the provider and creates an i18n folder:

ng add @jsverse/transloco

The schematic asks for your supported languages and whether to use HTTP loading, then registers everything in app.config.ts. A typical configuration looks like this:

// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideTransloco } from '@jsverse/transloco';
import { TranslocoHttpLoader } from './transloco-loader';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideTransloco({
      config: {
        availableLangs: ['en', 'es', 'fr'],
        defaultLang: 'en',
        reRenderOnLangChange: true,
        prodMode: !isDevMode(),
      },
      loader: TranslocoHttpLoader,
    }),
  ],
};

The generated loader simply fetches a JSON file per language from assets/i18n/:

// transloco-loader.ts
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Translation, TranslocoLoader } from '@jsverse/transloco';

@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
  private http = inject(HttpClient);

  getTranslation(lang: string) {
    return this.http.get<Translation>(`/assets/i18n/${lang}.json`);
  }
}

Translation files are plain nested JSON:

// assets/i18n/en.json
{
  "home": {
    "title": "Welcome back, {{ name }}",
    "items": "You have {{ count }} new messages"
  }
}

Using translations in a standalone component

Import the TranslocoModule’s pipe/directive and the service. In templates, the transloco pipe resolves keys reactively; with reRenderOnLangChange: true it updates the moment the language changes.

// home.component.ts
import { Component, inject, signal } from '@angular/core';
import { TranslocoModule, TranslocoService } from '@jsverse/transloco';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [TranslocoModule],
  template: `
    <h1>{{ 'home.title' | transloco: { name: user() } }}</h1>
    <p>{{ 'home.items' | transloco: { count: 3 } }}</p>

    @for (lang of langs; track lang) {
      <button (click)="switch(lang)">{{ lang }}</button>
    }
  `,
})
export class HomeComponent {
  private transloco = inject(TranslocoService);
  langs = ['en', 'es', 'fr'];
  user = signal('Ada');

  switch(lang: string) {
    this.transloco.setActiveLang(lang);
  }
}

Reading a value imperatively (for example inside a guard or service) uses translate:

const title = this.transloco.translate('home.title', { name: 'Ada' });

Output:

Welcome back, Ada

Setting up ngx-translate

ngx-translate uses a similar shape. Install the core package plus an HTTP loader:

npm install @ngx-translate/core @ngx-translate/http-loader

Configure it functionally in app.config.ts:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, HttpClient } from '@angular/common/http';
import { provideTranslateService, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

export function httpLoaderFactory(http: HttpClient): TranslateLoader {
  return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideTranslateService({
      defaultLanguage: 'en',
      loader: {
        provide: TranslateLoader,
        useFactory: httpLoaderFactory,
        deps: [HttpClient],
      },
    }),
  ],
};

In a component, import TranslatePipe and call use() to switch:

// home.component.ts
import { Component, inject } from '@angular/core';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [TranslatePipe],
  template: `
    <h1>{{ 'home.title' | translate: { name: 'Ada' } }}</h1>
    <button (click)="translate.use('es')">Español</button>
  `,
})
export class HomeComponent {
  translate = inject(TranslateService);
}

Choosing between them

Both cover the 90% case identically: JSON files, a pipe, runtime switching, parameter interpolation. The differences show up at scale.

FeatureTranslocongx-translate
Scoped/lazy translationsFirst-class (provideTranslocoScope)Manual setup
Missing-key handlerBuilt-in, configurableBuilt-in
Functional transpilers (plurals, etc.)Optional pluginsVia plugins
Signals integrationNative helpersCommunity wrappers
Schematics / CLI toolingRich (ng add, keys manager)Minimal

If you are starting fresh and want lazy-loaded feature translations and tighter tooling, pick Transloco. If you are maintaining an app that already uses ngx-translate, there is rarely a reason to migrate.

Avoid hard-coding raw strings as keys ("Welcome back"). Use structured keys like home.title so a missing translation degrades gracefully and refactors stay safe.

Best practices

  • Namespace keys by feature (checkout.summary.total) and lazy-load per-feature scopes so the initial JSON stays small.
  • Keep a single source-of-truth language (usually en) and run a key-extraction step in CI to catch missing translations before release.
  • Persist the user’s chosen language to localStorage and restore it on bootstrap so the selection survives reloads.
  • Prefer the pipe in templates over imperative translate() calls; the pipe re-renders automatically on language change.
  • Enable a missing-translation handler in development so untranslated keys are loud, not silently blank.
  • Lazy-load locale data for DatePipe/CurrencyPipe separately — i18n libraries translate strings but do not localize Angular’s number and date formatting.
Last updated June 14, 2026
Was this helpful?