OpenWOP openwop.dev

Status: Stable.

Normative home: i18n.

Why this exists

A host renders some text for humans: interrupt prompts, error messages, extension UI strings. This document states how a client asks for a locale and how a host answers. Machine-readable identifiers stay ASCII, dates and times stay ISO 8601, and number, currency and text-direction formatting are out of scope.

Language tags

Every locale identifier — Accept-Language, Content-Language and every locale field — is a BCP 47 tag.

  • Tags compare case-insensitively (RFC 5646 §2.1.1, RFC 4647 §2).
  • A host SHOULD advertise and emit tags in the case RFC 5646 §2.1.1 recommends: lowercase language, titlecase script, uppercase region (zh-Hant-TW, es-419).

Accept-Language

A client MAY send Accept-Language (RFC 9110 §12.5.4) on any request. A host MAY honor it to localize human-facing text in the response.

A host:

  • MUST parse the header without failing the request. A malformed value MUST NOT cause 400; the host uses its default locale and proceeds.
  • MUST NOT reject a request because it does not support the requested locale.
  • MUST honor q-values: the highest-q locale it supports wins, and ties go to request order.
  • MUST NOT infer a locale from the request body to override the header, which is authoritative.
  • SHOULD set Content-Language to the canonical tag of the locale used when it localized the response. When it localized nothing, it SHOULD omit Content-Language or set it to its default locale.
  • MAY cache localized text. When localization is expensive, caching by (localeTag, sourceText) is RECOMMENDED.
  • MAY return default-locale text at once and emit node.completed when the localized version is ready. This is discouraged for an interrupt prompt, which a human needs localized now.

Fallback

When a host cannot localize to the requested locale, it:

  1. walks the list in q-value order and uses the first locale it supports;
  2. otherwise tries the language family: asked for ja-JP and holding only ja, it uses ja and sets Content-Language: ja;
  3. otherwise uses its default locale, which it SHOULD advertise as i18n.defaultLocale;
  4. sets Content-Language to the locale it actually used, never another.

Error envelopes

A host MAY localize an error message, and MAY then add details.locale.

  • The error code MUST be the same registered identifier in every locale (errors.md); it is never translated.
  • details keys are schema field names and are not localized.
  • A human-facing details value MAY be localized, and SHOULD carry a locale sibling on the same object.

The i18n record

A host that negotiates locale SHOULD advertise i18n. A host advertising it honors Accept-Language on every protected route; a host that omits it serves one locale.

  • defaultLocale — the locale returned when no Accept-Language entry matches supportedLocales; "en" when omitted.
  • supportedLocales — the locales the host can return for human-facing text. It MUST contain defaultLocale, and its order carries no meaning.
  • A host that translates by machine SHOULD list only the locales it has validated end to end.

Replay and fork

Locale is chosen at request time. Content-Language is request-scoped and not recorded in the event log, so a replay re-projects the recorded text and does not re-render it. A fork localizes by its own request's Accept-Language, not the parent run's.

Clients

A client that wants localized content MUST check discovery for i18n before sending Accept-Language. A host that does not advertise it ignores the header and returns its default locale.

Sources: RFC 0206.