Files
skills/docs/localization/i18n/i18n-icu-parser/src/parser/format-bridge.md
2026-05-29 08:20:56 +01:00

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 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

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 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.freezed 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.