Files
2026-05-27 23:29:53 +01:00

97 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `@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.