@ngx-runtime-i18n/angular
v2.0.0
Published
Angular bindings for @ngx-runtime-i18n (signals-first, SSR-safe)
Downloads
21
Maintainers
Readme
@ngx-runtime-i18n/angular
Lean, SSR‑safe Angular wrapper around @ngx-runtime-i18n/core.
- Signals‑first service (
I18nService) andI18nPipefor ergonomic templates - Optional
I18nCompatService(RxJS) for non‑signals apps - SSR‑aware: TransferState snapshot on the server, hydration‑safe on the client
- Cancellation‑aware language switching (rapid toggles won’t corrupt state)
- Lazy Angular locale data per language to power pipes (
DatePipe,DecimalPipe, ...) - Configurable fallback chains with in-memory or localStorage catalog caching
Peer support: @angular/* >=16 <21
Install
Always install both packages explicitly:
npm i @ngx-runtime-i18n/angular @ngx-runtime-i18n/coreDirectory layout (recommended)
your-app/
src/
public/
i18n/
en.json
hi.json
de.jsonAt runtime, catalogs are fetched from /i18n/<lang>.json by default in our examples.
Quick Start (CSR)
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideRuntimeI18n } from '@ngx-runtime-i18n/angular';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(appRoutes),
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: (k) => k,
},
{
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 usage
<h1>{{ 'hello.user' | i18n:{ name: username } }}</h1>
<p>{{ 'cart.items' | i18n:{ count: items().length } }}</p>Component usage
import { Component, inject } from '@angular/core';
import { I18nService, I18nPipe } from '@ngx-runtime-i18n/angular';
@Component({
standalone: true,
imports: [I18nPipe],
template: `
<button (click)="switch('de')">Deutsch</button>
<div *ngIf="i18n.ready()">{{ i18n.t('hello.user', { name: 'Ashwin' }) }}</div>
`,
})
export class SomeCmp {
i18n = inject(I18nService);
switch(lang: string) {
if (this.i18n.ready()) this.i18n.setLang(lang);
}
}DX helpers
I18nService exposes synchronous helpers that pair nicely with Angular signals during development:
getCurrentLang()— snapshot the current language without subscribing tolang().getLoadedLangs()— inspect which catalogs are resident in memory.hasKey(key, lang = current)— check catalog coverage without formatting.
const lang = i18n.getCurrentLang();
const loaded = i18n.getLoadedLangs();
const missingLegacy = !i18n.hasKey('legacy.title');Render these in diagnostics panels or dev tools to confirm when catalogs hydrate.
Switching & preloading
I18nService now exposes two additional signals:
switching()becomestrueas soon as you callsetLang()with a new language, so you can show a spinner or disable controls.activeSwitchLang()mirrors the requested language (nullotherwise), letting you render “Switching to fr…” helper text.
It also adds preload helpers that warm the same loaders used by setLang() without mutating the active language:
preloadLang(lang)for a single language (includes fallback catalogs).preloadLangs(langs)for parallel prefetches (good when the user’s likely languages are known up front).preloadFallbackChain(lang?)to hydrate a language and its configured fallbacks (defaults to the active lang if none is passed).
const i18n = inject(I18nService);
// Disable the switcher while the next catalog downloads.
readonly onSwitch = (lang: string) => {
if (i18n.switching()) return;
i18n.setLang(lang);
};
// Warm caches for a user who usually toggles between German and Hindi.
await i18n.preloadLangs(['de', 'hi']);
// Preload the active route’s fallback chain before navigation.
await i18n.preloadFallbackChain('fr');Use this to preload catalogs during login, before showing a heavy route, or while waiting for other async work; the currently active language and UI stay stable.
Fallback chains
- Configure
RuntimeI18nConfig.fallbacks?: string[]to build an ordered lookup. Resolution always runs as active language → each configured fallback →defaultLang. - Values are deduped automatically and trimmed against
supported, so accidental repeats or unsupported tags are ignored. - Missing keys emit a single dev-mode warning and then flow through
onMissingKey().
Catalog caching
RuntimeI18nOptions.cacheModechooses your strategy:nonekeeps only the active fallback chain in memory (good for memory-constrained apps).memory(default) caches every loaded catalog for the current session.storagehydrates catalogs fromlocalStorage, serves them instantly, and refreshes them in the background. UsecacheKeyPrefixto isolate multiple apps.
- LocalStorage I/O never runs on the server, so SSR stays deterministic when you seed TransferState.
SSR + Hydration
See apps/demo-ssr in this repo for a complete Express + Angular SSR demo (including TransferState seeding and catalog fallbacks).
On the server, use the exported helper to seed TransferState with the same keys that provideRuntimeI18n() reads on the client:
// i18n.server.providers.ts
import { Provider } from '@angular/core';
import { RuntimeI18nSsrSnapshot, provideRuntimeI18nSsr } from '@ngx-runtime-i18n/angular';
export function i18nServerProviders(snapshot: RuntimeI18nSsrSnapshot): Provider[] {
return provideRuntimeI18nSsr(snapshot);
}RuntimeI18nSsrSnapshot.bootstrap holds the active language catalog and catalogs can optionally seed additional locales. Everything defaults to the same prefix as provideRuntimeI18n() (@ngx-runtime-i18n/core), but pass stateKeyPrefix to both helpers when you override it.
Use the same provideRuntimeI18n(...) on both server and client app bootstraps. The wrapper reads TransferState on the client first and only fetches missing catalogs as needed.
Options & Tokens
provideRuntimeI18n(config, { localeLoaders?, options?, stateKeyPrefix? })
config.defaultLang: string— fallback language.config.fallbacks?: string[]— ordered fallback catalog chain before the default.config.supported: string[]— allowed languages (authoritative list).config.fetchCatalog(lang, signal?)— async catalog loader (should be idempotent; honorAbortSignal).config.onMissingKey?: (key) => string— transform missing keys (dev‑only suggestion: return the key).
localeLoaders — map of language to dynamic imports of Angular locale data (enables localized pipes).options.autoDetect — on first boot: persisted → navigator → default.options.storageKey — localStorage key for the chosen language (falsy to disable).options.cacheMode — 'none' | 'memory' | 'storage' for catalog caching strategy (default: 'memory').options.cacheKeyPrefix — storage prefix when cacheMode === 'storage'.options.preferNavigatorBase — map en-GB → en if en is in supported.stateKeyPrefix — advanced: customize TransferState keys if you embed multiple i18n instances.
Services & Pipe
I18nService— signals‑first:lang(),ready(),t(key, params?),setLang(lang)I18nCompatService— RxJS equivalent for non‑signals codebasesI18nPipe—{{ 'path' | i18n:{...} }}(pure=false; listens tolangonly)
Pitfalls & Gotchas
- Angular pipes not localizing — Ensure you defined
localeLoadersfor the language you’re testing. - Hydration mismatch — Always seed TransferState on SSR; the wrapper is hydration‑safe when the first paint uses server data.
- 404 for catalogs — Place files under
src/public/i18nso they serve as/i18n/*.jsonin dev/prod. - Rapid language toggles — Supported; the wrapper cancels in‑flight fetches. Your
fetchCatalogmust respectAbortSignal.
Versioning & Support
- Angular:
>=16 <21 - Node: LTS recommended
- SemVer: breaking changes will bump major versions.
License
MIT
