International | Onboarding | 2026-08-16

Localizing Onboarding for Global Users

Translation does not cause locale bugs, it reveals them, and onboarding is the worst place to find out because nobody files a support ticket about an account they never managed to create.

Localizing Onboarding for Global Users

A sign-up flow that works in English can break in three ways at once the day it ships to Germany. The primary button label overflows, the address form demands a state that does not exist, and the SMS code never arrives because the carrier filtered an alphanumeric sender ID. None of those are translation problems. They are layout, data model and delivery problems that translation merely exposes.

Onboarding is the costliest place to discover them, because every user has to finish it before doing anything else, and nobody files a support ticket about an account they never managed to create.

Budget for string expansion before hiring a translator

LanguageTypical growth vs EnglishWhat breaks first
German20 to 35%Buttons, tab labels
French15 to 25%Field labels
Spanish15 to 25%Section headers
Russian15 to 30%Nav items
Japaneseshorter but tallerLine height, tight caps
Arabic20 to 25%Layout direction

The working rule: any label under 12 characters in English should be laid out as though it will be 20. In practice that means no fixed-width buttons, Row children that can wrap, and never centring a two-line title in a box sized for one. Run pseudo-localization first, replacing every string with a padded version wrapped in brackets, and you will find the truncation in an afternoon rather than in a store review.

Stop concatenating while you are at it. Building 'You have ' plus count plus ' new matches' cannot be translated correctly, because Russian has three plural forms and Arabic has six. ICU plural syntax in ARB files lets the translator supply each one.

RTL changes layout, not just text direction

Flutter handles most of right-to-left automatically if the code uses directional properties from the start: EdgeInsetsDirectional rather than EdgeInsets.only with left, AlignmentDirectional rather than Alignment, start and end rather than left and right. Set that convention on day one. Retrofitting it across forty widget files later is a genuinely miserable week.

What does not flip on its own, and needs a decision:

Names and addresses break the standard form

The first-name and last-name split fails for a large share of the world. Many Indonesian users have a single name. Spanish speakers commonly carry two surnames. Japanese and Korean conventions put family name first. Splitting the field makes people mangle their own name to satisfy validation.

For onboarding, one full-name field plus an optional 'What should we call you?' covers nearly every case and deletes a validation branch. Keep the display name separate from any legal name required by payments or identity checks.

Addresses are harder, because the field set itself changes by country. The UK has no state and uses alphanumeric postcodes with a space, like SW1A 1AA. Japan orders an address from largest unit down to smallest and writes the code as 100-0001. Hong Kong and the UAE have no postal code at all, so a required postcode field blocks sign-up outright.

Drive the form from the selected country and make postcode required only where one exists. If Stripe is already in the stack, its Address Element handles this per country and is cheaper than maintaining your own rule table.

Phone entry and OTP delivery differ by country

A one-time-code screen has three failure points and only one of them is code you wrote.

Entry: use a country picker with dial codes and store in E.164 format, like +447700900123. People type national format with leading zeros, and the UK drops that leading zero after +44 while Italy keeps it. Normalising this by hand is a bug factory, so lean on libphonenumber.

Delivery: alphanumeric sender IDs are normal in much of Europe and blocked in the United States, and several countries require sender IDs to be pre-registered. India requires DLT registration of both the sender and the message template, and an unregistered template fails silently. Nothing in your own logs will look wrong.

Fallback: SMS delivery in some markets sits well under 95%, so plan a second route from the start, whether that is an emailed code or a voice call, plus a resend button with a visible countdown instead of a silent lockout. Autofill affects the copy too, since iOS matches the app domain inside the message and Android needs an appended SMS Retriever hash, so the message body is part of the localization job.

Dates, currency and numbers: display versus storage

Store UTC timestamps and store money as integer minor units. Format at display time using the device locale. That single rule kills the two classic bugs.

First, 03/04/2026 reads as 3 April in London and 4 March in Chicago. Never render a purely numeric date during onboarding. 'Trial ends 4 April 2026' is unambiguous everywhere.

Second, the decimal separator. Germany writes 1.234,56 and Switzerland writes 1'234.56, so a price parsed against the wrong separator produces nonsense. Use NumberFormat.currency from intl with an explicit locale and currency code rather than interpolating a hardcoded symbol.

Currency reaches the offer itself. Store price tiers are set per storefront, and a plan at 9.99 USD is not 9.99 EUR once tax is applied. Decide whether you show tax-inclusive prices, which buyers expect across the EU, before the paywall copy is written.

Permission prompts belong in a locale-aware order

Purpose strings have to be translated in the platform files, not only in the app: InfoPlist.strings per locale on iOS, strings.xml per values folder on Android. An English purpose string appearing inside an otherwise Japanese app is both a review risk and an obvious trust problem.

Timing shifts by market as well. Requesting push on first launch depresses opt-in rates everywhere, and in the EU the marketing consent question has to stay separate from the technical notification grant, since agreeing to receive marketing and allowing the operating system to display alerts are not the same permission. Ask for push after the first real action is complete, and leave the marketing toggle unticked.

A fifteen-minute pass per locale

Before a locale ships, take a real device set to that language and region and complete the whole flow once: sign up with a local phone number, a local name containing diacritics, and a local address; confirm nothing truncates; confirm the code arrives and autofills; confirm the first push renders correctly; and check the paywall shows local currency and the expected tax treatment. Most locale bugs die inside that quarter hour.

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