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,182 @@
# @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.
<!-- begin:auto-docs -->
## Documentation tree
- [interpolation](./src/interpolation/interpolation.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [escape-html](./src/interpolation/escape-html.md) — `escapeHtmlChars` converts the five HTML special characters (`&`, `<`, `>`, `"`, `'`) to their numeric / named entity e…
- [escape-regex](./src/interpolation/escape-regex.md) — `escapeRegex` escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
- [interpolation](./src/interpolation/interpolation.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [observability](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [metrics](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [format-bridge](./src/parser/format-bridge.md) — `FormatBridge` is the dependency-injection seam that lets `@suaveplan/i18n-icu-parser` render ICU format placeholders (…
- [message-render](./src/parser/message-render.md) — `message-render` provides two standalone utility functions for `@suaveplan/i18n`: `extractVariables`, which collects al…
- [message](./src/parser/message.md) — `MessageParser` converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [escape-html](./src/interpolation/escape-html.md) — `escapeHtmlChars` converts the five HTML special characters (`&`, `<`, `>`, `"`, `'`) to their numeric / named entity e…
- [escape-regex](./src/interpolation/escape-regex.md) — `escapeRegex` escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
- [interpolation](./src/interpolation/interpolation.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metrics](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-bridge](./src/parser/format-bridge.md) — `FormatBridge` is the dependency-injection seam that lets `@suaveplan/i18n-icu-parser` render ICU format placeholders (…
- [message-render](./src/parser/message-render.md) — `message-render` provides two standalone utility functions for `@suaveplan/i18n`: `extractVariables`, which collects al…
- [message](./src/parser/message.md) — `MessageParser` converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_- [escape-html](./src/interpolation/escape-html.md) — `escapeHtmlChars` converts the five HTML special characters (`&`, `<`, `>`, `"`, `'`) to their numeric / named entity e…
- [escape-regex](./src/interpolation/escape-regex.md) — `escapeRegex` escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
- [interpolation](./src/interpolation/interpolation.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metrics](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-bridge](./src/parser/format-bridge.md) — `FormatBridge` is the dependency-injection seam that lets `@suaveplan/i18n-icu-parser` render ICU format placeholders (…
- [message-render](./src/parser/message-render.md) — `message-render` provides two standalone utility functions for `@suaveplan/i18n`: `extractVariables`, which collects al…
- [message](./src/parser/message.md) — `MessageParser` converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_- [escape-html](./src/interpolation/escape-html.md) — `escapeHtmlChars` converts the five HTML special characters (`&`, `<`, `>`, `"`, `'`) to their numeric / named entity e…
- [escape-regex](./src/interpolation/escape-regex.md) — `escapeRegex` escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
- [interpolation](./src/interpolation/interpolation.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [metrics](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-bridge](./src/parser/format-bridge.md) — `FormatBridge` is the dependency-injection seam that lets `@suaveplan/i18n-icu-parser` render ICU format placeholders (…
- [message-render](./src/parser/message-render.md) — `message-render` provides two standalone utility functions for `@suaveplan/i18n`: `extractVariables`, which collects al…
- [message](./src/parser/message.md) — `MessageParser` converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_- [escape-html](./src/interpolation/escape-html.md) — `escapeHtmlChars` converts the five HTML special characters (`&`, `<`, `>`, `"`, `'`) to their numeric / named entity e…
- [escape-regex](./src/interpolation/escape-regex.md) — `escapeRegex` escapes every regular-expression metacharacter in a string so the string can be embedded literally inside…
- [interpolation](./src/interpolation/interpolation.md) — <!-- begin:auto-docs --> _(no submodules)_- [observability](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_ - [metrics](./src/observability/metrics.md) — <!-- begin:auto-docs --> _(no submodules)_- [format-bridge](./src/parser/format-bridge.md) — `FormatBridge` is the dependency-injection seam that lets `@suaveplan/i18n-icu-parser` render ICU format placeholders (…
- [message-render](./src/parser/message-render.md) — `message-render` provides two standalone utility functions for `@suaveplan/i18n`: `extractVariables`, which collects al…
- [message](./src/parser/message.md) — `MessageParser` converts ICU-like message strings into a typed AST (Abstract Syntax Tree), and provides utilities to re…
- [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(no submodules)_ - [pluralization](./src/pluralization/pluralization.md) — <!-- begin:auto-docs --> _(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.
```typescript
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
```bash
bun add @suaveplan/i18n-icu-parser
```
## Quick Start
```typescript
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
- [parser/format-bridge.md](src/parser/format-bridge.md) — DI seam between this package and the formatter packages
- [parser/message.md](src/parser/message.md) — `MessageParser` class
- [parser/message-render.md](src/parser/message-render.md) — `renderNodes` / `extractVariables` standalone helpers
- [interpolation/interpolation.md](src/interpolation/interpolation.md) — `InterpolationEngine`
- [interpolation/escape-html.md](src/interpolation/escape-html.md) — `escapeHtmlChars` standalone helper
- [interpolation/escape-regex.md](src/interpolation/escape-regex.md) — `escapeRegex` standalone helper
- [pluralization/pluralization.md](src/pluralization/pluralization.md) — `PluralizationEngine` + 9 plural-rule fns
- [observability/metrics.md](src/observability/metrics.md) — OTel histogram recorders
## 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.