Internationalization (i18n) is one of those frontend areas where the surface looks simple (“just translate the strings”) but the depth surprises engineers. Pluralization, RTL layouts, locale-aware formatting, dynamic content — interviews probe whether you understand the gotchas.
The basics
i18n consists of:
- Translation: strings in different languages
- Locale-aware formatting: dates, numbers, currency, lists
- Layout direction: LTR (English, French) vs RTL (Arabic, Hebrew)
- Pluralization rules: cat / cats vs Polish 5 forms
- Time zones
i18n libraries
- react-intl (FormatJS): mature, ICU MessageFormat support, broad ecosystem
- react-i18next / i18next: simpler API, large community
- Lingui: compile-time optimization, smaller bundles
- Next-intl: built for Next.js App Router
For most projects: react-i18next or Lingui in 2026.
ICU MessageFormat
The standard for parameterized + pluralized translations:
{count, plural,
=0 {No messages}
one {You have one message}
other {You have # messages}
}
Handles plural forms across languages without bespoke logic.
Locale-aware formatting
Use Intl APIs (built into browsers):
Intl.NumberFormatfor numbers and currencyIntl.DateTimeFormatfor datesIntl.ListFormatfor lists (“apple, banana, and cherry”)Intl.RelativeTimeFormatfor “3 days ago” style
Don’t hardcode formats. ${date.toLocaleDateString()} is locale-aware automatically.
RTL support
For Arabic, Hebrew, Persian, Urdu, the layout flows right-to-left:
- Set
dir="rtl"on the html element - Use logical CSS properties (margin-inline-start, padding-block-end) instead of physical
- Mirror icons that have direction (arrows, back buttons)
- Numbers can stay LTR even in RTL text
Bonus: many bidi (mixed direction) edge cases. Use the Unicode Bidi Algorithm via the browser; do not try to override.
Translation workflow
Common tooling:
- Source format: ICU MessageFormat strings in source code
- Extraction: automated tooling pulls strings into a translation file (JSON, YAML, PO)
- TMS: Translation Management System like Lokalise, Phrase, or Crowdin
- Re-import: translated strings imported back into the codebase
- Validation: CI catches missing translations, mismatched placeholders
String IDs vs natural-language keys
Two patterns:
- String IDs: “checkout.button.confirm” — explicit, but disconnected from actual text
- Natural language as key: “Confirm checkout” — reads naturally, but renaming sources of strings means re-translating
Modern tools (Lingui, Next-intl) often use natural language as the key with hashing for stability.
Variable interpolation
Strings often have parameters:
"Welcome, {name}!"
Translate the template, not the result. The translator decides the position of the placeholder; word order varies between languages.
Pluralization gotchas
Languages have wildly different plural systems:
- English: 2 forms (1 vs everything else)
- French: 2 forms but with different boundaries (0 and 1 are singular)
- Arabic: 6 forms based on count
- Polish: 4 forms with complex rules
ICU MessageFormat handles this. Don’t hand-roll plural logic with conditionals.
Loading translations
- Inline (bundle all languages): simple, but bundle bloats
- Code-split per locale: load only the user’s language
- Server-side rendering with the right locale baked in (Next.js, Remix do this)
Time zones
Always store dates in UTC + IANA time zone identifier (“America/New_York”). Render in the user’s local time zone unless the event is explicitly in another zone (e.g., a flight from JFK).
Use date-fns-tz, Luxon, or date-fns with built-in time zone support.
Common mistakes
- Concatenating translated fragments (“You have ” + count + ” messages”) — breaks word order
- Hardcoded date formats (not locale-aware)
- Physical CSS properties (margin-left, padding-right) — break in RTL
- Translating the wrong parts (button text translated, status messages forgotten)
- No pseudo-localization in development to catch hardcoded strings
Pseudo-localization
A debugging mode where strings are converted to look “translated” but are still readable: “Account [Áççôûñţ]”. Catches hardcoded strings before they ship.
Frequently Asked Questions
How important is i18n for a US-only app?
Less critical, but still useful. Date and number formatting respect user locale, accessibility scales internationally, and “growing into i18n later” is harder than starting with it.
Should I use Google Translate to ship quickly?
For production, no. Mistakes are common, idioms break, and quality affects brand. Use professional translation for shipped copy.
How do I test RTL?
Set dir="rtl" in DevTools. Spot-check icons, layout, and any custom CSS. Add to E2E tests if RTL is a supported locale.