# `I18N_CODES` and `I18nErrorCode` — i18n error code registry ## Purpose `I18N_CODES` is the single, frozen registry of every fully-qualified error code that any package in the `@suaveplan/i18n*` family is permitted to throw. Keeping all 18 codes in one runtime constant has two non-negotiable benefits: (1) `instanceof I18nError` plus a `switch (err.code)` discrimination is exhaustive at the type level without importing every leaf, and (2) OTel span events, log sinks, and dashboards see one stable `"I18N."` prefix rather than 13 separate per-leaf prefixes. `I18nErrorCode` is the discriminant union derived from the registry — use it as the `TCode` parameter when extending `SuaveplanError`. ## Features - 18 codes covering parser, interpolation, pluralization, backends, formatters, detector, and plugin failures - Every code is namespaced under `"I18N."` (e.g. `"I18N.MISSING_KEY"`) - Frozen registry — `I18N_CODES.MISSING_KEY = "x"` throws at runtime - Type-level discriminant union (`I18nErrorCode`) is exhaustive - Back-compat alias `I18nCode` for pre-split call-sites ## Usage: Basic Example ```ts import { I18N_CODES, I18nError } from "@suaveplan/i18n-shared"; throw new I18nError(I18N_CODES.MISSING_KEY, "i18n.missing", { context: { key: "checkout.button" }, }); ``` ## Usage: Advanced Example ```ts import { I18N_CODES, I18nError, type I18nErrorCode, } from "@suaveplan/i18n-shared/errors"; function categorise(err: I18nError): "user" | "config" | "system" { const code: I18nErrorCode = err.code; switch (code) { case I18N_CODES.MISSING_KEY: case I18N_CODES.INVALID_LOCALE: return "user"; case I18N_CODES.INVALID_OPTIONS: case I18N_CODES.INVALID_FORMATTER_OPTIONS: case I18N_CODES.UNSUPPORTED_LOCALE: case I18N_CODES.PLUGIN_ALREADY_INSTALLED: return "config"; default: return "system"; } } ``` ## API Reference ### `I18N_CODES` Frozen `Record` of code-name → `"I18N."` literal. The 18 codes: | Cluster | Code | Failure | |---|---|---| | Parser | `MISSING_KEY` | Lookup found no value | | Parser | `INVALID_LOCALE` | Locale tag is not BCP-47 parseable | | Parser | `INVALID_OPTIONS` | Catalog/parser options failed validation | | Parser | `LOADER_FAILED` | Async catalog loader rejected | | Parser | `INTERPOLATION_ERROR` | Placeholder render threw | | Parser | `PLURAL_RULE_NOT_FOUND` | No plural rule for the locale | | Parser | `NESTING_DEPTH_EXCEEDED` | Recursion exceeded `maxDepth` | | Backend | `BACKEND_READ_FAILED` | Read raised | | Backend | `BACKEND_WRITE_FAILED` | Write raised | | Backend | `BACKEND_NOT_REGISTERED` | Lookup against unknown backend | | Backend | `BACKEND_LOCALE_NOT_AVAILABLE` | Locale missing from backend | | Backend | `BACKEND_TIMEOUT` | Backend exceeded its deadline | | Backend | `CHAINED_BACKEND_ALL_FAILED` | Every chain candidate raised | | Formatter | `INVALID_FORMATTER_OPTIONS` | Formatter opts rejected | | Formatter | `UNSUPPORTED_LOCALE` | Locale not in supported set | | Formatter | `FORMATTER_CACHE_OVERFLOW` | LRU cap exceeded | | Detector | `DETECTOR_CHAIN_EXHAUSTED` | No detector returned a locale | | Plugin | `PLUGIN_ALREADY_INSTALLED` | Duplicate plugin registration | ### `I18nErrorCode` `type I18nErrorCode = (typeof I18N_CODES)[keyof typeof I18N_CODES]` — discriminant union over all 18 fully-qualified strings. ### `I18nCode` Back-compat alias for `I18nErrorCode`. New code SHOULD prefer `I18nErrorCode`. ## Implementation Notes - The registry is constructed via `defineCodes("I18N", [...] as const)` from `@suaveplan/error`, which freezes the result with `Object.freeze` and infers the literal-typed `Record`. - The four "new" codes (`BACKEND_TIMEOUT`, `CHAINED_BACKEND_ALL_FAILED`, `DETECTOR_CHAIN_EXHAUSTED`, `FORMATTER_CACHE_OVERFLOW`) model failure modes the per-package split surfaces — they were latent in the monolith but never formally typed. - Cardinality 18 is asserted at type level via `expectTypeOf` in the unit test so future drift is caught at typecheck time.