How to Build a Multi-Language App the Right Way
· 6 min read · Mona Technologies
Most "multi-language" apps aren't multilingual — they're an English app with a translation file bolted on afterward. It shows: numbers formatted the American way in German invoices, buttons that overflow their containers in French, right-to-left languages that never got tested. The fix isn't a better translation vendor. It's making four structural decisions before a single screen gets built, not after.
Decide the language switch is routing, not a dropdown
The most common mistake is treating language as a piece of UI state — a dropdown that swaps strings on the same page. That breaks search engines (they need a distinct, crawlable URL per language to index each version), breaks browser back buttons, and breaks link-sharing, because a link to the Spanish page just opens whatever language the last visitor left the site in.
The right model is that language is part of the URL: yourapp.com/fr/pricing, yourapp.com/de/pricing, or subdomains like fr.yourapp.com. Next.js's own internationalization guide frames it exactly this way — locale is handled by prefixing the route or using separate domains, not by client-side string swapping. If your developers describe the language switch as "just swap the text," that's a sign the architecture is already wrong.
Never hardcode a string in a template — ever
Every piece of user-facing text — button labels, error messages, empty states, confirmation emails, push notification copy — needs to live in a translation file from day one, even if you only ship in English at launch. Retrofitting this later means an engineer has to hunt through every component, string by string, which on a real app is weeks of unglamorous work that gets deprioritized indefinitely. It's dramatically cheaper to enforce this as a build rule from the first commit than to clean it up after 200 screens exist.
- All UI copy lives in structured translation files (JSON/YAML), keyed by a stable identifier — never by the English text itself, which changes.
- Pluralization is handled by rules, not string concatenation — "1 item" vs "5 items" is a different sentence structure in Russian or Arabic than in English.
- Dates, currency, and numbers are formatted per-locale using the platform's built-in formatting, not hand-written string templates.
- The html lang attribute is set correctly per page — the W3C's internationalization guidance flags this as a baseline requirement, since screen readers rely on it to choose the right pronunciation rules.
Design for text that's 30-40% longer, and for text that reads right to left
German and Finnish compound words routinely blow past the pixel width you designed for in English. A button that says "Save" in English might need to say "Speichern" in German or "Enregistrer les modifications" in French — the same UI element, meaningfully wider text. If your design system uses fixed-width buttons and truncates or wraps unpredictably, every non-English release ships visibly broken.
Arabic, Hebrew, Farsi, and Urdu run right-to-left, which isn't just mirrored text — it's mirrored layout: navigation flips sides, icons that imply direction (like a "forward" arrow) need to flip, and form fields need their alignment reversed. This is far easier to build in from the start using CSS logical properties, which describe "start" and "end" instead of "left" and "right", than to retrofit into a codebase full of hardcoded left/right positioning.
Translation is the cheap part; workflow is the expensive part
Buying good translations is a solved problem — professional translators or a well-briefed AI-assisted pipeline can produce accurate copy for most business apps. What actually derails multi-language launches is process: nobody owns keeping translations in sync as the product changes, so three months after launch the French version is quietly running several releases behind the English one, with buttons showing raw translation keys like BUTTON_SUBMIT_CONFIRM instead of real words.
- Assign one owner, even part-time, per language who is notified automatically when new strings are added.
- Block deploys that ship new UI strings without a placeholder translation, so nothing goes live as a raw key.
- Review translated screens in context — a string that's correct in isolation often reads wrong once you see it inside the actual layout.
- Re-review after major redesigns; a translation done for the old layout can be technically accurate and still be wrong for the new one.
Don't translate everything — localize what matters
Full localization — adapting currency, legal terms, payment methods, imagery, and tone to a specific market — is expensive and only worth doing for markets you're seriously committing to. For everywhere else, straightforward translation with correct formatting is enough. Before adding a language, decide which tier it gets: is this a market you're investing in, where local payment methods and region-specific content matter, or is it a language you're supporting so existing users aren't locked out of the product they already use? Treating every language as a full localization project multiplies cost for markets that don't need it; treating a serious target market as a quick translation job under-serves the customers you're trying to win.
The short version
Multi-language support is an architecture decision, not a feature you add later — the cost of getting the URL structure, string handling, and layout flexibility right at the start is a fraction of the cost of retrofitting them after the app already has real users in multiple languages.
