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,106 @@
# @suaveplan/i18n-formatter-display-name - Intl.DisplayNames wrapper for the @suaveplan/i18n family — locale-aware language/region/script/currency/calendar display names. Per-(locale, type, style, fallback) cache, Zod-validated options, OTel histogram + cache-hit counter, structured I18nError mapping. Universal leaf package.
## Summary
Wrapper around `Intl.DisplayNames` for the `@suaveplan/i18n` family —
locale-aware language, region, script, currency, and calendar display
names. Universal leaf package; depends only on
`@suaveplan/i18n-shared`, `@suaveplan/types`, `@suaveplan/error`,
`@opentelemetry/api`, and `zod`.
<!-- begin:auto-docs -->
## Documentation tree
- [formatter](./src/formatter/formatter.md) — `DisplayNameFormatter` and the `formatDisplayName` standalone helper expose `Intl.DisplayNames` for the `@suaveplan/i18…
- [formatter](./src/formatter/formatter.md) — `DisplayNameFormatter` and the `formatDisplayName` standalone helper expose `Intl.DisplayNames` for the `@suaveplan/i18…
- [helpers](./src/helpers/helpers.md) — The five ergonomic helpers (`formatLanguageName`, `formatRegionName`, `formatCurrencyName`, `formatScriptName`, `format…
- [helpers](./src/helpers/helpers.md) — The five ergonomic helpers (`formatLanguageName`, `formatRegionName`, `formatCurrencyName`, `formatScriptName`, `format…
- [observability](./src/observability/observability.md) — The DisplayName formatter exports two OpenTelemetry instruments and a small in-memory state ring so spec-conformance te…
- [observability](./src/observability/observability.md) — The DisplayName formatter exports two OpenTelemetry instruments and a small in-memory state ring so spec-conformance te…
<!-- end:auto-docs -->
## Why this exists
The 14-package i18n modular split (cross-cutting design
`2026-05-05-i18n-modular-split.md`) lifts each `Intl.*` wrapper out of
the monolith into its own sibling package. Lifting the display-name
helper out of `@suaveplan/i18n` lets consumers that only need
country-name / currency-name rendering pull a single small package
instead of the entire i18n core.
The package adds the canonical Wave-3 formatter shape on top of the
monolith's standalone helper: Zod-validated options, a per-(locale,
type, style, fallback) cache, OTel histogram + cache-hit counter, the
class wrapper for symmetry with `NumberFormatter` /
`RelativeTimeFormatter` / `ListFormatter` / `DateFormatter`, and five
ergonomic helpers that pre-bind `formatDisplayName` with each
canonical `type` value.
## Install
```bash
bun add @suaveplan/i18n-formatter-display-name
```
## Quick Start
```ts
import {
DisplayNameFormatter,
formatDisplayName,
formatLanguageName,
formatRegionName,
formatCurrencyName,
} from "@suaveplan/i18n-formatter-display-name";
formatDisplayName("GB", "en-GB", { type: "region" }); // "United Kingdom"
formatLanguageName("fr", "en-GB"); // "French"
formatRegionName("US", "en-GB"); // "United States"
formatCurrencyName("USD", "en-GB"); // "US Dollar"
const f = new DisplayNameFormatter({ type: "region", style: "short" });
f.format("DE", "en-GB"); // "Germany"
```
## Modules
| Subpath | Description |
|---|---|
| `@suaveplan/i18n-formatter-display-name` | Root barrel — class + standalones + helpers + observability |
| `@suaveplan/i18n-formatter-display-name/formatter` | `DisplayNameFormatter`, `formatDisplayName`, types, schema |
| `@suaveplan/i18n-formatter-display-name/helpers` | Five ergonomic helpers — `formatLanguageName`, `formatRegionName`, `formatCurrencyName`, `formatScriptName`, `formatCalendarName` |
## Modules Documentation
- [formatter.md](src/formatter.md) — `DisplayNameFormatter` class, `formatDisplayName`, `clearDisplayNameCache`
- [helpers.md](src/helpers.md) — five ergonomic helpers
- [observability.md](src/observability.md) — OTel histogram + cache-hit counter
## Public Surface (snapshot)
| Export | Kind | Origin |
|---|---|---|
| `DisplayNameFormatter` | class | `formatter.ts` |
| `formatDisplayName` | function | `formatter.ts` |
| `clearDisplayNameCache` | function | `formatter.ts` |
| `DisplayNameFormatterOptionsSchema` | Zod schema | `formatter.schemas.ts` |
| `DisplayNameFormatterOptions` | type | `formatter.types.ts` |
| `DisplayNameType` | type | `formatter.types.ts` |
| `DisplayNameStyle` | type | `formatter.types.ts` |
| `DisplayNameFallback` | type | `formatter.types.ts` |
| `DisplayNameFormatterCacheKey` | type | `formatter.types.ts` |
| `formatLanguageName` | function | `helpers.ts` |
| `formatRegionName` | function | `helpers.ts` |
| `formatCurrencyName` | function | `helpers.ts` |
| `formatScriptName` | function | `helpers.ts` |
| `formatCalendarName` | function | `helpers.ts` |
| `recordDisplayNameFormat` | function | `observability.ts` |
| `recordDisplayNameCacheHit` | function | `observability.ts` |
| `getDisplayNameMetricsState` | function | `observability.ts` |
| `resetDisplayNameMetricsState` | function | `observability.ts` |
## Versioning
Part of the 14-package i18n modular-split rollout. Stays at `0.1.0`
until the slim core (Wave 6) cuts the family `1.0.0` release.

