183 lines
14 KiB
Markdown
183 lines
14 KiB
Markdown
# @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.
|
||
|