Engineering guide
Internationalization Architecture and Workflow
Separate internationalization engineering from localization delivery with clear ownership, Unicode, RTL, externalized messages, locale APIs, QA, and fallbacks.
Reviewed 10 September 2026 · Editorial owner: localization.guide
Internationalization and localization are separate responsibilities
Internationalization (i18n)
Engineering work that makes adaptation possible without rewriting core features: message extraction, Unicode-safe storage, flexible layouts, locale negotiation, formatting services, bidi support, and testable fallbacks.
Localization (l10n)
Adaptation for a specific audience: translation, terminology, imagery, units, legal content, market conventions, locale data, linguistic review, and regional release decisions.
Formatting is not translation, and validation is not formatting. Keep those concerns independently testable so a copy update cannot alter price logic and a locale change cannot mutate stored business data.
Make the i18n-to-l10n handoff explicit
Internationalization creates the technical capacity to adapt a product; localization uses that capacity for a specific language, region, or market. The handoff is a release system with named owners, not a one-time translation request at the end of development.
| Deliverable | Primary owner | Required collaborator | Release evidence |
|---|---|---|---|
| Unicode-safe data path and locale APIs | Engineering | Architecture and QA | Round-trip, formatter, fallback, and migration tests |
| Complete externalized source messages | Product and engineering | Localization lead | Variable validation, context notes, and pseudo-locale pass |
| Translation and market adaptation | Localization lead | In-market subject expert | Approved terminology, copy, legal and cultural review |
| RTL, expansion, fonts, and interaction | Design and frontend | Accessibility and localization QA | Visual regression, keyboard, screen-reader, and device evidence |
| Numbers, dates, addresses, and phones | Engineering and data owners | In-market QA | Versioned locale data plus country fixtures |
| Market release decision | Product owner | Legal, support, operations, and localization | Signed release gate, monitoring, rollback and feedback route |
Recommended application boundaries
| Layer | Owns | Must not own |
|---|---|---|
| Domain | Canonical amounts, dates, identifiers, rules | Translated labels or display strings |
| Messages | Keys, plurals, selects, context, translator notes | Number/date calculations |
| Locale services | Numbers, dates, lists, display names, collation | Business validation decisions |
| Regional validation | Address/phone metadata and user input rules | UI translation |
| Presentation | Layout, direction, accessible labels, components | Locale inference from hidden globals |
Externalize complete messages
A translator needs the complete thought and its grammatical context. Avoid concatenating fragments such as "You have " + count + " items". Use message syntax that preserves variables, plural branches, and select branches, and attach notes explaining UI context and variable meaning.
// messages/en.json
{
"cart.summary": "{count, plural, =0 {Your cart is empty} one {# item} other {# items}}",
"invite.sent": "Invitation sent to {recipientName}"
}
// Keep stable semantic keys. Do not use the English sentence as the key.
// Translator note: recipientName is a person or team display name.- Validate that every locale has the same variables and required plural/select branches.
- Escape or sanitize variables according to their output context.
- Do not split links, emphasis, or placeholders in ways that force English word order.
Use Unicode throughout—and normalize intentionally
Store, transport, search, render, and log Unicode without lossy conversions. Visually identical text can have different code-point sequences, so choose a documented normalization policy for comparisons and identifiers. Normalization is not transliteration, case folding, or accent removal; apply those separately and only when the product contract requires them.
const displayName = input.normalize("NFC");
// Search normalization is a product decision, not a display mutation.
const searchKey = displayName.normalize("NFKC").toLocaleLowerCase(locale);
// Preserve the original user-visible value when normalization would surprise them.Font coverage is part of the architecture. Define fallback stacks and test shaped scripts, combining marks, variation selectors, emoji sequences, and missing-glyph behavior with production fonts.
Design for RTL and mixed-direction content
- Set the document or component base direction with the HTML `dir` attribute.
- Use CSS logical properties such as `margin-inline-start`, `padding-inline`, and `border-inline-end`.
- Use
dir="auto"or bidi isolation for user-generated names, phone numbers, codes, and other mixed-direction strings. - Mirror arrows that express direction; do not mirror universal media controls, numbers, or brand marks without a design reason.
- Test focus order and reading order independently from visual mirroring.
export function UserLabel({ value }: { value: string }) {
return <bdi dir="auto">{value}</bdi>;
}
// Prefer logical layout utilities/properties.
// padding-inline-start: 1rem;
// inset-inline-end: 0;A small Next.js reference structure
app/
[lang]/
layout.tsx # validates locale; sets lang and dir
page.tsx
i18n/
config.ts # supported locales and default fallback
negotiate.ts # URL/cookie/header negotiation policy
messages.ts # allowlisted dynamic imports
formatters.ts # Intl number/date/list helpers
messages/
en.json
de.json
ar.json
lib/
address/ # versioned regional metadata
phone/ # parsing/validation boundary
tests/
messages.test.ts
locale-fixtures.test.ts
visual/export const supportedLocales = ["en", "de", "ar"] as const;
export type AppLocale = (typeof supportedLocales)[number];
export async function loadMessages(locale: AppLocale) {
switch (locale) {
case "de": return (await import("../messages/de.json")).default;
case "ar": return (await import("../messages/ar.json")).default;
default: return (await import("../messages/en.json")).default;
}
}Make fallback behavior explicit
Unsupported locale
Redirect or negotiate to a documented default, preserve the requested route where possible, and never load arbitrary import paths from an unchecked URL segment.
Missing message
Log a structured error with locale and key, fall back to the source locale in non-regulated UI, and fail the build for critical or newly introduced gaps.
Incomplete regional data
Use flexible input and generic display rather than applying a neighboring country’s validation rules.
Formatter/runtime failure
Use a safe, labeled fallback while preserving canonical data; do not invent a localized value by string replacement.
Localization delivery as a staged system
- Source design: write complete messages, define variables, allow expansion, and design bidi-safe components.
- Extraction: generate catalogs and reject duplicate, obsolete, or structurally invalid messages.
- Translation: provide screenshots, descriptions, terminology, constraints, and plural/select context.
- Review: perform linguistic and subject-matter review on complete workflows, not isolated strings.
- Integration: validate placeholders, compile messages, and package only allowlisted locale resources.
- Locale QA: run pseudo, automated locale fixtures, visual tests, accessibility checks, and regional devices.
- Release and monitoring: gate by locale, track fallback/missing-key errors, collect local feedback, and schedule data updates.
Primary and official sources
- Localization versus Internationalization — W3C Internationalization
- Internationalization guide for the App Router — Next.js
- Structural markup and right-to-left text — W3C Internationalization
- Unicode Normalization Forms — Unicode Consortium
- Internationalization Tag Set 2.0 — W3C
- Unicode Locale Data Markup Language — Unicode Consortium