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

6.4 KiB

Tasks — i18n number and relative-time formatters

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.

Audit Note (2026-05-02)

Minimal progress — DEFERRED, do not archive.

  • packages/i18n/i18n/src/formatting/ only contains currency and date. No number/ or relative-time/ directories.
  • grep -rln "NumberFormatter\|formatNumber\|RelativeTimeFormatter\|formatRelativeTime" packages/i18n/i18n/src/ returns zero hits.
  • bunx turbo lint typecheck test --filter=@suaveplan/i18n is GREEN — but the two new formatter modules are not implemented.
  • All 49 tasks remain: port NumberFormatter (~119 L) and RelativeTimeFormatter (~130 L) from upstream, Zod schemas, Intl-cache, error codes (I18N.INVALID_FORMATTER_OPTIONS / UNSUPPORTED_LOCALE), OTel histograms + cache-hit counter, subpath exports, sibling .mds, README links.

1. Spec authoring

  • 1.1 Read ../suaveplan/enterprise/i18n.md end-to-end
  • 1.2 Read openspec/specs/i18n/spec.md (canonical capability spec)
  • 1.3 Read openspec/AGENTS.md Sections 13 and 14
  • 1.4 Draft proposal.md from openspec/templates/proposal.md
  • 1.5 Fill ## Source mapping table — cite upstream number.ts / relative-time.ts line ranges
  • 1.6 Draft specs/i18n/spec.md from openspec/templates/spec-delta.md
  • 1.7 Every requirement carries a **Verified by:** line naming concrete it(...) titles
  • 1.8 openspec change validate i18n-formatters exits with no errors

2. Implementation — NumberFormatter

  • 2.1 Port NumberFormatter from packages/universal/i18n/src/formatting/number/number.ts (L1-L119) into packages/i18n/i18n/src/formatting/number/number.ts
  • 2.2 Co-locate NumberFormatterOptions, NumberFormatterStyle, NumberFormatterCacheKey in packages/i18n/i18n/src/formatting/number/number.types.ts
  • 2.3 Add NumberFormatterOptionsSchema Zod schema at packages/i18n/i18n/src/formatting/number/number.schema.ts
  • 2.4 Implement (locale, options-hash) cache for Intl.NumberFormat; expose clearCache() for tests
  • 2.5 Throw I18nError with I18N.INVALID_FORMATTER_OPTIONS on Zod failure; I18N.UNSUPPORTED_LOCALE on RangeError from Intl.NumberFormat constructor
  • 2.6 Export formatNumber(value, locale, options?) standalone helper

3. Implementation — RelativeTimeFormatter

  • 3.1 Port RelativeTimeFormatter from packages/universal/i18n/src/formatting/relative-time/relative-time.ts (L1-L130) into packages/i18n/i18n/src/formatting/relative-time/relative-time.ts
  • 3.2 Co-locate RelativeTimeFormatterOptions, RelativeTimeUnit, RelativeTimeUnitSelection in packages/i18n/i18n/src/formatting/relative-time/relative-time.types.ts
  • 3.3 Add RelativeTimeFormatterOptionsSchema Zod schema
  • 3.4 Port unit threshold table (seconds → minutes → hours → days → weeks → months → years) verbatim from L40-L96
  • 3.5 Implement (locale, options-hash) cache for Intl.RelativeTimeFormat
  • 3.6 Export formatRelativeTime(deltaMs, locale, options?) standalone helper

4. Error codes and observability

  • 4.1 Add I18N.INVALID_FORMATTER_OPTIONS, I18N.UNSUPPORTED_LOCALE to packages/i18n/i18n/src/errors/error-codes.ts via defineCodes
  • 4.2 Emit OTel histograms i18n.format.number.duration_ms and i18n.format.relative_time.duration_ms from production paths
  • 4.3 Emit OTel counter i18n.format.cache_hit (tagged by formatter type) on cache reuse

5. Wiring

  • 5.1 Update packages/i18n/i18n/src/formatting/index.ts to re-export NumberFormatter, formatNumber, RelativeTimeFormatter, formatRelativeTime and their types
  • 5.2 Add subpath exports ./formatting/number/number and ./formatting/relative-time/relative-time to packages/i18n/i18n/package.json exports
  • 5.3 Update root README.md ## Modules section to link the two new co-located .mds

6. Tests

  • 6.1 packages/i18n/i18n/src/formatting/number/number.test.ts — decimal, percent, currency styles round-trip; fraction-digit clamping; useGrouping toggle; notation modes (standard/compact/scientific/engineering)
  • 6.2 packages/i18n/i18n/src/formatting/number/number.spec-conformance.test.ts — Zod rejects unknown style → INVALID_FORMATTER_OPTIONS; bogus locale → UNSUPPORTED_LOCALE; cache hit increments OTel counter
  • 6.3 packages/i18n/i18n/src/formatting/relative-time/relative-time.test.ts — auto-unit selection across boundaries (60s → 1m, 60m → 1h, etc.); negative deltas; style and numeric overrides
  • 6.4 packages/i18n/i18n/src/formatting/relative-time/relative-time.spec-conformance.test.ts — Zod rejects unknown style; histogram records duration_ms; expectTypeOf<RelativeTimeUnit> matches the literal union
  • 6.5 Every it(...) carries { timeout: 5000 }

7. Documentation

  • 7.1 Sibling .md for number.ts (≥200 words, basic + advanced examples, full API reference)
  • 7.2 Sibling .md for relative-time.ts (≥200 words, basic + advanced examples)
  • 7.3 README links both new .mds under ## Modules

8. Pre-archive verification (the 5-gate triple)

  • 8.1 bun run check — Biome 0/0/0 across the monorepo
  • 8.2 bunx turbo lint typecheck test --filter=@suaveplan/i18n — green
  • 8.3 bunx turbo test:coverage --filter=@suaveplan/i18n — 100/100/100/100
  • 8.4 bun scripts/openspec-conformance.ts @suaveplan/i18n — exit 0, no error findings
  • 8.5 bunx turbo build --filter=@suaveplan/i18n — green

9. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 9.1 Generics threaded; expectTypeOf assertions on NumberFormatterStyle and RelativeTimeUnit literal unions
  • 9.2 Zod schemas wired at every public boundary (NumberFormatterOptionsSchema, RelativeTimeFormatterOptionsSchema)
  • 9.3 Both new error codes thrown from at least one production path
  • 9.4 Strict-mode tested: bogus locale fallback vs throw paths
  • 9.5 OTel histograms / counters emitted from production paths and asserted with a real in-memory MeterProvider
  • 9.6 Lifecycle dedup: cache reuse verified; clearCache() empties the map
  • 9.7 No retroactive tick — every box reflects work done in this change

10. Archive

  • 10.1 Fold spec delta into openspec/specs/i18n/spec.md
  • 10.2 Move openspec/changes/i18n-formatters/ → openspec/archive/<yyyy-mm-dd>-i18n-formatters/
  • 10.3 Refresh MANIFEST.md if package set or deps changed