Companion to
docs/launch/locale-coverage.md. Defines the workflow, glossary ownership, language fallback, RTL handling, and text-expansion QA that govern customer-facing strings inlibs/oshun/i18n/and domain-owned catalogs.
Catalog ownership#
| Surface or domain | Source-of-truth catalog | Owner |
|---|---|---|
| Shared shell strings | libs/oshun/i18n/src/index.ts (CUSTOMER_MESSAGES) |
Platform UI + localization |
| Assistant disclosures | libs/oshun/i18n/src/index.ts |
T&S + assistant product |
| Privacy disclosures | libs/oshun/i18n/src/index.ts |
Privacy + legal |
| Tara content | libs/oshun/i18n/ shared keys + Tara seed/content records |
Tara editorial |
| Veritas content | libs/oshun/i18n/ shared keys + Veritas seed/content records |
Veritas editorial |
| Nyx event copy | libs/oshun/i18n/ shared keys + Nyx seed/content records |
Nyx editorial |
| Arete prompts | libs/oshun/i18n/ shared keys + Arete seed/content records |
Arete product |
| Nisaba metadata | libs/oshun/i18n/ shared keys + canonical passage metadata |
Nisaba editorial |
| Metis course frames | libs/oshun/i18n/ shared keys + Metis course seed records |
Metis product |
| Persona disclosures | persona records inside libs/oshun/persona-policy-lilith/ |
T&S persona ops |
| Operator (admin) strings | apps/oshun/admin/src/i18n/ |
Admin product |
Shared shell strings — anything that appears in the customer shell chrome or in
the assistant compliance/disclosure copy — live in the canonical catalog at
libs/oshun/i18n/. Domain-owned content records may carry source text, but
their customer-visible shared labels, disclosures, status copy, formatting
rules, fallback rules, and launch-locale metadata depend on this package and may
not redefine its keys.
Glossary#
A single glossary, owned by the localization lead, governs proper nouns, persona names, domain names (Tara, Veritas, Nyx, Arete, Nisaba, Metis), and trust-and-safety terminology that must read identically across surfaces.
- Glossary location:
libs/oshun/i18n/glossary/. Each glossary entry is JSON: source term, allowed translations per locale, do-not- translate flag, gender/animacy hints, disclosure context. - Glossary review: localization lead + T&S lead sign-off for terms that touch persona identity or refusal copy.
- Glossary lint: a glossary-coverage check runs against every PR that adds or modifies a translation: terms in the glossary must translate to the allowed values per locale; do-not-translate terms must remain in source language. Failures block merge.
Fallback chain#
The runtime fallback for a missing key is:
- Requested locale (e.g.,
es-US). - Language-only locale (e.g.,
es). en-US.
The chain is implemented in resolveFallbackChain and translate in
libs/oshun/i18n/src/index.ts. The same package also exposes the mapping from
legacy customer language preferences (en, es, etc.) to launch locales
(en-US, es-US, etc.) so web and mobile selectors cannot advertise languages
outside the V1 launch coverage list. A fallback hit emits an analytics event so
that gaps surface in the launch dashboards.
A fallback gap discovered on a critical surface (disclosure, privacy, crisis) is a sev2 — it does not block GA, but it blocks the locale from being marked launch-quality for that surface.
RTL handling#
ar and he are RTL launch locales. Every shell, domain, and admin surface
must:
- Mirror the layout: the primary visual flow runs right-to-left.
- Mirror icons that imply direction (back/forward, next/previous, chevrons, sliders).
- Preserve bidi safety: customer-controlled strings appear inside bidi-isolated runs.
- Number and digit policy: western numerals by default; Arabic-Indic digits
opt-in per content piece for
ar. - Punctuation: locale-correct quotation marks, dashes, ellipsis.
The cross-cutting RTL test suite lives at tests/locale/rtl-smoke.spec.ts and
runs against every locale-tagged view in Playwright.
Text-expansion QA#
Every shell layout is verified against the LOCALE_EXPANSION_BUDGET table in
the i18n library:
en-US: 1.0 baseline.es-US: 1.25.fr-FR: 1.3.de-DE: 1.4.ar: 1.2 (script width plus mirroring).he: 1.15.ja-JP: 0.7 (denser script).pt-BR: 1.3.
The customer preference aliases exposed in profile settings and the language
switcher are launch-scoped: en, es, fr, de, ar, he, ja, and pt.
Unsupported prelaunch aliases such as zh, ko, and hi are not surfaced to
customers until they receive a launch-locale coverage row and full catalog
coverage.
QA generates synthetic strings at each locale's budget against every catalog key, drops them into the shell, and verifies no truncation, no unintended wrap, no overflow into the next region. Failures are filed against the shell owner; layout fixes are required before the locale can be marked launch-quality.
Translation workflow#
- Author the source string in
en-USin the matching catalog. PRs must include a one-sentence translator note when the meaning is non-obvious (e.g., "disclosure copy — must read calmly, not alarming"). - Run the catalog-lint check locally; this verifies the key is declared in the type union and the placeholder convention is correct.
- Trigger the localization pipeline: the localization lead routes the new key to the contracted translators with context (surface, character budget, tone, glossary references).
- Review the returned translations: locale leads + glossary lint + visual review against the actual surface (Storybook or staging) at the expansion-budget length.
- Land the translations: PR merges only when every launch locale has a
translation (the
every customer message key has a translation in every launch localetest enforces this). - Run the runtime smoke: in staging, render the surface in every locale; verify no fallback hits, no truncation, no RTL break.
Audit and provenance#
- Every change to
CUSTOMER_MESSAGESlands with the i18n maintainer + matching domain maintainer review. - Trust-and-safety disclosure copy changes require T&S sign-off.
- Privacy disclosure copy changes require privacy + legal sign-off.
- The translation pipeline records translator identity (team or contractor), timestamp, and source-language hash so customer-facing disclosures have a clean provenance audit trail.
Adding a new launch locale#
Adding a locale is a launch-class change:
- Add the locale to
OSHUN_LAUNCH_LOCALES. - Add a row to
LOCALE_EXPANSION_BUDGET,LOCALE_CALENDAR, andRTL_LOCALESif applicable. - Translate every key in
CUSTOMER_MESSAGES. - Run the RTL smoke and the expansion-budget QA across every shell.
- Update
docs/launch/locale-coverage.mdto reflect the locale's coverage tier. - Run the locale-coverage gate at the next release-readiness review.
Until every catalog entry passes the test suite, the locale stays in the
staging-only tier and is not surfaced to customers.
Owner#
Localization lead owns this workflow. Platform UI lead, T&S lead, privacy lead, and legal counsel co-review changes that affect disclosure or trust-touching copy. Updates require sign-off from localization plus any affected co-owner.