Reference · UX pattern catalogueInternationalisation patterns

10 patterns in this category — each with its illustration, what it is, and the fields that matter when you build or review one. Part of the UX pattern catalogue; the raw, agent-readable version is ux-pattern-catalogue/i18n-patterns.json.

Text

Cart EN: 4 chars Einkaufswag... DE: 13 chars +30-40% expansion

Text Expansion Buffer i18n-001

Design UI to accommodate 30-40% longer text in German/Finnish translations. Fixed-width containers will truncate or break with translated strings.

Implementation

Use flexible layouts (flex, grid, min-width instead of fixed width). Test with pseudo-localization — pad every string with 35% extra characters. Avoid fixed heights on text containers. Set overflow-wrap: break-word as a safety net.

Pitfall

Fixed-width containers truncate or break with translated strings. "Shopping Cart" (13 chars) becomes "Einkaufswagen" (13 chars in German but wider) or "Ostoskori" in Finnish. Button text, nav labels, and table headers are the most common casualties.

EN 1 item 2 items AR 0 1 2 3-10 11-99 JA no plural distinction CLDR plural categories

Pluralization Rules i18n-002

Use CLDR plural categories, not simple if/else. Different languages have vastly different plural rules — English has 2 forms, Arabic has 6, Japanese has 0 distinct forms.

Implementation

Use ICU MessageFormat: `{count, plural, one {# item} other {# items}}`. Libraries like i18next, FormatJS, or Intl.PluralRules handle CLDR rules automatically. Never hand-roll plural logic.

Pitfall

Writing `count === 1 ? "item" : "items"` only works for English. Arabic has singular, dual, and 4 plural forms. Polish has different rules for 2-4 vs 5-21. Japanese uses no plural distinctions at all.

Names

FULL NAME Sukarno FIRST NAME LAST NAME

Single Full Name Field i18n-003

Use one "Full name" field instead of splitting into first/last name. Name structures vary wildly across cultures — mononymous names, patronymics, multi-part family names, and name order all break the first/last assumption.

Implementation

Single "Full name" input field. Add an optional "Display name" or "Preferred name" field for how the user wants to be addressed. Store the full string as-is — never try to programmatically split or reorder.

Pitfall

Splitting names fails for mononymous names (Sukarno), patronymic systems (Icelandic), multi-part family names (van der Berg), and cultures where family name comes first (Chinese, Japanese, Korean, Hungarian).

PHONE NUMBER +1 ▾ (415) 555-1234 STORED AS +14155551234 E.164 format

Phone Number Format i18n-004

Accept flexible phone input, store in E.164 format, and display in locale-appropriate format. International phone numbers have wildly different lengths, groupings, and prefix rules.

Implementation

Use `inputmode="tel"` for mobile keyboard. Provide a country code dropdown. Store all numbers in E.164 format (+14155551234). Use libphonenumber for validation and display formatting. Never enforce a single country's format.

Pitfall

Validating against US format (xxx-xxx-xxxx) rejects valid international numbers. UK numbers have variable length, Indian numbers are 10 digits, and some countries share country codes. Stripping the + prefix corrupts international numbers.

Addresses

US Street City ST ZIP DE Straße PLZ Ort country-specific layout

Dynamic Address Form i18n-005

Render address fields dynamically based on the selected country. Address formats vary dramatically — field names, order, required fields, and postal code formats all change by country.

Implementation

Start with a country selector. Load country-specific field order and labels from locale data (Google's Address Data Service or libaddressinput). Show/hide fields dynamically — not all countries need state/province, postal code, or apartment fields.

Pitfall

US address format (Street, City, State, ZIP) doesn't work for Germany (PLZ before city), Japan (largest to smallest: prefecture, city, ward, block), UK (no state equivalent), or countries without postal codes (Ireland until 2015).

Formatting

US 1,000.50 DE 1.000,50 FR 1 000,50 Intl.NumberFormat

Locale Number Format i18n-006

Use Intl.NumberFormat for all displayed numbers. Thousands separators and decimal marks vary by locale and mixing them up causes data entry errors or misreading.

Implementation

`new Intl.NumberFormat(locale).format(number)` for display. For inputs, parse with locale awareness — accept both comma and period as decimal separators based on locale. Never format numbers with string concatenation.

Pitfall

1,000.50 (US) vs 1.000,50 (DE) vs 1 000,50 (FR) vs 1'000.50 (CH). Using the wrong separator can turn 1.234 into "one point two three four" or "one thousand two hundred thirty-four" depending on locale.

US (MM/DD) 03/05 EU (DD.MM) 05.03 5 Mar 2026 unambiguous format

Locale Date Format i18n-007

Use Intl.DateTimeFormat for all displayed dates. The ambiguity of numeric date formats causes real-world confusion — 03/05/2026 means March 5 in the US but May 3 in most of Europe.

Implementation

`new Intl.DateTimeFormat(locale, { year: "numeric", month: "short", day: "numeric" }).format(date)`. Use named months ("5 Mar 2026") to eliminate ambiguity in mixed-locale contexts. Store dates as ISO 8601 internally.

Pitfall

03/05/2026 means March 5 (US), May 3 (UK/EU), or could be read as 2026-05-03 in ISO contexts. MM/DD vs DD/MM ambiguity causes booking errors, incorrect deadlines, and data corruption when parsing user input.

US $100.00 DE 100,00 € JP ¥100 symbol position + decimals vary

Currency Formatting i18n-008

Use Intl.NumberFormat with style: "currency" for all monetary values. Symbol position, spacing, decimal rules, and symbol itself all vary by locale.

Implementation

`new Intl.NumberFormat(locale, { style: "currency", currency: currencyCode }).format(amount)`. Always pass the ISO 4217 currency code (USD, EUR, JPY) separately from the locale. Never hardcode currency symbols or decimal places.

Pitfall

$100.00 (US) vs 100,00 € (DE) vs 100 € (FR) vs ¥100 (JP, no decimals) vs Fr. 100.00 (CH). The currency symbol can appear before or after the number, with or without a space, and decimal precision varies.

Rtl

LTR Submit → RTL ← إرسال

RTL Layout Support i18n-009

Use CSS logical properties and dir="rtl" for Arabic, Hebrew, and Farsi layouts. Physical CSS properties create broken layouts when content direction is reversed.

Implementation

Replace all physical properties: margin-inline-start instead of margin-left, padding-inline-end instead of padding-right, inset-inline-start instead of left. Set dir="auto" on user-input fields. Use logical values (start/end) for text-align and float.

Pitfall

Physical CSS properties (margin-left, padding-right, text-align: left, float: left) break in RTL layouts. Icons with directional meaning (arrows, progress indicators) need to be mirrored. Mixing LTR and RTL content in one page creates visual chaos.

Testing

Logo EN English Deutsch Français 日本語

Locale Switcher i18n-010

Provide a visible language/locale switcher that persists the user's choice across navigation. The switcher should be easy to find and respect browser language preferences for the initial selection.

Implementation

Place a dropdown in the header or footer using language names in their own script (Deutsch, not German). Persist choice to localStorage + URL parameter (?lang=de). Respect Accept-Language header for initial selection. Use ISO 639-1 language codes.

Pitfall

Hiding the locale switcher in deep settings makes it unfindable. Losing the language preference on navigation forces users to re-select every page. Using flags for languages is inaccurate — Spanish is spoken in 20+ countries, English in 60+.