Internationalization Engineering
Build applications that work across languages, locales, and cultures. Covers i18n architecture, Unicode handling, RTL support, date/number formatting, translation management, and the patterns that prevent localization from becoming a retrofitting nightmare.
Internationalization (i18n) is the engineering foundation that lets an application adapt to different languages and regions without code changes. Localization (l10n) is applying that foundation to a specific locale. Retrofitting i18n into an existing application is one of the most expensive engineering tasks — doing it from the start costs nearly nothing.
Architecture
┌─────────────────────────────────────────────┐
│ Application │
│ │
│ ┌──────────────┐ ┌────────────────────┐ │
│ │ i18n Layer │ │ Locale Resources │ │
│ │ │ │ │ │
│ │ Translation │ │ en.json (English) │ │
│ │ Date format │ │ ja.json (日本語) │ │
│ │ Number format│ │ ar.json (العربية) │ │
│ │ Currency │ │ de.json (Deutsch) │ │
│ │ Pluralization│ │ zh.json (中文) │ │
│ │ RTL support │ │ │ │
│ └──────────────┘ └────────────────────┘ │
│ │
│ User's locale (Accept-Language, user pref) │
│ determines which resources are loaded │
└─────────────────────────────────────────────┘
Implementation
// React: Using react-intl (FormatJS)
import { IntlProvider, FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';
// Translation files
const messages = {
en: {
'greeting': 'Hello, {name}!',
'items.count': '{count, plural, =0 {No items} one {# item} other {# items}}',
'price.label': 'Price: {price}',
'date.created': 'Created on {date}',
},
ja: {
'greeting': 'こんにちは、{name}さん!',
'items.count': '{count, plural, other {#個のアイテム}}',
'price.label': '価格: {price}',
'date.created': '作成日: {date}',
},
ar: {
'greeting': 'مرحباً، {name}!',
'items.count': '{count, plural, =0 {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} other {# عنصر}}',
'price.label': 'السعر: {price}',
'date.created': 'تاريخ الإنشاء: {date}',
},
};
function App({ locale }) {
return (
<IntlProvider messages={messages[locale]} locale={locale}>
<h1>
<FormattedMessage id="greeting" values={{ name: 'Alice' }} />
</h1>
<p>
<FormattedMessage id="items.count" values={{ count: 5 }} />
</p>
<p>
<FormattedNumber value={1299.99} style="currency" currency="USD" />
{/* en: $1,299.99 | de: 1.299,99 $ | ja: $1,299.99 */}
</p>
<p>
<FormattedDate value={new Date()} year="numeric" month="long" day="numeric" />
{/* en: March 5, 2026 | ja: 2026年3月5日 | de: 5. März 2026 */}
</p>
</IntlProvider>
);
}
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| String concatenation for messages | Cannot reorder words in other languages | ICU MessageFormat with placeholders |
| Hardcoded date/number formats | MM/DD vs DD/MM confusion | Intl.DateTimeFormat, Intl.NumberFormat |
| ASCII-only text handling | ñ, ü, 日本語, العربية break | UTF-8 everywhere, Unicode-aware operations |
| No RTL support | Arabic/Hebrew layouts broken | CSS logical properties, dir=“auto” |
| Translate in code, not resource files | Translators need developer access | Externalize all strings to resource files |
Internationalization is an architecture decision, not a translation task. Build the i18n infrastructure early, externalize every user-facing string, use ICU MessageFormat for pluralization, and let the Intl API handle date/number formatting.