Introduction
Supporting multiple languages in Android apps sounds easy at first — just add some strings.xml
files, right?
In reality, especially when building a multi-module app with dynamic language switching and distributing it through Google Play using the standard .aab
format, things get complicated fast.
In my app Movieland, I encountered unexpected behavior: even though the app officially supports many languages and allows users to change them dynamically from the settings screen — without touching system preferences — some users were seeing broken UI or missing translations after downloading the app from the Play Store.
Let me walk you through the problem, how I investigated it, and the solution I implemented that improved both UX and reliability.
Why in-app language switching matters
Traditionally, Android apps relied on the system language settings to determine which resources to load.
If a user wanted a different language in an app, they had to change their entire device language — not ideal.
Today, many apps offer in-app language selection — letting users choose their preferred language directly inside the app, without affecting the rest of the system.
This approach is officially supported via per-app language preferences and significantly improves accessibility, internationalization, and user control.
But it also introduces new technical challenges:
- The selected locale must be applied dynamically
- The app should respond immediately to changes
- It has to work consistently across Android versions and devices
Movieland was built with this experience in mind — but the reality of .aab
delivery changed how it behaved in production.
The Problem With Language Resources and AAB
Android App Bundles (.aab
) are now the standard for publishing apps on Google Play. They allow Google to optimize installs by delivering only the resources relevant to each user’s device — including languages.
If you’re building multilingual support from scratch, the official documentation provides a solid guide:
👉 Per-app language preferences — Android Developers
In my case, the situation was slightly more complex:
- The app is multi-module
- All language resources are stored in a
core
module - The dynamic language switching logic lives in the
app
module, where the bundle is configured
This setup works flawlessly in development and during .apk
testing — all resources are bundled, and language switching is immediate.
But .aab
changes things.
By default, Google Play installs only the languages configured on the user’s device. Other languages are delivered on demand, only when needed. This can create unexpected delays or behaviors.
I could have forced all languages to be included using this Gradle configuration:
bundle { language { enableSplit = false } }
But I intentionally chose not to do that.
Why?
Because I wanted to avoid inflating the app size unnecessarily.
Even though the app supports 11 languages, I knew that most users only use 1 or 2.
Instead of disabling language splitting, I started looking for a smarter approach — one that would respect the .aab
behavior, but still provide a seamless user experience.
UX Observations and a Practical Insight
The first sign of trouble came from user behavior.
People would open the language settings in the app, select a different language — and… nothing happened. No immediate change. No feedback. It felt like the feature was broken.
But in development, it worked perfectly.
After some investigation, logs, and testing real installs, I discovered the truth:
Google Play was indeed delivering the missing language resources — but silently and only after the app was restarted.
From a technical standpoint, this was expected.
But from a UX perspective?
Terrible.
This led to a key realization:
Instead of showing every supported language, I could show only the ones that are already present on the device — the ones that would work instantly.

This approach not only solved the problem — it actually created a better experience:
- No broken expectations
- No waiting or confusion
- And if a user has multiple languages on their device, they can switch between them freely — without going into system settings
The result: clean, responsive, and user-friendly in-app language switching.
Applying the Language Selection
Once I had the filtered list of available languages, the next step was to present them in the UI and apply the selection instantly — without requiring a restart.
This was done via a custom bottom sheet component that lists the available languages and allows users to switch between them with a single tap.
1. Filtering and preparing the language list
First, I retrieve the languages currently configured on the user’s device and filter the supported list accordingly:
val userPreferredLanguages: Set<String> = (0 until LocaleList.getAdjustedDefault().size()) .map { LocaleList.getAdjustedDefault()[it].language } .toSet() val filteredLanguages = LanguageType.entries.filter { type -> Locale.forLanguageTag(type.locale).language in userPreferredLanguages }
Here, LanguageType
is an enum that defines the supported language codes and maps each one to a localized name:
enum class LanguageType(val locale: String, @StringRes val nameRes: Int) { ENGLISH("en", R.string.language_english), GERMAN("de", R.string.language_german), ... }
2. Rendering the bottom sheet language selector
The filtered list is mapped to a simple model and passed into a Compose component:
SelectLanguageBottomSheetComponent( list = filteredLanguages.map { type -> LanguageModel( type = type, selected = state.selectedLanguage == type.locale ) }, onItemClick = { item -> // Apply language } )
LanguageModel
just tracks the selected state:
data class LanguageModel( val type: LanguageType, val selected: Boolean )
3. Applying the selected language dynamically
When a user taps a language, the app immediately updates the locale. Here’s how the change is applied:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.getSystemService(LocaleManager::class.java) ?.applicationLocales = LocaleList.forLanguageTags(item.type.locale) } else { AppCompatDelegate.setApplicationLocales( LocaleListCompat.forLanguageTags(item.type.locale) ) }
Finally, I update the state and close the bottom sheet:
coroutineScope.launch { onAction(SettingsAction.ChangeLanguage(language = item.type.locale)) sheetState.hide() }
No app restart is needed. The change takes effect immediately and persists across sessions.
Job Offers
Conclusion
Supporting multiple languages in a modern Android app isn’t just about adding translations.
It’s about building a smart, responsive experience that works seamlessly with how the platform delivers resources — especially when using .aab
on Google Play.
In my case, the default behavior of resource delivery created a silent UX trap: language switching that seemed broken but wasn’t.
By dynamically filtering the available languages and applying changes instantly, I was able to fix this issue and — more importantly — make the feature feel intuitive and reliable.
It was a small adjustment in logic, but one that led to a much better experience overall.
Want to see it in action?
This isn’t just theory. You can try it yourself in a real-world app:
🎬 Movieland on Google Play
Open the app, head to Settings → Language, and experience how smooth in-app language switching can be — even with all the .aab
limitations under the hood.
Found this helpful?
- Drop a few claps 👏
- Leave a comment if you’ve faced similar issues
- Follow me on Medium to get more Android development insights from real-world production cases
This article was previously published on proandroiddev.com.