---ci--- project: atelier phase: 6 milestone: v0.3 status: complete requirements: covered: [ATELIER-60..91] partial: [] ---/ci---
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 byformatting.md(strings the formatter resolves) andrtl-bidi.md(thedirthe 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
.csvof strings) is a violation: it is unsupported by standard tooling, has no plural grammar, and cannot compose withformatting.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_countnot"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
(
enfromen-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-branchifencodes 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.pottemplate 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 revertof 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.