# Internationalization (i18n) & RTL Reference

Internationalization is building a product that can be adapted for different languages and regions without engineering changes. Localization (l10n) is the actual adaptation for a specific locale.

This reference focuses on what's visible in the design — the things that break or look wrong when a product goes multilingual.

---

## Text Expansion

Different languages take up very different amounts of space than English. This is the most common i18n design failure.

### Typical Expansion Rates vs English

| Language | Expansion |
|---|---|
| German | +30–40% |
| Finnish | +30–40% |
| French | +15–25% |
| Spanish | +15–25% |
| Portuguese | +15–30% |
| Russian | +15–25% |
| Arabic | -20–25% (shorter but RTL) |
| Chinese/Japanese/Korean | -30–50% (denser characters) |

### How to Design for Text Expansion

**Buttons:**
- Don't use fixed-width buttons. Use `min-width` with horizontal padding, so the button grows with the text.
- Test with a German or Finnish string — if the button wraps or overflows, the design isn't ready.
- Avoid putting two buttons side by side in a tight space — one may grow and overlap the other.

**Navigation:**
- Horizontal nav bars are the most vulnerable — they clip or wrap when labels expand.
- Test nav items with longer strings. Consider icon-only nav on mobile, with labels on desktop.

**Labels & headings:**
- Truncation (`text-overflow: ellipsis`) is acceptable for secondary content, never for critical labels.
- Two-line truncation is safer than one-line for headings.

**Cards & table cells:**
- Fixed-height cards clip translated text. Use `min-height` not `height`.
- Table cells should allow text wrapping, not fixed heights.

### Testing for Expansion
The quickest test: temporarily replace all strings with a string 40% longer than the original and scan for breakage.

---

## RTL (Right-to-Left) Languages

RTL languages: **Arabic, Hebrew, Persian (Farsi), Urdu, Pashto, Sindhi**

In RTL, the entire reading direction reverses — and the UI layout should mirror accordingly.

### What Mirrors in RTL

| Element | LTR | RTL |
|---|---|---|
| Text alignment | Left | Right |
| Page layout direction | Left to right | Right to left |
| Navigation | Logo left, actions right | Logo right, actions left |
| Sidebar | Left | Right |
| Form fields | Left-aligned | Right-aligned |
| Progress bars | Fill left → right | Fill right → left |
| Breadcrumbs | Home > Section > Page | Page < Section < Home |
| List bullets/numbers | Left of text | Right of text |
| Tooltips | Open right | Open left |
| Scrollbar | Right side | Left side |
| Back button icon | ← | → |

### What Does NOT Mirror in RTL

| Element | Stays the Same |
|---|---|
| Clock direction | Always clockwise |
| Phone numbers | Always LTR |
| Prices / currencies | Depends on locale standard |
| Code snippets | Always LTR |
| Non-directional icons | Heart, star, trash, settings |
| Media controls | Play/pause buttons stay the same |
| Logos | Unless specifically designed with RTL version |
| Mathematical expressions | Always LTR |

### Implementing RTL in Code

**HTML level:**
```html
<html dir="rtl" lang="ar">
```
Or per section:
```html
<div dir="rtl">...</div>
```

**CSS — Use logical properties instead of directional ones:**

| ❌ Directional (breaks in RTL) | ✅ Logical (works in both) |
|---|---|
| `margin-left` | `margin-inline-start` |
| `margin-right` | `margin-inline-end` |
| `padding-left` | `padding-inline-start` |
| `padding-right` | `padding-inline-end` |
| `border-left` | `border-inline-start` |
| `left: 0` | `inset-inline-start: 0` |
| `text-align: left` | `text-align: start` |
| `text-align: right` | `text-align: end` |
| `float: left` | `float: inline-start` |

**CSS `transform` for directional icons:**
```css
/* Flip directional icons in RTL */
[dir="rtl"] .icon-arrow,
[dir="rtl"] .icon-chevron,
[dir="rtl"] .icon-back {
  transform: scaleX(-1);
}
```

### RTL-Specific Design Checklist
- [ ] `dir="rtl"` applied at html or section level
- [ ] CSS uses logical properties (`inline-start/end`) not directional (`left/right`)
- [ ] Directional icons (arrows, chevrons) flip in RTL
- [ ] Layout mirrors (sidebar, nav, cards)
- [ ] Progress bars and sliders fill from correct direction
- [ ] Form fields are right-aligned
- [ ] Text is right-aligned
- [ ] Scrollbar moves to left side (handled by browser with `dir="rtl"`)

---

