Files
atelier/domains/i18n/locale-resources.md
T
Jon Chery 9ebc9c8868 docs(milestone): complete v0.3 — GitOps+Operators/AI-ML/i18n/Compliance
---ci---
project: atelier
phase: 6
milestone: v0.3
status: complete
requirements:
  covered: [ATELIER-60..91]
  partial: []
---/ci---
2026-08-05 03:45:38 +00:00

7.2 KiB

Locale Resources — Derived Rules

Derives from domains/i18n/first-principles.md. Covers P1 (Source Language is a Locale), P2 (Locale Identifiers Standardized), P3 (Resources External, Not Inline), P4 (Plural/Gender Parameterized), and P10 (Translation Reversible and Versioned). Referenced by formatting.md (strings the formatter resolves) and rtl-bidi.md (the dir the resource layer carries).

Resources are the Boundary (P3 Resources are External, Not Inline)

  • User-facing strings live in locale resource files, addressed by key. Code references a key; the resource layer resolves the key to the active locale. The source language is itself a locale (en-US), not a fallback baked into code.
  • String concatenation in code ("Welcome, " + name + "!") is the cardinal violation: it bakes in source-language word order and breaks for every locale with different grammar. Replace every concatenation with a parameterized message: t("welcome", { name }).
  • The resource is the single place a string lives. Editing a string in code instead of the resource is a locality violation (C4): the string and its locale consequences now live apart.

Resource File Formats

Format Shape When Notes
.po / .pot gettext; msgid → msgstr, plural headers Server-side, GNU ecosystem, PHP/Python/C Mature tooling (xgettext, msgmerge); supports plural categories via header
JSON (flat or namespaced) { "key": "value" } per locale JS/web, i18next, FormatJS Simple, machine-readable, but no native plural support — wrap with ICU MessageFormat
Fluent .ftl Mozilla FTL; asymmetric, resolver-driven Browser-grade l10n, asymmetric translations One message can resolve differently per locale without code changes; supports attributes, selectors
ICU Resource Bundle ICU binary/text resources ICU-native, JVM, C++ Tightest integration with ICU formatting/CLDR; steeper tooling
  • None is advocated over the others. The choice is ecosystem fit, not correctness. All four satisfy P3/P4 when used as the boundary.
  • A custom format (a hand-rolled .csv of strings) is a violation: it is unsupported by standard tooling, has no plural grammar, and cannot compose with formatting.md's ICU layer.

Key Naming and Namespaces (P2 Locale Identifiers Standardized)

  • Locale identifiers are BCP 47 tags (en-US, ar-EG, zh-Hans-CN). No ad-hoc codes. The resource file is named for its locale: en-US.json, ar-EG.po, ftl/ar-EG/main.ftl.
  • Message keys are stable, semantic, and structured — not prose. checkout.cart.item_count not "You have 3 items in your cart". A key that is the source string (t("You have items")) breaks the moment the source copy is edited; the key must outlive the copy.
  • Namespaces segment by surface (checkout.*, errors.*, onboarding.*) so that a locale can be loaded incrementally and so that key collisions across surfaces are impossible. A flat namespace with thousands of keys is a C2 (Clarity) violation waiting to happen.

Fallback Chains

  • The fallback chain is explicit: requested locale → language-only (en from en-GB) → default locale → key itself (last resort). The default locale is declared once, not re-derived in every call site.
  • A missing key in the requested locale falling back silently to the source locale is a P3 violation: the user is silently shown the developer's locale, which is not the locale they asked for. Missing keys must be observable (see testing-i18n.md).
  • Fallback is a property of the resource layer, not of individual components. A component that re-implements fallback is duplicating a contract (C6 Composability violation).

Plural and Gender in Resources (P4 Plural/Gender Parameterized)

  • Plural variants live in the resource, selected by the formatter, parameterized by the count. The code passes the count; the resource carries the variants; the formatter picks the right one per the locale's CLDR plural rules.

    // JSON + ICU MessageFormat (FormatJS / i18next)
    {
      "cart.item_count": "{count, plural, one {# item} other {# items}}"
    }
    // ar-EG.json — six plural categories per CLDR
    {
      "cart.item_count": "{count, plural, zero {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}}"
    }
    
  • if (n == 1) branching in code is a violation regardless of language. Arabic has six plural categories; Russian has three; English has two. A two-branch if encodes exactly one locale's rules and is wrong for every other.

Extraction Tooling (P1, P3)

  • Strings are extracted mechanically (e.g. xgettext, i18next- parser, FormatJS babel plugin), not by hand-tagging. Mechanical extraction produces a .pot template that translators work from; the template is regenerated on every build.
  • A string that cannot be extracted (built at runtime from fragments) is a P3 violation: it is invisible to the pipeline. If the extractor cannot see it, neither can the translator.
  • The extracted template is versioned (P10): the diff between templates is the change in translatable surface. A template that is not committed is a contract that is not reviewable.

Versioning and Rollback (P10 Translation Reversible and Versioned)

  • Resource files are committed to git alongside code. A bad translation is a git revert of the resource, not a hot-patch over the translator's work. Every locale resource has history, provenance (which translator / which service produced which commit), and a rollback path.
  • A locale resource that is generated by a translation service and committed without review is a P10 violation: the resource is versioned but the provenance is opaque. Review the diff the same way you review a code diff.

What Violates Locale-Resource Discipline

Violation Principle
t("You have " + n + " items") concatenation P3 Resources are External, Not Inline
A custom .csv string store instead of a standard format P3 Resources are External, Not Inline
Locale file named english.json not en-US.json P2 Locale Identifiers are Standardized
if (n == 1) { t("item") } else { t("items") } in code P4 Plural and Gender are Parameterized
A key equal to the source string (t("Welcome back")) P2 / P10 — keys must outlive copy
Silent fallback to the source locale with no signal P3 Resources are External, Not Inline
A runtime-built string the extractor cannot see P3 Resources are External, Not Inline
Resource files committed by a bot with no human review P10 Translation is Reversible and Versioned

Relationship to Other Domains

  • domains/uiux/copywriting.md — copy lives in resources; UI microcopy is the source content the resource layer carries.
  • domains/api/error-responses.md — API error messages are locale- resource keys resolved at the boundary, not inline strings.
  • domains/data/schema-design.md — locale identifiers and resource shapes are a data contract; treat them as schema.
  • domains/i18n/formatting.md — the formatter resolves the parameterized message this layer produces.