Files
skills/docs/localization/i18n/i18n-shared/src/errors/error-codes.md
2026-05-29 08:20:56 +01:00

4.0 KiB

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

import { I18N_CODES, I18nError } from "@suaveplan/i18n-shared";

throw new I18nError(I18N_CODES.MISSING_KEY, "i18n.missing", {
    context: { key: "checkout.button" },
});

Usage: Advanced Example

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.