View File

@@ -0,0 +1,98 @@
# DisplayNameFormatter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`DisplayNameFormatter` and the `formatDisplayName` standalone helper
expose `Intl.DisplayNames` for the `@suaveplan/i18n` family at the
canonical Wave-3 formatter surface: Zod-validated options, structured
error mapping, per-(locale, type, style, fallback) cache, and OTel
emission. Consumers convert ISO codes (`"GB"`, `"USD"`, `"fr"`,
`"gregory"`) into their localised display names — the universal way to
render country names, currencies, languages, scripts, and calendar
identifiers in a user's locale.
## Features
- `DisplayNameFormatter` class with stored options + `format(code, locale)` method
- `formatDisplayName(code, locale, options)` standalone helper
- All six `Intl.DisplayNames` `type` values: `language` / `region` / `script` / `currency` / `calendar` / `dateTimeField`
- Three `style` values: `long` / `short` / `narrow`
- Two `fallback` policies: `code` (default) / `none`
- Construction-time Zod validation with `INVALID_FORMATTER_OPTIONS` mapping
- `Intl.DisplayNames` `RangeError` mapped to `UNSUPPORTED_LOCALE` with original error attached as `cause`
- Per-(locale, type, style, fallback) cache — repeated calls reuse the same `Intl.DisplayNames`
- `clearDisplayNameCache()` test-only helper
## Usage: Basic Example
```ts
import { formatDisplayName } from "@suaveplan/i18n-formatter-display-name";
formatDisplayName("GB", "en-GB", { type: "region" }); // "United Kingdom"
formatDisplayName("fr", "en-GB", { type: "language" }); // "French"
formatDisplayName("USD", "en-GB", { type: "currency" }); // "US Dollar"
```
## Usage: Advanced Example
```ts
import {
DisplayNameFormatter,
type DisplayNameFormatterOptions,
} from "@suaveplan/i18n-formatter-display-name";
const opts: DisplayNameFormatterOptions = {
type: "region",
style: "short",
fallback: "none",
};
const f = new DisplayNameFormatter(opts);
// Reuses the same cached Intl.DisplayNames per locale.
const us = f.format("US", "en-GB");
const fr = f.format("FR", "fr-FR");
// Unknown region with fallback: "none" returns the code unchanged.
const unknown = f.format("ZZ", "en-GB"); // "ZZ"
```
## API Reference
### `formatDisplayName(code, locale, options): string`
Standalone helper. Validates `options` via Zod, looks up or constructs
the cached `Intl.DisplayNames`, and returns
`formatter.of(code) ?? code`. Throws `I18nError(INVALID_FORMATTER_OPTIONS)`
on Zod failure or `I18nError(UNSUPPORTED_LOCALE)` on
`Intl.DisplayNames` `RangeError`.
### `class DisplayNameFormatter`
- `constructor(options: DisplayNameFormatterOptions)` — stores options after Zod validation
- `format(code: string, locale: string): string` — same semantics as the standalone
### `clearDisplayNameCache(): void`
Test-only helper that empties the per-(locale, type, style, fallback)
cache.
## Implementation Notes
- The cache is a process-wide `Map<string, Intl.DisplayNames>` keyed
by `${locale}|${type}|${style}|${fallback}`. The single shared
cache is a deliberate choice — `Intl.DisplayNames` instances are
immutable, so cross-call reuse is safe.
- `formatter.of(code)` returns `undefined` when the input is not a
recognised code AND `fallback: "none"` was specified. The `?? code`
fallback returns the input unchanged in that case.
- OTel histogram `i18n.format.display_name.duration_ms` records the
end-to-end format time per call; counter
`i18n.format.cache_hit{formatter="display_name"}` increments on
cache reuse only (not on first miss).
- Construction-time validation ensures the class never wraps invalid
options. The standalone re-validates on every call (cheap — a Zod
`safeParse` over a 3-field object).

