5.2 KiB
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 informat-bridge.types.tsfor type-only imports without runtime cost noopFormatBridgeexported 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
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
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 toString(value)on undefined results. This means a partial bridge (onlynumberdefined) still renders date and currency placeholders — they just emit rawString(value)for the missing methods. noopFormatBridgeisObject.freezed at module load. This guards against accidental mutation in tests or downstream packages corrupting the shared singleton.currencyreturns${code} ${String(value)}rather thanString(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.tsto keep this implementation file zero-runtime-cost when imported for the bridge interface alone.