Internationalization for Web Apps: Routing, Content, and RTL Support

A client's product initially launched in English and Spanish, and when they added Arabic support eight months later to enter a new market, we learned how much RTL support needs to be a day-one architectural decision rather than a feature bolted on afterward. CSS written with left and right properties — margin-left, text-align: left — doesn't flip automatically, and we found dozens of components across the codebase with hardcoded directional values that looked visually broken in Arabic despite the translations themselves being correct.
The fix going forward was switching to CSS logical properties — margin-inline-start instead of margin-left, padding-inline-end instead of padding-right — which respect the document's direction automatically based on the dir attribute. Retrofitting this across an existing codebase took real time; for new projects since then, we start with logical properties by default even for English-only builds, so RTL support later doesn't require touching every component.
Routing for locales we handled with a locale prefix in the URL path — /en/dashboard, /es/dashboard, /ar/dashboard — using next-intl's App Router integration, rather than a query parameter or cookie-only approach, because a URL-based locale is what search engines can properly index per-language and what lets users bookmark or share a link in their language directly.
Content translation itself split into two categories that needed different handling: static UI strings, which we manage through a structured JSON catalog per locale reviewed by professional translators, and dynamic user-generated or database-sourced content, which needed a per-record locale field and fallback logic for records not yet translated into a given language. Conflating these two into one translation system, which is what the original build had done, made the dynamic content workflow far messier than it needed to be.
Date, number, and currency formatting we moved entirely onto the Intl API rather than manual string formatting, since locale-specific formatting rules — decimal separators, date order, currency symbol placement — are genuinely difficult to get right by hand and Intl handles them correctly out of the box across every locale we've needed so far.

