API Reference
Complete API documentation for @angular-translation-service/core.
TranslationService
The core service providing reactive i18n for Angular applications. Inject via Angular DI.
select<N extends TranslationNamespace>(scope: N): Signal<TranslationNamespaces[N] | undefined>
Returns a scope signal for template access. Automatically triggers lazy loading for the namespace. Data is undefined until the namespace loads, then returns a proxy-wrapped dictionary for safe deep property access. When generated types are registered, the namespace name and returned schema are typed.
protected readonly common = this.i18n.select('common'); // template: @let t = common(); {{ t?.nav?.title }}
translate<K extends TranslationKey>(key: K, params?: TranslationParams<K>): Signal<string>
Returns a cached signal for a single translation key. Automatically triggers namespace loading and re-runs when the active language changes. Key format: namespace:dotted.path. When generated types are registered, invalid keys become TypeScript errors.
protected readonly title = this.i18n.translate('common:nav.title'); protected readonly greeting = this.i18n.translate('common:greeting', { name: 'Igor' });
instant<K extends TranslationKey>(key: K, params?: TranslationParams<K>): string
Returns the translation synchronously. It is non-reactive: values read with instant() do not update after setLang(). If called before a lazy namespace has ever been requested, dev mode warns once and starts loading that namespace; the current synchronous call still returns ''. Use ensureNamespaces() first or prefer translate() for reactive UI state.
setLang(lang: string): Promise<void>
Switches the active language. Reloads all previously requested namespaces for the new language and persists the preference to localStorage (if storageKey is configured). Logs a warning and returns early if the language is not in supportedLangs.
ensureNamespaces(ns: string[]): Promise<void>
Pre-loads additional namespaces. Deduplicates concurrent loads automatically. Useful for route guards or resolvers.
lang: WritableSignal<string>
The current language signal. Read in templates to show the active language.
supportedLangs: readonly string[]
Read-only array of all supported language codes from the configuration. Useful for building language switchers.
// Build a language switcher protected readonly languages = this.i18n.supportedLangs; // template: @for (lang of languages; track lang) { ... }
ready: Signal<boolean>
True when all coreNamespaces have been loaded for the current language.
Reactivity: translate() vs instant()
translate() and select() are reactive signal APIs. Use them for labels, menus, option arrays, and anything that should update after setLang(). instant() is a synchronous snapshot for imperative code such as logs, confirm prompts, or already-loaded toast messages.
Avoid frozen option arrays
Calling instant() while defining component fields freezes those labels in the boot language and may read before a lazy namespace is ready.
protected readonly densityOptions = [ { value: 'compact', label: this.i18n.instant('settings:density.compact') }, { value: 'comfortable', label: this.i18n.instant('settings:density.comfortable') }, ];
Prefer computed() + translate()
Build derived UI state inside computed() and read the translation signals there. The array rebuilds when the namespace loads and whenever the language changes.
protected readonly densityOptions = computed(() => [ { value: 'compact', label: this.i18n.translate('settings:density.compact')() }, { value: 'comfortable', label: this.i18n.translate('settings:density.comfortable')() }, ]);
Escape hatch for imperative key maps
When you truly need instant(), read lang() inside the computed and make sure the namespace was preloaded with ensureNamespaces().
protected readonly navItems = computed(() => { this.i18n.lang(); // make instant() re-run after language changes return NAV_ITEMS.map((item) => ({ ...item, label: this.i18n.instant(item.labelKey) })); });
Performance note: parameterized translate(key, params) calls return uncached computed signals, so avoid recreating them inside very large loops. Hoist stable params or use select() where possible.
Typed Translation Keys
ats generate now emits a module augmentation for @angular-translation-service/core. Include the generated file in your TypeScript program and the service API narrows from permissive string keys to your generated key union and namespace schemas. Apps that do not generate types keep the old flexible string API.
Generated registry
export interface I18nTypes { common: { nav: { home: string; }; }; } declare module '@angular-translation-service/core' { interface TranslationKeyRegistry { keys: I18nTranslationKey; namespaces: I18nTypes; } }
What TypeScript catches
Static TypeScript call sites get checked end-to-end. The pipe still accepts template strings because Angular template expressions cannot expose the same module augmentation contract.
this.i18n.translate('common:nav.home'); // OK this.i18n.translate('common:nav.hmoe'); // TypeScript error const common = this.i18n.select('common'); common()?.nav.home; // typed namespace schema
Interpolation Placeholders
Use single braces as the canonical placeholder style in JSON values: {name}. Existing double-brace placeholders such as {{name}} are still tolerated for compatibility.
Canonical form
Single braces match the translate(key, { name }) mental model and avoid visual collision with Angular template interpolation.
{
"greeting": "Hello, {name}!"
}
this.i18n.translate('common:greeting', { name: 'Igor' });Angular pipe params gotcha
When passing an object literal to the pipe inside Angular interpolation, leave a space between the object closing brace and Angular's closing braces. Without the space, Angular may parse the braces together.
<!-- Avoid: object brace touches Angular's closing braces --> <p>{{ 'common:greeting' | translate:{ name: userName()}}}</p> <!-- Prefer: keep a space before the interpolation closes --> <p>{{ 'common:greeting' | translate:{ name: userName() } }}</p>
TranslatePipe
Template sugar for TranslationService.translate(). The pipe caches its signal internally to avoid recreation on every change detection cycle.
Setup
Import TranslatePipe in your component's imports array:
import { TranslatePipe } from '@angular-translation-service/core'; @Component({ imports: [TranslatePipe], template: `...`, })
Usage
<!-- Basic --> <h1>{{ 'common:nav.title' | translate }}</h1> <!-- With params --> <p>{{ 'common:greeting' | translate:{ name: 'Igor' } }}</p>
provideTranslation()
Configures the translation service at the application level. Uses APP_INITIALIZER to preload core namespaces before the app renders.
| Option | Type | Description |
|---|---|---|
defaultLang | string | Default language code (e.g., 'en') |
supportedLangs | string[] | All supported language codes |
coreNamespaces | string[] | Namespaces preloaded via APP_INITIALIZER before the app renders |
loader | TranslationLoader | Function that loads translation files. Use httpLoader() or importLoader() |
detectLanguage | boolean | Enable browser language detection via navigator.language |
storageKey | string | localStorage key for persisting language preference across sessions |
namespaceSeparator | string | Separator between namespace and key path (default: ':') |
fallbackChain | Record<string, string[]> | Fallback language chains for regional locales |
Loaders
httpLoader(basePath: string): TranslationLoader
Creates a fetch-based loader. Loads JSON from: basePath/lang/namespace.json. Place your JSON files in the Angular project's public/ directory (files in public/ are served at the root URL).
// Files at: public/i18n/en/common.json → served at /i18n/en/common.json loader: httpLoader('/i18n')
importLoader(factory: (lang, ns) => Promise<{ default: Record }>): TranslationLoader
Creates a loader using dynamic imports. The factory callback must return a module with a default export containing the translation dictionary. The bundler can statically analyze import paths for tree-shaking and code splitting.
loader: importLoader((lang, ns) => import(`./i18n/${lang}/${ns}.json`) )
Injection Tokens
TRANSLATION_CONFIG: InjectionToken<TranslationConfig>
Injection token for the translation configuration. Automatically provided by provideTranslation(). Useful for custom providers that need access to the configuration.
CURRENT_LANGUAGE: InjectionToken<string>
Injection token for server-side language detection. Provided by provideTranslationSSR() on the server to inject the language from the HTTP request. On the client, language is resolved from TransferState or local detection.
createRecursiveProxy()
Creates a crash-proof proxy object that safely intercepts all property access. Used internally by select() to prevent template errors when namespaces are still loading. The proxy returns nested proxies for any property access, ensuring expressions like t().nav.title never throw — they render as empty strings instead.
Behavior
| Access | Result |
|---|---|
| String coercion / template interpolation | Returns the key path (e.g., 'common.nav.title') |
| Property access | Returns another recursive proxy |
| JSON serialization | Returns {} |
Promise detection (then/catch) | Returns undefined (prevents async unwrapping) |
Iteration (@for loops) | Yields nothing (empty iterator) |
Fallback Chains
Define fallback languages for regional locales. When loading translations, all languages in the chain are fetched in parallel and deep-merged — the most specific locale's keys overwrite fallback keys.
provideTranslation({ defaultLang: 'en', supportedLangs: ['en', 'es', 'es-AR'], fallbackChain: { 'es-AR': ['es', 'en'], // es-AR → es → en 'es': ['en'], // es → en }, // ... })
Language Detection
The initial language is resolved using the following priority chain (first match wins). Steps 1–2 always run; step 3 requires detectLanguage: true.
- SSR token — Language injected via
CURRENT_LANGUAGEon the server (always checked) - localStorage — Previously persisted preference (always checked if
storageKeyis configured) - Browser —
navigator.languagewith base-language matching (e.g., browser'pt'matches config'pt-BR') (only whendetectLanguage: true) - Default — Falls back to
defaultLang
SSR & Hydration
The @angular-translation-service/core/ssr secondary entry point provides seamless server-side rendering with zero flash-of-untranslated-content (FOUC) and no hydration mismatch.
provideTranslationSSR(config: TranslationSSRConfig): EnvironmentProviders
Add to your server application config. On the server, it provides CURRENT_LANGUAGE from the HTTP request and snapshots all loaded translations into TransferState. On the client, it hydrates translations synchronously — no HTTP requests needed for the initial render.
// app.config.server.ts import { provideTranslationSSR } from '@angular-translation-service/core/ssr'; export const serverConfig = { providers: [ provideTranslationSSR({ langFromRequest: (req) => { const accept = (req as Request).headers.get('accept-language') ?? 'en'; return accept.split(',')[0].split('-')[0]; }, }), ], };
How it works
| Phase | What happens |
|---|---|
| Server render | CURRENT_LANGUAGE is set from the request, core namespaces load, then all loaded translations are serialized into TransferState |
| Client hydration | Translations are restored from TransferState synchronously — no HTTP requests, no FOUC, no NG0501 |
| After hydration | TransferState entry is removed to free memory. Subsequent namespace loads use normal HTTP loading |
TranslationSSRConfig
| Option | Type | Description |
|---|---|---|
langFromRequest | (req: unknown) => string | Extract the language from the incoming HTTP request |
Error Behavior
Angular Translation Service is designed to be crash-proof. Here's how errors are handled:
| Scenario | Behavior |
|---|---|
| Namespace fails to load | Error logged to console. Template shows empty strings (via recursive proxy) |
| Missing translation key | Returns the raw key string (e.g., 'common:nav.missing') |
setLang() with unsupported language | Logs a warning and returns early — no state change |
| Accessing nested keys while loading | Recursive proxy returns empty strings, preventing template crashes |