---ci--- project: atelier phase: 6 milestone: v0.3 status: complete requirements: covered: [ATELIER-60..91] partial: [] ---/ci---
10 KiB
Internationalization (i18n) — First Principles
Grounded in Unicode ICU + CLDR, W3C i18n WG, BCP 47 / RFC 5646, ICU MessageFormat / FormatJS / i18next / Mozilla Fluent, the JavaScript
IntlAPI, and WCAG 2.1 AA. The developer's language is one locale among many, not the neutral form.
1. The Principles
P1. Source Language is a Locale, Not the Default
The developer's own language is one locale among many — it is not the
"neutral" or "unlocalized" form of the product. Strings are extracted
from day one, addressed by key, and routed through a locale resource
layer even when only one locale is populated. Treating the source
language as the default produces hidden concatenations, hardcoded
grammar assumptions, and a translation debt that compounds until the
first second locale arrives — at which point the fix is a rewrite, not
a patch. The source locale is en-US (or whatever the team writes in);
it is not null. This is the i18n angle on domains/uiux/copywriting.md:
copy lives in resources, not in code.
P2. Locale Identifiers are Standardized
Use BCP 47 language tags (en-US, ar-EG, zh-Hans-CN, pt-BR).
No ad-hoc locale codes, no two-letter-only hacks, no invented keys.
The tag carries language, script (when needed), and region (when
needed); it is the contract between the resource layer, the
formatting layer, and the runtime. A locale identifier that is not
BCP 47 is a key that cannot be resolved by any standard tool, which
is a correctness violation. Cross domains/data/schema-design.md:
locale identifiers are a data shape with a defined vocabulary.
P3. Resources are External, Not Inline
User-facing strings live in locale resource files (.po, JSON,
Fluent .ftl, ICU Resource Bundle), never concatenated inline in
code. Inline strings are invisible to the translation pipeline,
unversionable as a unit, and untestable for completeness. String
concatenation in code ("Welcome, " + name + "!") is the cardinal
violation: it bakes in source-language grammar and breaks for every
locale with different word order. Resources are the boundary; code
addresses strings by key, the resource layer resolves the key to the
locale. This is the i18n angle on C4 Locality: strings and their
locale-specific consequences live together in the resource, not
scattered across code.
P4. Plural and Gender are Parameterized
Plural forms, gender, and select are expressed with ICU MessageFormat
(or an equivalent parameterized formatter), never with if (n == 1)
branching in code. Plural rules are locale-specific — English has
one/other, Arabic has six categories, Russian has three — and a
hand-rolled branch encodes exactly one locale's rules while pretending
to be universal. The formatter is the contract; the resource carries
the variants; the code passes the count and lets the formatter choose.
A if (n == 1) plural is a C1 (Correctness) violation masquerading as
a shortcut.
P5. Formatting is Locale-Aware
Dates, times, numbers, currencies, units, and relative time are
formatted via ICU / CLDR / the JavaScript Intl API — never
hand-rolled. A hand-rolled date formatter encodes one locale's
conventions and silently produces wrong output for every other locale
(mm/dd/yyyy vs dd/mm/yyyy is the canonical failure). CLDR is the
source of truth for locale data; Intl is the runtime that exposes
it. Formatting correctness is observable: a misformatted date is a
wrong answer in the user's locale, even if it is "right" in the
developer's. Cross domains/api/error-responses.md for localized
error messages at API boundaries.
P6. Text Direction is a Layout Primitive
RTL and bidi are first-class layout concerns, not a CSS afterthought.
Logical CSS properties (margin-inline-start, padding-block-end,
inset-inline-end) over physical (margin-left, padding-top). The
dir attribute is set on the document and on subtrees; the bidi
algorithm (UAX #9) handles inline reordering. A layout that assumes
LTR is a layout that is wrong for ar, he, fa, ur, and any
RTL-mixed context. Text direction is not a skin — it is a structural
property of the layout, and fixing it late is a rewrite. This is the
i18n angle on domains/uiux/accessibility.md: RTL support is an
accessibility concern for non-Latin-script users.
P7. Layout Accommodates Expansion
Translated text expands and contracts — German is ~30% longer than
English, Japanese often shorter, RTL mirroring shifts every visual
anchor. Layouts are flexible: no fixed pixel widths for text, no
truncation without an ellipsis-and-title strategy, no
white-space: nowrap on translatable strings. A layout that breaks
on a 30% expansion is a layout that is wrong for most of the world's
locales. Designing for the worst-case expansion up front is cheaper
than reworking every screen when the first long-form locale ships.
P8. Pseudo-Locales Test Early
Test with pseudo-locales (accented, lengthened, RTL-mirrored, brack-
enclosed) before real translations arrive. A pseudo-locale run
surfaces hardcoded strings, layout overflow, broken concatenation,
and LTR assumptions while the fix is still cheap — the translator
hasn't been paid yet, and the string freeze hasn't happened. Finding
these bugs after real translation is a C5 (Reversibility) violation:
the cost of undoing is now a re-translation. Cross
domains/testing/fixtures.md and domains/testing/pyramid.md for
where pseudo-locales sit in the testing pyramid.
P9. Images and Icons are Cultural
Icons, colors, gestures, and imagery are locale-sensitive. A mailbox icon means "email" in the US and "mail" in Japan — but a green checkmark means "correct" in the West and "incorrect" in some East Asian contexts. A thumbs-up is positive in much of the world and an insult in parts of the Middle East. Avoid locale-bound symbols as universal; parameterize imagery per locale where the symbol is not globally neutral. Icons are not a universal language; they are a locale with a picture. This is a C2 (Clarity) concern: an icon whose meaning changes by locale is unclear to the reader it was not drawn for.
P10. Translation is Reversible and Versioned
Resource files are versioned alongside code; a bad translation is a rollback, not a hot-patch. Every locale resource has a history (what shipped when), a provenance (which translator / which service), and a rollback path. A translation that breaks the UI is reverted to the prior resource version, the same way a code regression is reverted to the prior commit. Translations without version history are anecdote, not artifact — you cannot tell what changed, when, or why. This is the i18n angle on C5 Reversibility applied to the resource layer.
2. Core Principle Trace
Each i18n P-rule derives from one or more core C-rules (C1–C8). The matrix extension lands in P4 of the v0.3 plan; the traces below are authoritative.
| P-rule | Core | Why |
|---|---|---|
| P1 Source Language is a Locale, Not the Default | C2, C1 | Clarity of locale intent; correctness of treating source as one-of-many |
| P2 Locale Identifiers are Standardized | C2, C6 | Clarity of a standard vocabulary; composability with standard tools |
| P3 Resources are External, Not Inline | C4, C6 | Locality of strings and their locale consequences; composability of the resource layer |
| P4 Plural and Gender are Parameterized | C1, C6 | Correctness of locale-specific plural rules; composability of the formatter contract |
| P5 Formatting is Locale-Aware | C1, C7 | Correctness of formatted output; observability of format correctness |
| P6 Text Direction is a Layout Primitive | C1, C4 | Correctness of layout for RTL; locality of direction with the text it governs |
| P7 Layout Accommodates Expansion | C8, C3 | Economy of rework; simplicity of flexible layouts over per-locale overrides |
| P8 Pseudo-Locales Test Early | C7, C5 | Observability of i18n defects early; reversibility of fixing before translation |
| P9 Images and Icons are Cultural | C1, C2 | Correctness of locale-appropriate symbols; clarity of meaning across locales |
| P10 Translation is Reversible and Versioned | C5 | Reversibility of the resource layer |
3. What Violates These Principles
| Violation | Principle Breached |
|---|---|
| A user-facing string hardcoded in source | P3 Resources are External, Not Inline |
"Welcome, " + name + "!" string concatenation |
P3 Resources are External, Not Inline |
if (n == 1) { return "item"; } else { return "items"; } |
P4 Plural and Gender are Parameterized |
A locale code like en_us or english instead of en-US |
P2 Locale Identifiers are Standardized |
A hand-rolled date formatter (getMonth() + 1 + "/" + getDay()) |
P5 Formatting is Locale-Aware |
margin-left: 10px on a translatable layout |
P6 Text Direction is a Layout Primitive |
| A fixed-width text container that overflows on German | P7 Layout Accommodates Expansion |
| First i18n test runs against real translations, not pseudo-locales | P8 Pseudo-Locales Test Early |
| A thumbs-up icon shipped as universally positive | P9 Images and Icons are Cultural |
| Resource files with no git history or no rollback path | P10 Translation is Reversible and Versioned |
| The source language treated as the "unlocalized" default | P1 Source Language is a Locale, Not the Default |
4. Relationship to Other Domains
i18n is the locale-awareness layer that domains/uiux/ consumes and
that domains/api/ surfaces at boundaries. It borrows the testing
discipline of domains/testing/ and the data-shape discipline of
domains/data/. Cross-links are one-directional (per D-026 extended):
domains/uiux/copywriting.md← P1, P3 (strings live in resources)domains/uiux/accessibility.md← P6 (RTL is an a11y concern for non-Latin users)domains/uiux/components.md← P6, P7 (layout primitives that survive direction and expansion)domains/api/error-responses.md← P5 (localized error messages)domains/data/schema-design.md← P2, P3 (locale data shapes)domains/testing/fixtures.md← P8 (pseudo-locale fixtures)domains/testing/pyramid.md← P8 (pseudo-locale tier mapping)domains/testing/first-principles.md← P8 (testing discipline for locale)