first commit
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# FormatBridge
|
||||
|
||||
## Purpose
|
||||
|
||||
`FormatBridge` is the dependency-injection seam that lets
|
||||
`@suaveplan/i18n-icu-parser` render ICU format placeholders
|
||||
(`{value, number}`, `{amount, number, currency=USD}`,
|
||||
`{date, date, short}`, `{ts, time, long}`,
|
||||
`{delta, relativeTime}`) without statically depending on any
|
||||
formatter package. The renderer (`renderNodes`) consults whichever
|
||||
methods are present on the bridge and falls back to
|
||||
`String(value)` when a method (or the bridge itself) is undefined.
|
||||
This keeps the parser package a true universal leaf with no
|
||||
`Intl.*` runtime cost when the consumer doesn't need locale-aware
|
||||
formatting.
|
||||
|
||||
The slim `@suaveplan/i18n` core (Wave 6) constructs a default
|
||||
bridge by importing the formatter packages
|
||||
(`@suaveplan/i18n-formatter-number`, `-date`, `-currency`,
|
||||
`-relative-time`) and threads it through every render call so
|
||||
consumers who use `createI18n({ ... })` get full ICU output with
|
||||
zero wiring. Consumers who construct a `MessageParser` directly
|
||||
MAY pass their own bridge or accept the no-Intl fallback.
|
||||
|
||||
## Features
|
||||
|
||||
- Five method slots, every one OPTIONAL: `number`, `currency`,
|
||||
`date`, `time`, `relativeTime`
|
||||
- Renderer never throws on missing bridge or missing method —
|
||||
always falls back to `String(value)` (or
|
||||
`${currency} ${String(value)}` for currency)
|
||||
- Style enums (`NumberFormatStyle`, `DateStyle`,
|
||||
`RelativeTimeStyle`) declared in `format-bridge.types.ts` for
|
||||
type-only imports without runtime cost
|
||||
- `noopFormatBridge` exported as a frozen singleton consumers can
|
||||
use directly or spread-and-extend
|
||||
- Single-file subpath export (`@suaveplan/i18n-icu-parser/parser/format-bridge`)
|
||||
so the slim core's wiring imports only this one module — keeps
|
||||
bundle size minimal
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { renderNodes, type FormatBridge } from "@suaveplan/i18n-icu-parser/parser";
|
||||
|
||||
const bridge: FormatBridge = {
|
||||
number: (value, locale) =>
|
||||
new Intl.NumberFormat(locale).format(value),
|
||||
};
|
||||
|
||||
const nodes = [
|
||||
{ type: "text" as const, value: "Total: " },
|
||||
{
|
||||
type: "typed-argument" as const,
|
||||
name: "amount",
|
||||
argType: "number" as const,
|
||||
style: "decimal",
|
||||
},
|
||||
];
|
||||
|
||||
const text = renderNodes(nodes, { amount: 1234.5 }, {
|
||||
locale: "en-US",
|
||||
formatBridge: bridge,
|
||||
});
|
||||
// "Total: 1,234.5"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
MessageParser,
|
||||
noopFormatBridge,
|
||||
type FormatBridge,
|
||||
} from "@suaveplan/i18n-icu-parser/parser";
|
||||
|
||||
// Spread-and-extend pattern: keep the no-op fallback for anything
|
||||
// you don't care about, override just the methods you need.
|
||||
const bridge: FormatBridge = {
|
||||
...noopFormatBridge,
|
||||
currency: (value, currency, locale) =>
|
||||
new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency,
|
||||
}).format(value),
|
||||
date: (value, locale, style = "medium") => {
|
||||
const d = value instanceof Date ? value : new Date(value);
|
||||
const presets: Record<string, Intl.DateTimeFormatOptions> = {
|
||||
short: { dateStyle: "short" },
|
||||
medium: { dateStyle: "medium" },
|
||||
long: { dateStyle: "long" },
|
||||
full: { dateStyle: "full" },
|
||||
};
|
||||
return new Intl.DateTimeFormat(locale, presets[style]).format(d);
|
||||
},
|
||||
};
|
||||
|
||||
const parser = new MessageParser();
|
||||
const out = parser.format(
|
||||
"Total: {amount, number, currency/USD} on {when, date, long}",
|
||||
{ amount: 1234.5, when: new Date("2026-05-05T00:00:00Z") },
|
||||
{ locale: "en-US", formatBridge: bridge }
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `interface FormatBridge`
|
||||
|
||||
| Method | Signature | Notes |
|
||||
|---|---|---|
|
||||
| `number?` | `(value, locale, style?) => string` | Style is `NumberFormatStyle \| string \| undefined` so notation styles (`compact`, `scientific`) pass through as raw strings |
|
||||
| `currency?` | `(value, currency, locale) => string` | `currency` is an ISO 4217 code |
|
||||
| `date?` | `(value, locale, style?) => string` | Default style is `medium` |
|
||||
| `time?` | `(value, locale, longForm?) => string` | `longForm: true` should include seconds |
|
||||
| `relativeTime?` | `(value, locale, style?) => string` | Style is `"long" \| "short" \| "narrow"` |
|
||||
|
||||
### `noopFormatBridge: Readonly<FormatBridge>`
|
||||
|
||||
Frozen identity bridge. Every method coerces inputs to
|
||||
`String(value)` (or `${currency} ${String(value)}` for
|
||||
`currency`). Safe to share by reference; mutation throws TypeError.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The bridge is intentionally OPTIONAL across the entire surface
|
||||
area. The renderer never asserts `formatBridge != null`; it
|
||||
uses optional chaining (`bridge?.number?.(...)`) and falls
|
||||
through to `String(value)` on undefined results. This means a
|
||||
partial bridge (only `number` defined) still renders date and
|
||||
currency placeholders — they just emit raw `String(value)` for
|
||||
the missing methods.
|
||||
- `noopFormatBridge` is `Object.freeze`d at module load. This
|
||||
guards against accidental mutation in tests or downstream
|
||||
packages corrupting the shared singleton.
|
||||
- `currency` returns `${code} ${String(value)}` rather than
|
||||
`String(value)` alone so the no-op fallback still surfaces the
|
||||
ISO 4217 code in test output, making fallback hits visible
|
||||
during development.
|
||||
- The style enums are declared in `format-bridge.types.ts` to keep
|
||||
this implementation file zero-runtime-cost when imported for
|
||||
the bridge interface alone.
|
||||
Reference in New Issue
Block a user