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.

Android14localization4per-app-languageAppCompatapp-dev35

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-05-20
Two Weeks of Letting Antigravity Translate Localizable.xcstrings Across 8 Languages
A two-week log of letting Antigravity draft 8-language translations for new keys added to Localizable.xcstrings. What I delegated, what I kept under my own judgment, the unexpected behaviors, and how I reconciled drafts against the existing translation corpus.
App Dev2026-05-03
Getting Started with AR on Android Using io.github.sceneview — Working Past AnchorNode Errors
A practical guide to building Android AR features with SceneView (io.github.sceneview): AnchorNode error fixes, ARCore session and install handling, plane visualization, and combining Antigravity AI for dynamic scene generation.
App Dev2026-06-21
The Back Button Showed an Interstitial Sometimes, Not Others — Rewriting Nested ifs Into a List of Independent Guards
Interstitial display on back press was unstable because nested if statements hid the priority between conditions. Here is how I split it into reason-returning guards and generated tests from a decision table.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →