97 lines
7.7 KiB
Markdown
97 lines
7.7 KiB
Markdown
# `@suaveplan/i18n-formatter-display-name` — `Intl.DisplayNames` wrapper
|
||
|
||
**Package**: `@suaveplan/i18n-formatter-display-name` (NEW)
|
||
**Status**: PROPOSED
|
||
**Type**: feature (new universal-tier package; verbatim port of `formatting/display-name/` plus class wrapper + Zod schema + observability uplift)
|
||
**Date**: 2026-05-05
|
||
**Wave**: 3 (depends on Wave 1 foundation; required by Wave 6 core slim)
|
||
**Design doc**: `openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md` (especially §5 formatter package shape, §7.1)
|
||
**Path**: `packages/i18n/i18n-formatter-display-name/`
|
||
**Tier**: universal
|
||
|
||
## Why
|
||
|
||
The 14-package i18n modular split lifts each `Intl.*` wrapper out of the monolith into its own sibling package. `@suaveplan/i18n-formatter-display-name` ports the existing `formatting/display-name/formatDisplayName` standalone helper, adds the `DisplayNameFormatter` class wrapper for symmetry with the other formatters, and brings the package up to the canonical Wave-3 shape.
|
||
|
||
Today `formatting/display-name/` ships a thin slice:
|
||
|
||
- `formatDisplayName(code, locale, options)` standalone helper (`packages/i18n/i18n/src/formatting/display-name/display-name.ts` L57–L64)
|
||
- `clearDisplayNameCache` test-only helper (`display-name.ts` L67–L69)
|
||
- Per-(locale, type, style, fallback) cache (`display-name.ts` L21, L23–L47)
|
||
- `UNSUPPORTED_LOCALE` mapping (`display-name.ts` L40–L46)
|
||
- `DisplayNameType`, `DisplayNameStyle`, `DisplayNameFallback`, `DisplayNameOptions` type aliases (`display-name.ts` L4–L19)
|
||
|
||
The split adds the class wrapper, Zod-validated options, `INVALID_FORMATTER_OPTIONS` mapping, and OTel histogram + cache-hit counter so the package is consistent with the rest of the formatter family.
|
||
|
||
## What changes
|
||
|
||
### New package: `@suaveplan/i18n-formatter-display-name`
|
||
|
||
- ADDED: `packages/i18n/i18n-formatter-display-name/` package scaffold (`package.json`, `tsconfig.json`, `vite.config.ts`, `README.md`).
|
||
- ADDED: `src/formatter.ts` — `DisplayNameFormatter` class (NEW); `formatDisplayName` standalone (port of monolith L57–L64); `clearDisplayNameCache` (port of L67–L69). Per-(locale, type, style, fallback) cache key from monolith L27–L29.
|
||
- ADDED: `src/formatter.types.ts` — `DisplayNameType` (`"language" | "region" | "script" | "currency" | "calendar" | "dateTimeField"`), `DisplayNameStyle` (`"long" | "short" | "narrow"`), `DisplayNameFallback` (`"code" | "none"`), `DisplayNameFormatterOptions`, `DisplayNameFormatterCacheKey`. Sourced from monolith `display-name.ts` L4–L19 plus new types for the class surface.
|
||
- ADDED: `src/formatter.schemas.ts` — `DisplayNameFormatterOptionsSchema` Zod schema. NEW (the monolith does not currently Zod-validate options).
|
||
- ADDED: `src/helpers.ts` — `formatLanguageName(code, locale, style?)`, `formatRegionName`, `formatCurrencyName`, `formatScriptName`, `formatCalendarName` ergonomic shortcuts that pre-bind `formatDisplayName` with the corresponding `type`.
|
||
- ADDED: `src/observability.ts` — local OTel histogram `i18n.format.display_name.duration_ms` + counter `i18n.format.cache_hit{formatter="display_name"}`.
|
||
- ADDED: `src/formatter.test.ts` — port of monolith `display-name.test.ts` (93 lines) plus new tests covering `DisplayNameFormatter` class, Zod-validation, cache-hit behaviour.
|
||
- ADDED: `src/formatter.spec-conformance.test.ts` — every requirement scenario in this delta.
|
||
- ADDED: `src/helpers.test.ts` — covers all five ergonomic helpers.
|
||
- ADDED: `src/index.ts` — explicit named re-exports.
|
||
- ADDED: `src/formatter.md`, `src/helpers.md`, `src/observability.md` — sibling docs.
|
||
|
||
### Subpath exports
|
||
|
||
- `.` — root barrel
|
||
- `./formatter` — `DisplayNameFormatter` class + `formatDisplayName` + types + schema only
|
||
- `./helpers` — `formatLanguageName`, `formatRegionName`, `formatCurrencyName`, `formatScriptName`, `formatCalendarName` only
|
||
|
||
### Dependencies
|
||
|
||
- `@suaveplan/i18n-shared workspace:^` — `I18N_CODES`, `I18nError`
|
||
- `@suaveplan/types workspace:^`
|
||
- `@suaveplan/error workspace:^`
|
||
- `@opentelemetry/api catalog:observability`
|
||
- `tslib catalog:build`
|
||
- `zod catalog:forms`
|
||
- **No** dep on any other formatter package, on `@suaveplan/i18n` core, or on any backend package.
|
||
|
||
## Out of scope
|
||
|
||
- **Removing `formatting/display-name/` from `packages/i18n/i18n/src/`** — happens in Wave 6.
|
||
- **`formatToParts` for display-name output** — out of scope; `Intl.DisplayNames` does not expose it.
|
||
- **CLDR-based fallback strategies beyond `code` / `none`** — out of scope; matches `Intl.DisplayNames` fallback semantics.
|
||
|
||
## Source mapping
|
||
|
||
| Requirement | Source file | Source lines | Notes |
|
||
|---|---|---|---|
|
||
| `formatDisplayName` standalone | `packages/i18n/i18n/src/formatting/display-name/display-name.ts` | L57–L64 | Verbatim port |
|
||
| `clearDisplayNameCache` | `packages/i18n/i18n/src/formatting/display-name/display-name.ts` | L67–L69 | Verbatim port |
|
||
| Per-(locale, type, style, fallback) cache | `packages/i18n/i18n/src/formatting/display-name/display-name.ts` | L21, L23–L47 | Verbatim port |
|
||
| `UNSUPPORTED_LOCALE` mapping | `packages/i18n/i18n/src/formatting/display-name/display-name.ts` | L40–L46 | Verbatim port; throws `I18nError` from `@suaveplan/i18n-shared` |
|
||
| Type aliases (`DisplayNameType`, `DisplayNameStyle`, `DisplayNameFallback`, `DisplayNameOptions`) | `packages/i18n/i18n/src/formatting/display-name/display-name.ts` | L4–L19 | Verbatim port; rename `DisplayNameOptions` → `DisplayNameFormatterOptions` for symmetry |
|
||
|
||
### Requirements with no source line
|
||
|
||
| Requirement | Justification |
|
||
|---|---|
|
||
| `DisplayNameFormatter` class wrapper | New — symmetric with `NumberFormatter`, `RelativeTimeFormatter`, `ListFormatter`, `DateFormatter` |
|
||
| `DisplayNameFormatterOptionsSchema` Zod schema | New — current monolith does not validate options; canonical Wave-3 formatter shape mandates it |
|
||
| `INVALID_FORMATTER_OPTIONS` mapping | Bound to the new Zod schema |
|
||
| OTel histogram `i18n.format.display_name.duration_ms` + cache-hit counter | New — canonical Wave-3 formatter shape mandates these |
|
||
| Five ergonomic helpers (`formatLanguageName`, `formatRegionName`, `formatCurrencyName`, `formatScriptName`, `formatCalendarName`) | Symmetric with `formatPercent` / `formatCompact` for the number formatter; pre-binds the required `type` so consumers don't have to remember the discriminant string |
|
||
|
||
## Verification approach
|
||
|
||
- `packages/i18n/i18n-formatter-display-name/src/formatter.test.ts` — port of monolith `display-name.test.ts`; cover all six `type` values; long / short / narrow styles; `code` / `none` fallback; class wrapper twins helper output; Zod failure → `INVALID_FORMATTER_OPTIONS`; bogus locale → `UNSUPPORTED_LOCALE`
|
||
- `packages/i18n/i18n-formatter-display-name/src/formatter.spec-conformance.test.ts` — every requirement scenario runs as an executable test
|
||
- `packages/i18n/i18n-formatter-display-name/src/helpers.test.ts` — `formatLanguageName("fr", "en-GB")` → `"French"`; `formatRegionName("GB", "en-GB")` → `"United Kingdom"`; `formatCurrencyName("USD", "en-GB")` → `"US Dollar"`; `formatScriptName("Latn", "en-GB")` → `"Latin"`; `formatCalendarName("gregory", "en-GB")` returns the gregorian display name
|
||
- `packages/i18n/i18n-formatter-display-name/src/observability.test.ts` — histogram named `i18n.format.display_name.duration_ms`; counter `i18n.format.cache_hit{formatter="display_name"}` increments only on cache hit
|
||
- `packages/i18n/i18n-formatter-display-name/src/index.test.ts` — every documented public export resolves; subpaths resolve
|
||
|
||
## Impact
|
||
|
||
- New universal-tier package — joins the rest of the i18n family.
|
||
- Indirect consumer in Wave 6: `@suaveplan/i18n` slim core re-exports `DisplayNameFormatter` and `formatDisplayName` under the back-compat root barrel.
|
||
- `MANIFEST.md` and `CLAUDE.md` updated post-merge.
|