International | Localization | 2026-08-16

Multilingual Mobile App Planning

Multi-language support is an architecture decision rather than a translation purchase, and the order you do the work in decides what it costs.

Multilingual Mobile App Planning

Adding a second language to an app written in English is not a translation project. It is a refactor of a codebase that already shipped. That is why localization budgets blow up: the translation invoice is the small line item and the engineering time around it is the large one.

The real bill for retrofitting

A 40-screen app with authentication, a catalog, checkout, and settings typically carries 700 to 1,400 user-visible strings. Pulling them out later is mechanical but not fast. Budget two to four minutes per string once you count locating it, choosing a key, and threading a BuildContext into places that never had one: form validators, service classes, exception messages. That is 40 to 80 engineering hours before a single word gets translated.

Then layout breaks. Short German labels routinely double in length against English, and full sentences grow 15 to 30 percent, so every fixed-width button and every single-line Row with two children becomes a ticket.

The same groundwork at the start costs six to ten hours: a localization config, one ARB file, and the habit of never typing a user-facing string into a widget. The gap between those numbers is the argument.

Key architecture: name for meaning, never for the English words

Keys like home_title_2 and settings_button_new rot immediately. Use a namespace of feature, component, and role, such as cart.checkoutButton.label or auth.emailField.errorInvalid. A translator scanning the list can then tell a heading from a button without opening the app.

Two rules save the most pain. Never key a string by its English content, because the English will change and the key will lie. And do not share one key across two screens just because the wording matches: "Save" as a button verb and "Save" as a savings heading are Speichern and Ersparnis in German. Duplicating a key is cheap; finding the collision in a customer screenshot is not.

In Flutter that means ARB files driven by flutter gen-l10n, with the English template as the source of truth and a metadata block on every entry carrying a description and typed placeholders. Do not concatenate. Building "You have " + count + " new orders" in Dart produces a sentence no translator can reorder, and word order is what changes between languages.

Plurals and gender break naive string tables

English has two plural forms. That is unusual. The CLDR rules behind every serious i18n library define up to six per language:

A counter string therefore has to be authored as an ICU plural message, not a base string with an appended "s", so each locale supplies only the categories its grammar needs.

Gender matters wherever you interpolate a person into a sentence. "Priya liked your post" inflects the verb in Spanish, Hebrew, and Russian. Either collect a grammatical gender preference, which most consumer apps would rather not do, or choose constructions that sidestep it and put that in the copy guidelines.

Numbers, dates, and currency get formatted by intl against the active locale, never assembled by hand. Make one call explicitly: currency follows the transaction, while grouping and decimal separators follow the user. A German-speaking customer paying in dollars should see 1.234,56 US$, not $1,234.56.

Give translators context or pay for it twice

Translators working from a bare list of strings guess, and guesses cost a second round. Send the description on every key, a maximum character count wherever the UI is constrained, a screenshot reference for anything ambiguous, and a do-not-translate glossary of your product and plan names.

Wire it as a pipeline, not an email thread. Lokalise, Crowdin, and Phrase all read and write ARB or XLIFF; the workable setup keeps English ARB in git as the source, pushes it to the platform on merge, and takes completed locales back as a pull request. Manual copy-paste survives about two releases before the files drift.

On money: professional human translation runs roughly $0.10 to $0.20 per word. A 1,000-string app averaging four words per string is about 4,000 words, so $400 to $800 per language for the first pass and $50 to $150 for incremental updates each release. Machine translation with human review costs less and is fine for descriptive UI. Raw machine output on payment terms, cancellation policies, or anything with legal weight is where you inherit real exposure.

Before booking any of it, run a pseudo-locale in CI: expand every string by about 40 percent and wrap it in brackets. Anything still showing plain unbracketed English was never externalized, and anything that overflows will overflow in German too.

User-generated content is a different problem

UI strings are finite and yours. User content is unbounded and someone else's:

  1. Store a BCP-47 language tag on every user-generated record at write time, from the composer's locale or a detection call. Inferring it at read time gives inconsistent answers.
  2. Sorting needs locale-aware collation. Firestore orders strings by UTF-8 byte value, so "Äpfel" lands after "Zebra". If alphabetical order matters, store a normalized sort key beside the display value.
  3. Translate on demand and cache, keyed by source hash plus target locale. Cloud Translation runs around $20 per million characters, trivial for a support ticket and ruinous if you translate every message for every reader on every load.
  4. Moderation degrades sharply outside English. Keyword lists need a per-language version, and classifiers need spot-checking per market.

Anything sent from a server needs the locale stored on the server

This one catches teams after launch. A push notification's title and body are rendered by the operating system from the payload, so the Cloud Function building that payload must already know the user's language. Device locale is not available to your backend at send time. The reliable pattern is a preferred-language field on the user document, updated whenever the app locale changes. Localizing on device with data-only messages works on Android and is unreliable on iOS once the app is terminated.

The same rule covers Stripe, where the Checkout session takes a locale parameter and receipt language follows the customer record, plus every transactional email template and generated invoice. Twelve templates going multilingual becomes twelve times the number of locales, a content project someone has to own.

A rollout order that keeps scope honest

Sequence the work so each stage proves something before the next is funded. Externalize every string and add the pseudo-locale check to CI first, while the app is small. Add one second language sharing the Latin script, Spanish or French, to prove the pipeline end to end. Only then consider a right-to-left locale, because mirroring is a design pass rather than a config flag: EdgeInsetsDirectional throughout, correct Directionality handling, and a decision about which icons must not mirror, like playback controls and clocks.

Localize the store listing alongside the app. App Store Connect and Google Play both accept per-locale titles, descriptions, and screenshots, and a listing in the local language moves installs there far more than a translated settings screen will.

CrateShip Studios
White-label Flutter apps, delivered in 30-60 days, from $2,500 - full source code included.
Get started