AshwinSathian/ngx-runtime-i18n
A modern runtime internationalisation library for Angular 16+. Fully compatible with SSR and hydration. Features include fallback chains, persistent catalog caching, and developer-friendly helpers — all powered by signals.
Signals-first runtime i18n for Angular — SSR-safe, hydration-friendly, ICU-lite, and configurable fallback chains.
provideRuntimeI18n() installs I18nService, I18nPipe, and the optional I18nCompatService (RxJS bridge)apps/demo-ssr)fallbacks[] → defaultLangnone, memory, or storage (localStorage revalidation)lang() signal plus getCurrentLang(), getLoadedLangs(), and hasKey()npm i @ngx-runtime-i18n/angular @ngx-runtime-i18n/core
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRuntimeI18n } from '@ngx-runtime-i18n/angular';
export const appConfig: ApplicationConfig = {
providers: [
provideRuntimeI18n(
{
defaultLang: 'en',
supported: ['en', 'hi', 'de'],
fallbacks: ['de'],
fetchCatalog: (lang, signal) =>
fetch(`/i18n/${lang}.json`, { signal }).then((r) => {
if (!r.ok) throw new Error(`Failed to load catalog: ${lang}`);
return r.json();
}),
onMissingKey: (key) => key,
},
{
localeLoaders: {
en: () => import('@angular/common/locales/global/en'),
hi: () => import('@angular/common/locales/global/hi'),
de: () => import('@angular/common/locales/global/de'),
},
options: {
autoDetect: true,
storageKey: '@ngx-runtime-i18n:lang',
cacheMode: 'storage',
cacheKeyPrefix: '@ngx-runtime-i18n:catalog:',
preferNavigatorBase: true,
},
}
),
],
};
<!-- Template -->
<h1>{{ 'hello.user' | i18n:{ name: username } }}</h1>
<p>{{ 'cart.items' | i18n:{ count: items().length } }}</p>
<small>Fallback → {{ 'legacy.title' | i18n }}</small>
// Component
import { Component, effect, inject, signal } from '@angular/core';
import { I18nPipe, I18nService } from '@ngx-runtime-i18n/angular';
@Component({
standalone: true,
imports: [I18nPipe],
template: `
<button (click)="switch('de')">Deutsch</button>
<div>{{ i18n.t('hello.user', { name: 'Ashwin' }) }}</div>
`,
})
export class ToolbarComponent {
i18n = inject(I18nService);
loaded = signal<string[]>([]);
constructor() {
effect(() => {
this.i18n.lang(); // subscribe to the signal
this.loaded.set(this.i18n.getLoadedLangs());
});
}
async switch(lang: string) {
if (this.i18n.ready()) await this.i18n.setLang(lang);
}
}
Need RxJS? Inject I18nCompatService for lang$, ready$, and t() without signals.
I18nService exposes synchronous utilities for tooling and debugging:
getCurrentLang() — snapshot of the active language without reading the signalgetLoadedLangs() — list of catalogs currently cached in memoryhasKey(key, lang = current) — determine if a key exists before renderingPair these with Angular effect()/computed() to display diagnostics in dev tools:
const lang = this.i18n.getCurrentLang();
const loaded = this.i18n.getLoadedLangs();
const missingLegacy = !this.i18n.hasKey('legacy.title');
RuntimeI18nConfig.fallbacks?: string[]. Lookup order is active language → each fallback (in order) → defaultLang. Missing keys then flow to onMissingKey.RuntimeI18nOptions.cacheMode controls persistence:
none — keep only the active fallback chain in memorymemory (default) — cache every loaded language for the sessionstorage — hydrate catalogs from localStorage, then refresh them in the background (cacheKeyPrefix controls storage keys)localStorage; hydration stays deterministic when you seed TransferState.The repo ships with apps/demo-ssr, a full Express + Angular SSR demo. The server reads catalogs from dist/browser/i18n, injects them via i18nServerProviders, and the browser reuses them through TransferState. Run it locally:
nx build demo-ssr
nx serve demo-ssr # http://localhost:4000
nx serve demo # CSR demo with caching + fallbacks
nx serve demo-ssr # SSR + hydration demo
Catalog JSON lives under apps/demo*/public/i18n/<lang>.json.
| Package | Description |
|---|---|
@ngx-runtime-i18n/core | Framework-agnostic primitives (ICU-lite formatter, shared types). |
@ngx-runtime-i18n/angular | Angular wrapper (signals, SSR-safe service, pipes). |
MIT © Ashwin Sathian