Why We Build i18n-First: Lessons from 12 Countries
Retrofitting internationalisation into an existing codebase is expensive and error-prone. After shipping products across twelve countries, we've learned that building i18n into the architecture from day one pays dividends every sprint.
Featured Image
Every product team that has retrofitted internationalisation into a mature codebase has the same story. The initial estimate is optimistic. The actual work turns out to be deeply entangled with application logic. Edge cases in date formatting, pluralisation, and bidirectional text surface only after the text extraction is supposedly complete. What was described as a two-sprint feature takes six — and the codebase is messier at the end than it was at the start.
At wecopars, we have shipped products into twelve countries across three writing directions — left-to-right, right-to-left Arabic and Persian, and the complex typographic requirements of CJK scripts. We started learning the i18n-first lesson the hard way on our earlier projects and have refined our approach into a set of architectural defaults that we now apply from the very first sprint of any project that has even a remote chance of being localised.
The True Cost of Retrofit i18n
The problem with retrofitting i18n is not just the mechanical work of extracting strings into translation files. It is that hardcoded strings are often entangled with application logic in ways that only become apparent when you try to extract them. Pluralisation logic that lives in JSX string templates. Date and number formatting that assumes a single locale. Component layouts that assume left-to-right text flow at a structural level.
Each of these entanglements requires not just extraction but refactoring. And refactoring in a mature codebase always carries risk — tests that were written against the original hardcoded behaviour, edge cases that the original developer knew about but are not documented, and subtle interactions between components that only manifest under specific locale conditions.
The engineering cost is compounded by the product cost: features that were built without i18n in mind often need to be redesigned rather than just translated. A form layout that works for 20-character English field labels may break with 40-character German equivalents. A navigation structure that works for five short English words may not accommodate seven-word Japanese equivalents without a visual regression.
The i18n-First Architecture We Use
Our i18n-first architecture has three pillars: a routing layer that handles locale detection and URL structure from day one, a translation system with a clearly defined dictionary structure that every component talks to, and a set of design system constraints that ensure layouts are locale-flexible by default.
For the routing layer, we use next-intl with localePrefix: 'never' — locale is stored in a cookie and detected at the middleware level, which keeps URLs clean and makes locale switching transparent to the URL structure. For the translation system, we organise dictionaries by namespace (one namespace per major feature or page), which keeps files manageable and makes it easy to hand specific namespaces to localisation vendors.
For the design system, the key constraint is that all text containers must be sized by content rather than by fixed pixel dimensions. Text that is hardcoded to a specific width will break in some locale. Text that flows within a flexbox or grid container that can accommodate varying lengths will handle virtually any locale gracefully.
RTL Support Without Compromising Code Quality
Right-to-left support is where many i18n implementations go wrong. The naive approach — adding CSS overrides for RTL in a separate stylesheet — produces code that is difficult to maintain and almost impossible to test comprehensively. A change to a component's layout logic requires updating two stylesheets, and the RTL stylesheet tends to accumulate technical debt faster than the base stylesheet.
The better approach, which we have standardised on, is to use CSS logical properties throughout the codebase rather than physical directional properties. Instead of margin-left and padding-right, you write margin-inline-start and padding-inline-end. The browser handles the direction flip automatically based on the document's dir attribute. The result is a single set of styles that works correctly in both LTR and RTL contexts without any overrides.
Tailwind CSS v3 and later support logical properties through utility classes like ms-4 (margin-inline-start) and pe-6 (padding-inline-end). Adopting these classes consistently from the start of a project means that RTL support is largely automatic — the main remaining concern is icon directionality and components that have inherently directional semantics.
