Skip to content

Overview

@xndrjs/i18n is a compiler-first, type-safe i18n system based on ICU MessageFormat. Local dictionary files (JSON or YAML) are the authoring source; a build-time codegen step parses ICU templates and generates exact TypeScript types for keys and parameters. At runtime a shared engine caches compiled messages; a generated handle (createI18n) loads namespaces on demand and exposes typed t() on a locale-bound scope.

Namespaces are always multi-namespace (t(namespace, key, params?)). Delivery is split-by-locale (default) or custom areas — there is no single-file / eager-bundle mode.

For motivation and the developer journey (SSR/CSR, React gates, CMS refresh without rebuild), see Type-safe i18n for TypeScript and React. React bindings live in @xndrjs/i18n-react.

flowchart TD
  sources[JSON or YAML dictionaries] --> codegen[xndrjs-i18n-codegen]
  codegen --> compiled[generated/translations/*.json]
  codegen --> types[i18n-types.generated.ts]
  codegen --> loaders[namespace-loaders.generated.ts]
  codegen --> factory[instance.generated.ts]
  codegen --> schema[dictionary-schema.generated.ts]
  sources --> audit[xndrjs-i18n-audit]
  CMS[CMS or API payload] --> validate[validateExternal* optional]
  validate --> authoring[update authoring files]
  authoring --> regen[regenerateNamespaces]
  regen --> compiled
  factory --> handle[createI18n handle]
  loaders --> handle
  handle --> scope["load → locale-bound t()"]
PageTopics
DictionariesJSON shape, YAML authoring, serving from public/
DeliverySplit-by-locale and custom areas
CodegenICU inference, generated files, runCodegen vs regenerateNamespaces
RuntimeHandle: createI18n({ state?, fetchImpl? }), load / peek / serialize
React@xndrjs/i18n-react: I18nRoot, withI18n, <I18n>
Locale fallbackFallback chains, locale projection helpers
Lazy loadingNamespace loaders, load({ namespaces, locale }), fetch DI
External validationCMS/API payloads before writing authoring files
Configurationi18n.codegen.json reference
Errors & exportsError prefixes, package exports
Terminal window
pnpm add @xndrjs/i18n zod
pnpm add -D tsx

For React apps, also install @xndrjs/i18n-react (peer: react ≥ 19). See React.

DependencyRole
@xndrjs/i18nRuntime providers, validation helpers, codegen CLI (xndrjs-i18n-codegen), audit CLI (xndrjs-i18n-audit)
tsxPeer dependency (dev) — codegen and audit CLIs run TypeScript directly
zodPeer dependency — validates i18n.codegen.json; also powers generated validateExternal* helpers in dictionary-schema.generated.ts

Production bundles depend on @xndrjs/i18n and intl-messageformat (pulled in by the package). Codegen runs at build time only.

Terminal window
pnpm --filter YourPackageOrAppName exec xndrjs-i18n-setup multi .
pnpm --filter YourPackageOrAppName exec xndrjs-i18n-setup multi apps/myapp --project MyApp

multi is optional (scaffolding is always multi-namespace). This creates under the target directory (for example i18n/):

  • i18n.codegen.json
  • starter translation files (JSON by default; YAML supported)
  • index.ts exporting createI18n() (and generated types)

Pass a src/ parent when your app uses a src/ layout (for example xndrjs-i18n-setup multi src).

Add to package.json. Prefer a single script that runs core codegen first, then React bindings when you use @xndrjs/i18n-react:

{
"scripts": {
"i18n:codegen": "xndrjs-i18n-codegen --config i18n/i18n.codegen.json",
"i18n:react-codegen": "xndrjs-i18n-react-codegen --config i18n/i18n.codegen.json",
"i18n:generate": "pnpm run i18n:codegen && pnpm run i18n:react-codegen",
"i18n:audit": "xndrjs-i18n-audit --config i18n/i18n.codegen.json"
}
}

Run after every change to translation files (JSON or YAML) — or wire into your build:

Terminal window
pnpm --filter YourPackageOrAppName run i18n:generate

Default config path is i18n/i18n.codegen.json. Paths inside the config are relative to the directory containing that file.

Terminal window
# Report to stdout (exit 0 even when gaps exist)
pnpm --filter YourPackageOrAppName exec xndrjs-i18n-audit --config i18n/i18n.codegen.json
# Write the same JSON report to a file
pnpm --filter YourPackageOrAppName exec xndrjs-i18n-audit --config i18n/i18n.codegen.json --out audit.json
# CI gate: exit 1 when runtime would still miss strings after fallback
pnpm --filter YourPackageOrAppName exec xndrjs-i18n-audit --config i18n/i18n.codegen.json --fail-on effective
FlagPurpose
--config <path>Path to i18n.codegen.json (default: i18n/i18n.codegen.json relative to cwd).
--out <path>Write the JSON report to a file instead of stdout.
--fail-on effective | direct | anyOptional. Without it, the CLI always exits 0 (report-only). With it, exits 1 when gaps remain: effective = t() would throw; direct = no string in the dictionary for that locale; any = either.
--allow-emptyTreat "" as a valid template (default: empty strings count as missing).

Produces a JSON report of missing translations per namespace and locale. requiredLocales are all locales your i18n instance can use.

Report fieldMeaning
missingDirectByLocaleThe key has no string for that locale in the dictionary. Often fine at runtime if fallback supplies another locale — mainly a translator to-do list.
missingEffectiveByLocaleNo string for that locale, and fallback cannot find one either. t() would throw — fix before shipping.

When localeFallback is set in config, codegen enriches generated LOCALE_FALLBACK with [locale]: null for every MyProjectLocale not explicitly configured (runtime unchanged).