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.
| Aspect | Built-in @angular/localize | Transloco / ngx-translate |
|---|---|---|
| Switch language at runtime | No (reload/new build) | Yes, instant |
| Bundle strategy | One bundle per locale | Single bundle, lazy JSON |
| Translation source | Compile-time XLIFF/XMB | Runtime JSON files |
| Best for | SEO sites, max performance | Dashboards, 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.
| Feature | Transloco | ngx-translate |
|---|---|---|
| Scoped/lazy translations | First-class (provideTranslocoScope) | Manual setup |
| Missing-key handler | Built-in, configurable | Built-in |
| Functional transpilers (plurals, etc.) | Optional plugins | Via plugins |
| Signals integration | Native helpers | Community wrappers |
| Schematics / CLI tooling | Rich (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 likehome.titleso 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
localStorageand 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/CurrencyPipeseparately — i18n libraries translate strings but do not localize Angular’s number and date formatting.