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.

OptionTypeDescription
defaultLangstringDefault language code (e.g., 'en')
supportedLangsstring[]All supported language codes
coreNamespacesstring[]Namespaces preloaded via APP_INITIALIZER before the app renders
loaderTranslationLoaderFunction that loads translation files. Use httpLoader() or importLoader()
detectLanguagebooleanEnable browser language detection via navigator.language
storageKeystringlocalStorage key for persisting language preference across sessions
namespaceSeparatorstringSeparator between namespace and key path (default: ':')
fallbackChainRecord<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

AccessResult
String coercion / template interpolationReturns the key path (e.g., 'common.nav.title')
Property accessReturns another recursive proxy
JSON serializationReturns {}
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.

  1. SSR token — Language injected via CURRENT_LANGUAGE on the server (always checked)
  2. localStorage — Previously persisted preference (always checked if storageKey is configured)
  3. Browsernavigator.language with base-language matching (e.g., browser 'pt' matches config 'pt-BR') (only when detectLanguage: true)
  4. 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

PhaseWhat happens
Server renderCURRENT_LANGUAGE is set from the request, core namespaces load, then all loaded translations are serialized into TransferState
Client hydrationTranslations are restored from TransferState synchronously — no HTTP requests, no FOUC, no NG0501
After hydrationTransferState entry is removed to free memory. Subsequent namespace loads use normal HTTP loading

TranslationSSRConfig

OptionTypeDescription
langFromRequest(req: unknown) => stringExtract the language from the incoming HTTP request

Error Behavior

Angular Translation Service is designed to be crash-proof. Here's how errors are handled:

ScenarioBehavior
Namespace fails to loadError logged to console. Template shows empty strings (via recursive proxy)
Missing translation keyReturns the raw key string (e.g., 'common:nav.missing')
setLang() with unsupported languageLogs a warning and returns early — no state change
Accessing nested keys while loadingRecursive proxy returns empty strings, preventing template crashes