View File

@@ -0,0 +1,98 @@
# Display-name ergonomic helpers
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The five ergonomic helpers (`formatLanguageName`, `formatRegionName`,
`formatCurrencyName`, `formatScriptName`, `formatCalendarName`)
pre-bind `formatDisplayName` with the required `type` discriminant so
consumers don't have to remember the discriminant string. Each helper
forwards an optional `style` argument to the underlying call.
The helpers exist because `Intl.DisplayNames` requires the caller to
pass `{ type: "<x>" }` on every call — across a real-world UI codebase
that adds noise and creates a chance to typo a discriminant. The
helpers eliminate both.
## Features
- `formatLanguageName(code, locale, style?)` — pre-binds `type: "language"`
- `formatRegionName(code, locale, style?)` — pre-binds `type: "region"`
- `formatCurrencyName(code, locale, style?)` — pre-binds `type: "currency"`
- `formatScriptName(code, locale, style?)` — pre-binds `type: "script"`
- `formatCalendarName(code, locale, style?)` — pre-binds `type: "calendar"`
- All accept the optional `style` parameter (`"long" | "short" | "narrow"`); when omitted, the underlying `formatDisplayName` defaults to `"long"`.
## Usage: Basic Example
```ts
import {
formatLanguageName,
formatRegionName,
formatCurrencyName,
} from "@suaveplan/i18n-formatter-display-name";
formatLanguageName("fr", "en-GB"); // "French"
formatRegionName("GB", "en-GB"); // "United Kingdom"
formatCurrencyName("USD", "en-GB"); // "US Dollar"
```
## Usage: Advanced Example
```ts
import {
formatLanguageName,
formatRegionName,
formatScriptName,
} from "@suaveplan/i18n-formatter-display-name";
// Build a locale picker — language + region in the user's locale.
const locales = ["fr-FR", "en-GB", "ja-JP", "zh-Hans-CN"];
const userLocale = "en-GB";
const labels = locales.map((tag) => {
const [lang, ...rest] = tag.split("-");
const region = rest[rest.length - 1];
const lname = formatLanguageName(lang, userLocale);
const rname = region ? formatRegionName(region, userLocale) : "";
return rname ? `${lname} (${rname})` : lname;
});
// ["French (France)", "English (United Kingdom)", "Japanese (Japan)", "Chinese (China)"]
// Render script name in narrow style for compact UIs.
const narrow = formatScriptName("Latn", "en-GB", "narrow");
```
## API Reference
Every helper has the signature
`(code: string, locale: string, style?: DisplayNameStyle) => string`.
- **`formatLanguageName(code, locale, style?)`** — Returns the language
display name. `code` is a BCP-47 language subtag (`"fr"`, `"zh"`,
`"en-GB"` is also accepted by the underlying `Intl.DisplayNames`).
- **`formatRegionName(code, locale, style?)`** — Returns the region
display name. `code` is an ISO 3166-1 alpha-2 region code (`"GB"`,
`"US"`).
- **`formatCurrencyName(code, locale, style?)`** — Returns the currency
display name. `code` is an ISO 4217 currency code (`"USD"`,
`"EUR"`).
- **`formatScriptName(code, locale, style?)`** — Returns the script
display name. `code` is an ISO 15924 script code (`"Latn"`,
`"Cyrl"`).
- **`formatCalendarName(code, locale, style?)`** — Returns the calendar
display name. `code` is a Unicode calendar identifier (`"gregory"`,
`"buddhist"`, `"islamic"`).
Every helper throws `I18nError(UNSUPPORTED_LOCALE)` on a malformed
locale tag (matching the standalone semantics).
## Implementation Notes
- Each helper is a one-line wrapper around `formatDisplayName`. There
is no separate cache layer — the underlying `formatDisplayName`
cache by `(locale, type, style, fallback)` covers all five.
- The helpers are pure functions with no shared state.

