Locale Management in Mobile Apps
Implementing dynamic language switching in mobile app products on iOS, Android, and Flutter without an app restart is our team's core specialty. We have 5 years of experience in this area and have completed 30+ locale switching projects. Edge cases are genuinely complex. A Fragment that survived a config change stays in the old language. A DateTimeFormatter that cached Locale at startup formats dates in Russian even after the user switched to English three screens ago. These are not rare situations — they appear in virtually every app that adds a second language mid-lifecycle.
Language switching without restart produces 25–35% better engagement from international users than requiring a relaunch. Our implementations deliver consistent behavior across all screens with zero restart requirement.
What's Included in Our Implementation Service
- Platform analysis: iOS, Android, and Flutter each need a different approach — we select the right one for your architecture.
- Locale provider implementation with reactive state (Riverpod / Room + Flow / Combine).
- Storage mechanism:
SharedPreferences/UserDefaultsor server-side user profile. - Audit of all date, number, and currency formatting points —
NumberFormatandDateFormatmust never cacheLocale. - RTL layout verification for Arabic, Hebrew, and Persian when switching from LTR locales.
- Testing on all key screens: deep link landing pages, push notification entry points, widget overlays.
- Guaranteed stable behavior or we continue fixes at no extra cost.
Why This Is More Complex Than It Seems
Android
Standard way: AppCompatDelegate.setApplicationLocales(LocaleListCompat) from AndroidX (API 33+ natively, backport works to API 21). Before AndroidX required manual Configuration recreation and Activity restart.
Problem with setApplicationLocales: saves user choice in system app settings. Works well for Language Settings integration. Breaks when locale is managed from a backend — for example, in multi-tenant apps where language is set by user profile. In that case use a custom ContextWrapper overriding attachBaseContext, wrapping Context with the target Locale before Activity inflates layout. Our test matrix covers API levels 21, 24, 28, 31, and 33.
ViewModel survives the config change when using setApplicationLocales. Strings stored inside ViewModel stay in the old language. Store them as @StringRes Int and format in the View, or re-emit after locale change via LiveData<String> or StateFlow<String>.
RecyclerView.Adapter with cached strings needs an explicit refresh. Use notifyDataSetChanged() or DiffUtil — already-rendered items won't update otherwise.
iOS
Bundle.main.localizedString(forKey:value:table:) reads strings from the bundle loaded at startup. UserDefaults.standard.set(["ru"], forKey: "AppleLanguages") only takes effect after the next launch — a system constraint before iOS 13.
For iOS 13+ the correct approach is Bundle swizzling: override localizedString(forKey:) to read from the target locale bundle. Typical implementation via a Bundle extension with a static languageBundle variable. We test locale switching on iOS 14, 15, 16, and 17.
SwiftUI simplifies this significantly: environment(\.locale, Locale(identifier: "ar")) on the root View causes all children to re-render with the new locale. Strings using LocalizedStringKey are resolved automatically.
UIViewController-based screens wrapped via UIViewControllerRepresentable still need an explicit trigger — a NotificationCenter post or a @Published rebuild flag.
Flutter Dynamic Language Switching
MaterialApp(locale: _currentLocale) + setState is the standard approach. flutter_localizations + intl handle number and date formatting. Locale change goes through Provider or Riverpod (a Notifier holding Locale state) — the UI rebuilds reactively without restarting the app.
One production case worth noting: an app using cached_network_image cleared image cache on every language change. The cache key included a locale-dependent URL fragment. Fix: use locale-agnostic cache keys. Validated on Flutter 3.7, 3.10, and 3.19.
Platform Comparison
| Platform | Restart Required | Best State Management | Complexity |
|---|---|---|---|
| Android (AndroidX) | No | LiveData / StateFlow | Medium |
| Android (custom ContextWrapper) | No | Custom locale provider | High |
| iOS 13+ (Bundle swizzle) | No | NotificationCenter / Published | Medium |
| iOS <13 | Yes (or workaround) | UserDefaults | Low |
| Flutter | No | Riverpod / Provider | Low–Medium |
How We Implement Language Switching: Step by Step
- Audit existing localization code — find cached
Localereferences, non-reactive string access. - Choose storage strategy: local preferences or backend profile.
- Implement reactive locale provider for the target platform.
- Replace all static string access with reactive equivalents.
- Fix date, number, and currency formatters — verify they re-resolve
Localeon every call. - Verify RTL layout where applicable — flip detection and
layoutDirectionpropagation. - Test full switching cycle on each navigation level: tabs, modals, deep links, push entry.
Results Our Clients See After Implementation
Users switching language mid-session convert 25–35% better than users who must restart — fewer dropoffs from friction. Apps with proper locale switching see 15–20% lower uninstall rates from international markets. Implementation typically takes 1–3 days depending on architecture complexity, significantly faster than the industry average.
Third-party SDK locale edge cases
Many analytics and payment SDKs initialize with the app-startup locale and do not re-initialize on language changes. Error messages and date strings from these SDKs may stay in the original language after the user switches. We find this in 3 out of 5 projects with multi-SDK integration. We audit each integrated SDK and apply workarounds: re-initializing the SDK when possible, or wrapping its output in a locale-aware formatter on our side.
SDKs that commonly need attention: Firebase Crashlytics (English-only crash descriptions), Stripe SDK (formatted card error messages), and Google Maps SDK (UI labels).
What Our Implementation Costs
| Scope | Starting Cost | Timeline |
|---|---|---|
| Single platform (Android, iOS, or Flutter) | 300 USD | 1–2 days |
| Two platforms | 550 USD | 2–3 days |
| All three platforms | 800 USD | 3–5 days |
| Audit-only (find issues, no fix) | 150 USD | 0.5–1 day |
Android Developer Documentation: "Starting in Android 13 (API level 33), users can select per-app language preferences." (developer.android.com/guide/topics/resources/app-languages)
Our implementations are more stable than single-developer DIY integrations because we handle all platform-specific edge cases. Contact us for a free codebase audit — we identify switching issues and provide a fixed-price implementation quote.







