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

7.7 KiB
Raw Permalink Blame History

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