View File

@@ -0,0 +1,98 @@
# DisplayName observability
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The DisplayName formatter exports two OpenTelemetry instruments and a
small in-memory state ring so spec-conformance tests can assert exact
attribute payloads without standing up a full SDK exporter. The
instruments match the canonical Wave-3 formatter contract — every
sibling formatter (number, relative-time, list, date, currency,
collator, segmenter) emits the same two metric names so dashboards
and alerts compose cleanly across the family.
## Features
- Histogram `i18n.format.display_name.duration_ms` (unit `ms`) — per-format wall-clock time
- Counter `i18n.format.cache_hit{formatter="display_name"}` — increments on cache reuse only
- In-memory state ring (`getDisplayNameMetricsState()`) for spec-conformance assertions
- `resetDisplayNameMetricsState()` test-only helper to clear the ring between cases
## Usage: Basic Example
```ts
import { metrics } from "@opentelemetry/api";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
// import your MetricReader / OTLP exporter ...
const provider = new MeterProvider({ readers: [/* reader */] });
metrics.setGlobalMeterProvider(provider);
// Subsequent formatDisplayName calls now emit through your reader.
import { formatDisplayName } from "@suaveplan/i18n-formatter-display-name";
formatDisplayName("fr", "en-GB", { type: "language" });
```
## Usage: Advanced Example
```ts
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
clearDisplayNameCache,
formatDisplayName,
} from "@suaveplan/i18n-formatter-display-name";
import {
getDisplayNameMetricsState,
resetDisplayNameMetricsState,
} from "@suaveplan/i18n-formatter-display-name";
it("emits histogram + cache-hit counter", () => {
clearDisplayNameCache();
resetDisplayNameMetricsState();
formatDisplayName("fr", "en-GB", { type: "language" });
formatDisplayName("fr", "en-GB", { type: "language" });
const state = getDisplayNameMetricsState();
expect(state.durationSamples).toHaveLength(2);
expect(state.cacheHitSamples).toHaveLength(1);
});
```
## API Reference
### `recordDisplayNameFormat(locale, type, style, durationMs): void`
Records the duration histogram sample. Tags: `locale`, `type`,
`style`. Called internally by `formatDisplayName`; consumers do not
need to invoke it directly.
### `recordDisplayNameCacheHit(): void`
Increments the cache-hit counter with the constant tag
`{ formatter: "display_name" }`. Called internally on a cache hit.
### `getDisplayNameMetricsState(): DisplayNameMetricsState`
Returns the in-memory ring of `durationSamples` and `cacheHitSamples`.
Used by spec-conformance tests to assert exact attribute payloads.
### `resetDisplayNameMetricsState(): void`
Clears both ring buffers. Test-only helper; call before the
under-test code to assert exactly the samples your scenario
generates.
## Implementation Notes
- The OTel meter is named `suaveplan.i18n.formatter.display_name`.
Every formatter in the family has its own meter so OTel resource
attributes can be tied per-package if a consumer chooses.
- Sample dashboard query (PromQL/Grafana over OTLP):
`histogram_quantile(0.99, sum(rate(i18n_format_display_name_duration_ms_bucket[5m])) by (le, locale))`
- The in-memory ring is intentionally unbounded — tests are short and
call `resetDisplayNameMetricsState()` between cases. A long-running
test loop should call reset to avoid memory growth.
- `style` defaults to `"long"` in the histogram tag when the caller
omits it; `type` is always required so it is never defaulted.