# WrI18n

> Reactive i18n service. Active locale lives on a signal, catalogs load lazily through a swappable loader (HTTP or static), and double-brace placeholders interpolate at render time. Scopes let feature modules ship and load their own catalogs. Keep ngwr's own strings with `provideWrI18nBaseCatalogs` rather than spreading `wrEn` into your catalog: a spread is shallow, so any namespace you share with ngwr, e.g. `common`, `validation`, `table`, keeps only the side spread last, and the other side's keys stop resolving with nothing logged — ngwr's components fall back to English and a `wrT` read of a lost key renders the raw key.

Source: https://ngwr.dev/reference/services/i18n  
Kind: Service

## Why ngwr provides this

Angular's built-in i18n bakes translations in at build time — one bundle per locale, no runtime switching. This service swaps catalogs at runtime, scopes keys per feature, and interpolates parameters.

## Live demo

Switch the active locale — every binding on the page updates without a re-render.

## Reading a key from TypeScript

There are two families here and picking the wrong one fails at runtime rather than at compile time. `useI18nText()`, `readI18nText()` and `useI18nFormatter()` all call `inject()`, so they only run in an **injection context** — a field initializer, a constructor, or inside `runInInjectionContext()`. Call one from a click handler, a `setTimeout`, a route resolver body or a module-level constant and Angular throws `NG0203`. The service's own `t()` and `translate()` have no such rule: you already hold the instance, so they work anywhere.

| Call | Returns | Where it may run | Use it for |
| --- | --- | --- | --- |
| `useI18nText(input, key, fallback)` | `Signal<string>` | Injection context | A component string a consumer can override with an input |
| `readI18nText(key, fallback)` | `Signal<string>` | Injection context | The same, with no override input to forward |
| `useI18nFormatter(key, fallback)` | `(params) => string` | Injection context | A label that interpolates per row |
| `i18n.translate(key, params?)` | `Signal<string>` | Anywhere | A reactive string built after construction |
| `i18n.t(key, params?)` | `string` | Anywhere | A one-shot read — a toast message, a log line |

```typescript
import { Component, inject } from '@angular/core';
import { WrI18n, readI18nText } from 'ngwr/i18n';

@Component({ /* … */ })
export class InvoiceCard {
  private readonly i18n = inject(WrI18n);

  // Field initializer — an injection context. Fine.
  protected readonly heading = readI18nText('invoice.heading', 'Invoice');

  protected onCopy(): void {
    // readI18nText('invoice.copied', 'Copied') here throws NG0203:
    // a click handler is not an injection context.
    this.toast.show(this.i18n.t('invoice.copied'));   // this is the way
  }
}

// Outside a component altogether — a resolver, an interceptor, a helper:
const label = runInInjectionContext(injector, () => readI18nText('x', 'X'));
```

`readI18nText()` returns a _signal_, and that is not a style choice: catalogs land a microtask after the first change-detection pass even with the static loader, so anything that read a plain string at construction time would freeze the English fallback for the life of the app. Keep the signal and call it in the template.

## Service

Everything on the injected `WrI18n` instance.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `WrI18n` | Injectable service. | `service` | `—` |
| `locale` | Active locale signal. Read-only — use `use(locale)` to write. | `Signal<string>` | `defaultLocale` |
| `use(locale)` | Switch the active locale. Ignored if outside `availableLocales`. Persists via WrStorage. | `(locale: string) => void` | `—` |
| `t(key, params?, scope?)` | Eager translate. Returns the value, or the missing-handler fallback. | `(key, params?, scope?) => string` | `—` |
| `translate(key, params?, scope?)` | Reactive translate — `Signal<string>` that re-evaluates on locale + catalog changes. | `(key, params?, scope?) => Signal<string>` | `—` |
| `registerScope(scope)` | Register a feature scope. Catalogs auto-load on every locale change. | `(scope: string) => Promise<WrI18nCatalog>` | `—` |
| `available()` | Available locales — pass-through from config. | `() => readonly string[]` | `—` |

## Providers

Wire the service up at bootstrap. Pick exactly one loader.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `provideWrI18n(options?)` | Root provider. Pass `defaultLocale`, `availableLocales`, an optional `missingHandler`, and an optional `loader`. Every field is optional — called bare it takes `defaultLocale` from Angular's `LOCALE_ID`. | `(options: ProvideWrI18nOptions = {}) => EnvironmentProviders` | `—` |
| `provideWrI18nStaticLoader(catalogs, scopes?)` | Inline catalogs at bootstrap. Best for small apps and SSR. `catalogs` is keyed by locale; `scopes` is keyed by scope name and then by locale, for a lazy feature that ships its own strings. A loader serves only the catalogs you pass it and ngwr registers none of its own: pass only your own keys here and add ngwr's with `provideWrI18nBaseCatalogs`. | `(catalogs: WrI18nStaticCatalogs, scopes?: WrI18nStaticScopedCatalogs) => Provider` | `—` |
| `provideWrI18nBaseCatalogs(catalogs)` | Register ngwr's shipped catalogs (`wrEn`, `wrRu`, …) underneath the loader, keyed by locale. The lookup walks them key by key once the loader's catalog misses, so your keys win and a namespace both sides define keeps both halves. Use it instead of `{ ...wrEn, ...yours }`: a spread is shallow, so any namespace you share with ngwr, e.g. `common`, `validation`, `table`, keeps only the side spread last, and the other side's keys stop resolving with nothing logged. | `(catalogs: WrI18nBaseCatalogs) => Provider` | `—` |
| `provideWrI18nHttpLoader({ path, rootPath? })` | Fetch JSON catalogs at runtime. `{locale}` and `{scope}` tokens interpolate. | `({ path, rootPath? }) => Provider` | `—` |

## Template helpers

The same service, reached from a template.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| ``WrTPipe — `\| wrT[: params][: scope]``` | Impure pipe. Re-evaluates on every CD cycle. | `pipe` | `—` |
| ``WrTDirective — `[wrT]="key" [wrTParams] [wrTScope]``` | Writes `textContent` of the host element on locale change. | `directive` | `—` |
| `useI18nFormatter(key, fallback)` | Returns a `(params) => string` helper for the given key. The second argument is the English fallback the helper serves when no catalog carries the key — it is required, not a scope. | `(key: string, fallback: string) => (params?: WrI18nParams) => string` | `—` |

## [wrT] inputs

The directive form, bound on any element.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `wrT`required | Translation key. Required. | `string` | — |
| `wrTParams` | Interpolation params for `{{name}}` tokens. | `WrI18nParams \| null` | `null` |
| `wrTScope` | Optional scope — scoped lookup first, then root fallback. | `string \| null` | `null` |

## See also

- [Translations](https://ngwr.dev/guides/translations) — How to actually use this: loaders, scopes, interpolation, a locale switcher.
- [WrStorage](https://ngwr.dev/reference/services/storage) — Used to persist the active locale across reloads. Swap the engine to change where.
