Use the per-app language preference API. With AppCompat 1.6.0 or newer (released January 2023), one call switches the language of the whole app and persists the choice:
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
// On the main thread, after Activity.onCreate()
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags("fr"))
// Read it back; an empty list means the app follows the system language
val current = AppCompatDelegate.getApplicationLocales()
// Return to the system language
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
On Android 13 (API 33) and later the call is handed to the system's LocaleManager: the system stores the choice, applies it to the whole app, and the user can also change it in the system settings. On Android 12 and lower AppCompat applies the locales itself to your AppCompatActivity contexts, so activities must extend AppCompatActivity. Running activities are recreated with the new configuration unless they handle locale configuration changes themselves; Compose then recomposes and stringResource returns the new language.
Two manifest additions complete the setup. The first lists the languages you ship so that Android 13+ shows them in system settings (AGP 8.1 or newer can generate the file with androidResources { generateLocaleConfig = true }):
<?xml version="1.0" encoding="utf-8"?>
<!-- res/xml/locales_config.xml -->
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en"/>
<locale android:name="fr"/>
<locale android:name="zh-Hant"/>
</locale-config>
<application android:localeConfig="@xml/locales_config">
<!-- Lets AppCompat persist the choice on Android 12 and lower -->
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data android:name="autoStoreLocales" android:value="true" />
</service>
</application>
With autoStoreLocales, AppCompat saves the locale list and restores it before your first activity is created on older versions; the read is a blocking disk access on the main thread, which StrictMode may report. Without it you must store the choice yourself and call setApplicationLocales before onCreate, for example in attachBaseContext, and only on API levels below 33.
The old approach, building a Configuration with a new locale and calling Resources.updateConfiguration() or createConfigurationContext() in every activity, is discouraged: updateConfiguration() is deprecated, the override does not persist, it is easy to miss the application context, and on Android 13 it fights the system setting. createConfigurationContext() is still a valid API, but using it by hand means reimplementing the persistence and recreation that AppCompat already does.
Pitfalls:
AppCompatActivity contexts. For strings read from the application context or a Service, use ContextCompat.getString(context, id) or ContextCompat.getContextForLanguage(context).pt-BR, zh-Hant) and keep localeConfig in sync with your values-* folders.All of this is documented in Per-app language preferences and the AppCompat release notes.
Published Oct 9, 2026 · Markdown version