## Date, Time & Number Formats

Never hardcode locale-specific formats. Use the browser's `Intl` API.

### Date Formats by Region

| Region | Format | Example |
|---|---|---|
| US | MM/DD/YYYY | 01/15/2025 |
| UK / Australia | DD/MM/YYYY | 15/01/2025 |
| ISO / Technical | YYYY-MM-DD | 2025-01-15 |
| Germany | DD.MM.YYYY | 15.01.2025 |
| Japan | YYYY年MM月DD日 | 2025年01月15日 |
| Arabic | Right-to-left, varies | Locale-dependent |

**Always use `Intl.DateTimeFormat`:**
```javascript
new Intl.DateTimeFormat('de-DE').format(new Date()) // 15.1.2025
new Intl.DateTimeFormat('en-US').format(new Date()) // 1/15/2025
new Intl.DateTimeFormat('ar-SA').format(new Date()) // Arabic numeral date
```

### Number Formats

| Locale | Thousands separator | Decimal separator | Example |
|---|---|---|---|
| en-US | , | . | 1,234,567.89 |
| de-DE | . | , | 1.234.567,89 |
| fr-FR | (thin space) | , | 1 234 567,89 |
| ar | (varies) | (varies) | Use Intl API |

**Always use `Intl.NumberFormat`:**
```javascript
new Intl.NumberFormat('de-DE').format(1234567.89) // 1.234.567,89
```

### Currency

- Symbol position varies: `$100` (US) vs `100 €` (Europe) vs `¥100` (Japan)
- Always use `Intl.NumberFormat` with `style: 'currency'`
- Never hardcode `$` prefix

```javascript
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1234)
// → 1.234,00 €
```

---

## Font Support for Non-Latin Scripts

Most web fonts only support Latin characters. If you use a custom font and the user's OS language is Arabic, Chinese, Korean, etc., the browser will fall back to a system font — creating a jarring inconsistency.

### Check Font Coverage

| Script | Common Web Font Support |
|---|---|
| Latin (English, French, German...) | ✅ Most web fonts |
| Cyrillic (Russian, Bulgarian...) | ✅ Many Google Fonts |
| Greek | ✅ Many Google Fonts |
| Arabic | ❌ Most Latin web fonts — needs dedicated Arabic font |
| Hebrew | ❌ Most Latin web fonts — needs dedicated Hebrew font |
| CJK (Chinese, Japanese, Korean) | ❌ Most Latin web fonts — system fonts usually |
| Devanagari (Hindi) | ❌ Most Latin web fonts — Noto fonts or dedicated |

### Recommended Universal Font: Noto

Google's **Noto** font family is designed to support all scripts without "tofu" (□ characters). It's a safe fallback for multilingual products.

### CSS Font Stack for Multilingual

```css
font-family: 'YourFont', 'Noto Sans', system-ui, sans-serif;
```

---

## Text in Images

**Never embed text inside images** if the product will be translated.

- Images with text can't be localized without recreating the image
- Screen readers can't read text in images
- Text in images doesn't scale with browser font settings

**Solution:** Use CSS text overlays on top of images. Keep the image as a background, text as a separate HTML element.

---

## Pluralization

Languages handle plurals very differently. English has 2 forms (1 item, 2 items). Arabic has 6 plural forms. Russian has 3.

**Never do this in code:**
```javascript
`${count} item${count === 1 ? '' : 's'}` // Only works for English
```

**Do this instead:**
Use an i18n library that handles plural rules (i18next, react-intl, FormatJS):
```javascript
t('items', { count }) // Library handles the plural form per locale
```

### Design implication: Leave room for plural variations
In designs, don't assume a short string stays short in all plural forms. "1 item" vs "1,234 items" is one thing — but in some languages, the word itself changes length significantly across plural forms.

---

## i18n Design Audit Checklist

- [ ] No hardcoded strings visible in the UI (all from translation files)
- [ ] Buttons use `min-width` not fixed width
- [ ] Fixed-height containers use `min-height` to accommodate expansion
- [ ] RTL layout tested with `dir="rtl"` (if targeting RTL languages)
- [ ] Directional icons flip in RTL
- [ ] Dates use `Intl.DateTimeFormat`
- [ ] Numbers use `Intl.NumberFormat`
- [ ] Currency uses `Intl.NumberFormat` with currency style
- [ ] No text embedded in images
- [ ] Pluralization handled by an i18n library, not string concatenation
- [ ] Font stack includes multilingual fallback (Noto or system-ui)
- [ ] Navigation tested with 40% longer strings
