first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
# `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.<code>"` 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.