RFC 0001: Locale negotiation
Summary
v1 freezes every numeric and date format to en-US so that two conforming implementations cannot disagree about how 1234.5 or 2026-04-15 renders. This RFC proposes a minimal, additive locale-negotiation surface that lets implementations declare which locales they support and lets a .uw.md file declare the locale it was authored in. Parsing and rendering remain deterministic per locale; cross-locale conversion is explicitly out of scope.
Motivation
SupportedLocaleinprotocol.ts:99is'en-US'literally — the only allowed value.NumberFormatRules(protocol.ts:101) andDateFormatRules(protocol.ts:110) carry alocalefield that is therefore meaningless.- Format spec §III currency/percent/ratio rules are hard-coded to US conventions (
$,1,000.00, ISO dates rendered asMM/DD/YYYYin chat). - Adopters in the EU, UK, and Asia have to either fork the renderer or store values in a non-canonical way to get their locale's display rules.
The cost of staying frozen is an entire class of adopters self-excluded from conformance. The cost of getting locale wrong is non-determinism — the same file rendered by two conforming tools showing different numbers. Locale negotiation is the design that lets adopters opt in without breaking determinism.
Proposed change
Format spec
Add a locale frontmatter key (default en-US) and one new normative section in Part III:
# Frontmatter
locale: en-US # default; one of the SupportedLocale valuesSpec language (RFC 2119):
A conforming implementation MUST support
en-US. An implementation MAY support additional locales. When parsing a file whoselocalethe implementation does not support, the parser MUST emit aBLOCKING-LOCALE-001validation error and MUST NOT fall back to a different locale.
Numeric, percent, ratio, and date rendering rules are factored into a per-locale table in spec §III. en-US keeps its current rules verbatim; new locales land via additive amendments to the table.
Protocol spec
SupportedLocale becomes:
export type SupportedLocale =
| 'en-US'
| 'en-GB'
| 'de-DE'
| 'fr-FR'
| 'ja-JP'
| 'zh-CN';ImplementationManifest.capabilities gains a supported_locales: SupportedLocale[] field (defaulting to ['en-US'] for backwards compatibility). A reader that encounters a file whose locale is outside its declared supported_locales errors per the format-spec rule above.
NumberFormatRules and DateFormatRules are kept; their locale field becomes meaningful. The BUILTIN_FORMAT_RULES registry gains one entry per listed locale.
Library
formatCurrency,formatPercent,formatRatio,formatDateaccept an optionallocale: SupportedLocaleargument; default remains'en-US'.parseUWFilereadsfrontmatter.localeand threads it through to the validator and renderer.- New
BUILTIN_REMEDIATIONS['BLOCKING-LOCALE-001']entry covering the unsupported-locale error.
The change is additive — files without a locale key continue to be treated as en-US.
Compatibility analysis
- Existing
.uw.mdfiles — no breakage. Files withoutlocaledefault toen-US. - Tier-1 readers — must declare
supported_localesin their manifest. A reader that only supportsen-UScontinues to work for every existing file; it errors on a file authored inde-DE, which is the desired behavior. - Tier-2 editors — must preserve the
localekey on round-trip (already covered by general round-trip preservation). - Tier-3 calc hosts — calc engine is locale-agnostic (numbers are stored canonically); only display changes. No behavior change.
- Tier-4 agent hosts — must run agents with the file's declared locale (so generated narrative text matches).
- Modules —
ModuleManifest.requires_locales?: SupportedLocale[](additive, optional). A module that hard-codes US-specific thresholds declaresrequires_locales: ['en-US'].
No deprecation path required — additive change.
Conformance impact
New fixtures in conformance/tier-1-reader/fixtures/:
de-DE-rendering/— same Parkview deal frontmatter set tode-DE, expected chat/summary outputs use German formatting (1.234,56 €,15.04.2026).unsupported-locale-error/— file withlocale: xx-XX, expectedBLOCKING-LOCALE-001error from a reader that doesn't declare it.
Existing tier-1/2/3 fixtures continue to pass without change (they implicitly carry locale: en-US).
Reference implementation
- Files affected:
packages/uwmd-core/src/protocol.ts— expandSupportedLocale, addsupported_localestoImplementationManifest.packages/uwmd-core/src/format.ts— per-locale formatter dispatch.packages/uwmd-core/src/format-rules.ts(new) —BUILTIN_FORMAT_RULESregistry.packages/uwmd-core/src/validator.ts— emitBLOCKING-LOCALE-001on unsupported locale.packages/uwmd-core/src/renderer.ts— thread locale through render.spec/UW_FORMAT_SPEC_v1.md— Part III locale table.spec/UW_PROTOCOL_v1.md— §IV manifest changes.
- API surface:
formatCurrency(value, { locale }),formatPercent(value, { locale }), etc. (additive optional argument). New export:BUILTIN_FORMAT_RULES. - Test plan: unit tests for each locale's formatter; integration test that a file with
locale: de-DErenders the German fixture's expected output.
Alternatives considered
Per-section locale override. Allow
localeinside_metaso a single file could mix locales. Rejected — opens the door to internally-inconsistent files (one section inde-DE, another inen-US) and complicates renderer state. The frontmatter-level decision is simpler and covers the actual use case (a deal originated in one country).Runtime locale switching at render time. Let the reader pass a
localeto render, regardless of file content. Rejected — breaks determinism. A file should render the same way for any conforming reader. The file declares its locale; readers either support it or error.Defer to ICU's full locale tag space.
SupportedLocale = stringwith an ICU-style validator. Rejected for v2-of-protocol — too large a surface to certify against. The closed enum can grow over time via small additive RFCs.
Unresolved questions
- Which locales ship in the first wave? Proposal:
en-US,en-GB,de-DE,fr-FR,ja-JP,zh-CN. Pulls from where adopters have indicated demand. - Date display format — should the chat-render's date format also be per-locale (
15.04.2026forde-DE), or always ISO (2026-04-15)? Recommend per-locale for chat; always ISO inside JSON content. - Currency symbol disambiguation — does
en-USalways render$asUSD? What about a deal in Mexico authored inen-USwith peso amounts? Likely needs acurrency_codefield on monetary values to fully resolve.
Prior art
- CommonMark stays locale-agnostic by punting display entirely. Not an option here — UW Markdown's value proposition is determinism including display.
- JSON Schema
format: date— defines the wire format but punts display. We do the same for storage; display is the new surface. - ICU locale data — the canonical source for per-locale formatting rules. Our
BUILTIN_FORMAT_RULESis a small curated subset chosen to ship verbatim, not a runtime ICU dependency. - OpenAPI 3.1 i18n — does not attempt locale negotiation; each spec is monolingual. UW Markdown's frontmatter-locale approach is closer to what HTTP
Accept-Languagedoes, except evaluated at parse time rather than request time.