Blog Infos
Author
Published
Topics
, , , ,
Published

Photo by Ling App on Unsplash

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 .aabbehavior, 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

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

When interacting with a mobile app that regularly crashes or freezes, 53% of users uninstalled the app, 37% stopped using it, and 28% looked for a replacement.
Watch Video

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

Sean Higgins
Mobile Field Engineer
Instabug

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

Sean Higgins
Mobile Field Enginee ...
Instabug

Beyond the Basics: Performance Monitoring and User Experience for Mobile App Growth

Sean Higgins
Mobile Field Engineer
Instabug

Jobs

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.

Menu