◉ANTIGRAVITY LABJP
Articles/App Development
▣ App Development/2026-06-22Intermediate

Letting Users Switch the App's Language Without Touching Their Device Language — Notes on Android Per-App Language

How to implement in-app language switching on Android with AppCompatDelegate.setApplicationLocales and locales_config, plus the gotchas around pre-API-33 compatibility, activity recreation, and AdMob, from an implementation point of view.

Android27localization6per-app-languageAppCompatapp-dev54

✦ Premium Article

A request to put a language switch in the settings screen tends to arrive sooner for apps published abroad. With the wallpaper and utility apps I maintain on Android, I once heard from an English-speaking user who said, "I want to keep my phone in English but read this one app in Japanese." Asking them to switch the entire device language doesn't solve that.

Android gained a proper mechanism for switching only the app's language in API 33 (Android 13), called Per-App Language Preferences. When you actually wire it up, though, a string of quiet snags appears: how to handle devices below API 33, a flicker the moment the language changes, and settings that vanish after a restart. This article is my implementation notes, written while letting an Antigravity agent handle the research and a first draft of the code.

Overriding the device language and switching the app language are two different things

The long-standing approach was to override locale in Configuration and rebuild the Context. Many of you have probably seen code that swaps the locale in attachBaseContext. It works, but it has two weaknesses.

First, the OS has no idea about your app's language setting. Your app won't appear in the system Settings list of "App languages." Second, you have to reapply the locale yourself on every recreation, and libraries and system dialogs tend to stay in the device language.

setApplicationLocales, from API 33 onward, hands this setting to the OS. The OS remembers the per-app language, so it shows up in Settings, and on every launch the OS prepares a Context with the correct locale. Here is how the two compare.

AspectConfiguration override (legacy)setApplicationLocales (API 33+ / AppCompat backport)
Registered with OSNoYes (shows in Settings app languages)
PersistenceYou store it (SharedPreferences, etc.)OS (or AppCompat's store) holds it
Restore after restartYou reapplyRestored automatically
Language of system dialogsTends to stay in device languageTends to follow the app language
Amount of codeHigh (Context swapping)Low (one call plus config)

New project or existing one, my conclusion is to build around AppCompatDelegate.setApplicationLocales. AppCompat 1.7 backports this API to below API 33, so you can write the same code even when your minimum version is old.

Declaring supported languages to the OS with locales_config

First, tell the OS which languages your app supports. Create res/xml/locales_config.xml and reference it from the manifest via android:localeConfig. With this in place, the system Settings "App language" screen shows your language list.

<!-- res/xml/locales_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
    <locale android:name="en" />
    <locale android:name="ja" />
    <locale android:name="ko" />
    <locale android:name="zh-Hans" />
</locale-config>
<!-- AndroidManifest.xml -->
<application
    android:localeConfig="@xml/locales_config"
    ... >
</application>

Here is the first pitfall. The language tags listed in locales_config.xml must match the languages that actually have a values-xx folder. For region or script tags like zh-Hans, prepare the resource folder as values-b+zh+Hans rather than values-zh-rCN so they line up. When the listed languages and the resources diverge, you get a confusing state where the language appears in Settings but selecting it changes nothing.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦The proper Android 13+ way to switch only the app's language without changing the device language, and how to back it down to API levels below 33
✦Where activity recreation, process recreation, and persistence trip you up when changing language, with the code that avoids it
✦How to diagnose why AdMob or a WebView won't follow the app language, and what actually worked
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

▣ App Dev2026-09-10
When the iOS 27 Date Landed, the First File I Grepped Was AGENTS.md
iOS 27 and iPadOS 27 ship on September 14. Old version numbers linger in your source, but also in the files only your agent reads. Here is a script that counts both zones, plus the one line keyword search will never find.
▣ App Dev2026-09-08
Answering the age-rating questionnaire from a file in the repo, not from memory
The App Store age-rating questionnaire became mandatory in September 2026. Here is how I moved the answers into a declaration file in the repo and had a stdlib-only script argue back, with the real output.
▣ App Dev2026-09-04
The Five Days Between the iOS 27 RC and Release — I Check Init Order and Entitlement Restore Before Layout
A record of how I narrow the agent's search scope during the short window before an OS release — by launch path rather than by screen. Includes the audit script I run to catch consent and ads init ordering, entitlement restore gaps, and hardcoded size branches.