Files
skills/plugins/document-navigator/docs/localization/i18n/i18n-icu-parser
admin 9bf56a806b Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:10:00 +01:00
..

@suaveplan/i18n-icu-parser - ICU MessageFormat parser, {{var}} interpolation engine, and 9-language CLDR pluralization for the @suaveplan/i18n family. Universal leaf package — no formatter, backend, or core dependencies. Renders ICU placeholders through a FormatBridge DI seam so the package stays free of Intl wrappers.

Summary

ICU MessageFormat parser, {{var}} interpolation engine, and 9-language CLDR pluralization for the @suaveplan/i18n family. Universal leaf package — depends only on @suaveplan/i18n-shared, @suaveplan/error, @suaveplan/types, and the OTel API. No formatter, backend, detector, or core dependencies.

Documentation tree

  • interpolation — (no submodules)
    • escape-html — escapeHtmlChars converts the five HTML special characters (&, <, >, ", ') to their numeric / named entity e…
    • escape-regex — escapeRegex escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
    • interpolation — (no submodules)
  • observability — (no submodules)
  • format-bridge — FormatBridge is the dependency-injection seam that lets @suaveplan/i18n-icu-parser render ICU format placeholders (…
  • message-render — message-render provides two standalone utility functions for @suaveplan/i18n: extractVariables, which collects al…
  • message — MessageParser converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
  • pluralization — (no submodules)
- [escape-html](./src/interpolation/escape-html.md) — `escapeHtmlChars` converts the five HTML special characters (`&`, `<`, `>`, `"`, `'`) to their numeric / named entity e…
  • escape-regex — escapeRegex escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
  • interpolation — (no submodules)- observability — (no submodules)- metrics — (no submodules)- format-bridge — FormatBridge is the dependency-injection seam that lets @suaveplan/i18n-icu-parser render ICU format placeholders (…
  • message-render — message-render provides two standalone utility functions for @suaveplan/i18n: extractVariables, which collects al…
  • message — MessageParser converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
  • pluralization — (no submodules)- pluralization — (no submodules)- escape-html — escapeHtmlChars converts the five HTML special characters (&, <, >, ", ') to their numeric / named entity e…
  • escape-regex — escapeRegex escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
  • interpolation — (no submodules)- observability — (no submodules)- metrics — (no submodules)- format-bridge — FormatBridge is the dependency-injection seam that lets @suaveplan/i18n-icu-parser render ICU format placeholders (…
  • message-render — message-render provides two standalone utility functions for @suaveplan/i18n: extractVariables, which collects al…
  • message — MessageParser converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
  • pluralization — (no submodules)- pluralization — (no submodules)- escape-html — escapeHtmlChars converts the five HTML special characters (&, <, >, ", ') to their numeric / named entity e…
  • escape-regex — escapeRegex escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
  • interpolation — (no submodules)- observability — (no submodules)- metrics — (no submodules)- format-bridge — FormatBridge is the dependency-injection seam that lets @suaveplan/i18n-icu-parser render ICU format placeholders (…
  • message-render — message-render provides two standalone utility functions for @suaveplan/i18n: extractVariables, which collects al…
  • message — MessageParser converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
  • pluralization — (no submodules)- pluralization — (no submodules)- escape-html — escapeHtmlChars converts the five HTML special characters (&, <, >, ", ') to their numeric / named entity e…
  • escape-regex — escapeRegex escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
  • interpolation — (no submodules)- observability — (no submodules) - metrics — (no submodules)- format-bridge — FormatBridge is the dependency-injection seam that lets @suaveplan/i18n-icu-parser render ICU format placeholders (…
  • message-render — message-render provides two standalone utility functions for @suaveplan/i18n: extractVariables, which collects al…
  • message — MessageParser converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
  • pluralization — (no submodules) - pluralization — (no submodules)

Why this exists

The 14-package i18n modular split needs one home for the three pure-text-processing concerns that share the MessageNode AST, the INTERPOLATION_ERROR / PLURAL_RULE_NOT_FOUND / NESTING_DEPTH_EXCEEDED error codes, and the same renderer contract: ICU parsing, {{var}} interpolation, and CLDR pluralization. Splitting them further would fragment the seam without benefit; bundling them as @suaveplan/i18n-icu-parser keeps the AST internal and exposes a clean public surface (MessageParser, InterpolationEngine, PluralizationEngine, renderNodes) for the slim core to compose.

The FormatBridge seam

ICU format placeholders ({value, number}, {amount, number, currency=USD}, {date, date, short}, {ts, time, long}, {delta, relativeTime}) need locale-aware output. To stay free of the per-formatter packages, the renderer accepts a FormatBridge via RenderOptions.formatBridge. When the bridge (or a specific method on it) is undefined, the renderer falls back to String(value) and never throws — the parser remains fully usable in isolation, with no Intl.* runtime cost.

The slim @suaveplan/i18n core (Wave 6) constructs a default bridge by importing the formatter packages and threads it through createI18n({ ... }) so consumers who use the high-level façade get full ICU output with zero wiring.

import {
    MessageParser,
    type FormatBridge,
} from "@suaveplan/i18n-icu-parser";

const bridge: FormatBridge = {
    number: (v, locale) => new Intl.NumberFormat(locale).format(v),
    currency: (v, currency, locale) =>
        new Intl.NumberFormat(locale, { style: "currency", currency }).format(v),
};

const parser = new MessageParser();
parser.format(
    "{amount, number, currency/USD} on {when, date, short}",
    { amount: 1234.5, when: new Date() },
    { locale: "en-US", formatBridge: bridge }
);

Install

bun add @suaveplan/i18n-icu-parser

Quick Start

import {
    InterpolationEngine,
    MessageParser,
    PluralizationEngine,
} from "@suaveplan/i18n-icu-parser";

const parser = new MessageParser();
parser.format("{count, plural, one {1 item} other {# items}}", { count: 3 });
// "3 items"

const engine = new InterpolationEngine({ escapeHtml: true });
engine.render("hello {{ name }}", { name: "Ada" });
// { text: "hello Ada", missingVariables: [] }

const plural = new PluralizationEngine();
plural.resolve(0, { zero: "none", one: "one", other: "many" }, { locale: "ar" });
// { category: "zero", message: "none", count: 0 }

Modules

Subpath Description
@suaveplan/i18n-icu-parser Root barrel — every public symbol
@suaveplan/i18n-icu-parser/parser MessageParser, renderNodes, extractVariables, FormatBridge, noopFormatBridge, AST types
@suaveplan/i18n-icu-parser/parser/format-bridge FormatBridge interface + noopFormatBridge ONLY (single-file import for slim-core wiring)
@suaveplan/i18n-icu-parser/interpolation InterpolationEngine, escapeHtmlChars, escapeRegex, InterpolationConfigSchema, types
@suaveplan/i18n-icu-parser/pluralization PluralizationEngine, the 9 plural-rule functions, defaultPluralRule, normalizeLocale, getBaseLocale, types

Modules Documentation

Public Surface (snapshot)

Export Kind Origin
MessageParser class parser/message.ts
extractVariables function parser/message-render.ts
renderNodes function parser/message-render.ts
renderToChunks function parser/message-render.ts
preprocessIcuApostrophes function parser/message.ts
findUnknownArgTypes function parser/message.ts
FormatBridge type parser/format-bridge.ts
noopFormatBridge const parser/format-bridge.ts
MessageNode, ParsedMessage, ParserOptions, RenderOptions, RenderContext, ValidationIssue, ValidationResult types parser/*.types.ts
InterpolationEngine class interpolation/interpolation.ts
escapeHtmlChars function interpolation/escape-html.ts
escapeRegex function interpolation/escape-regex.ts
InterpolationConfigSchema const interpolation/interpolation.schemas.ts
PluralizationEngine class pluralization/pluralization.ts
arabicPlural / chinesePlural / englishPlural / frenchPlural / germanPlural / japanesePlural / polishPlural / russianPlural / welshPlural / spanishPlural / defaultPluralRule functions pluralization/pluralization.ts
recordParseDuration / recordInterpolationDuration / recordPluralDuration functions observability/metrics.ts

Versioning

Part of the i18n modular-split rollout. Stays at 0.1.0 until the slim core (Wave 6) cuts the family 1.0.0 release. Test titles in parser/message.test.ts, interpolation/interpolation.test.ts, and pluralization/pluralization.test.ts mirror the pre-split monolith one-for-one so the core's conformance suite re-runs unchanged against the bridged surface.

Wave 2 Scope

Pure addition. packages/i18n/i18n/src/ retains its copies of the parser, interpolation engine, and pluralization sources; both copies coexist during Waves 2–5 and the monolith remains green. The slim core (Wave 6) deletes the monolith copies and re-exports from this package.