Add Android 13 Per-App Languages to Flutter (localeConfig)
Your Flutter app ships five languages, supportedLocales is set, everything works — but open Settings > Apps > your app on an Android 13+ phone and there's no Language option. Meanwhile WhatsApp and Gmail sit right there with a full per-app language picker.
This is the Android counterpart to the iOS stuck-in-English trap: just like iOS ignores supportedLocales until you declare CFBundleLocalizations in Info.plist, Android 13's per-app language preferences ignore your Flutter app entirely until you declare an android:localeConfig in the Android manifest. supportedLocales is Dart-side metadata — the OS never sees it.
Flutter doesn't generate this for you (it's tracked in flutter/flutter#109842, still open), so it's a five-minute manual job. Here's the whole thing, copy-paste ready.
Why your Flutter app language is not in Android settings
Since Android 13 (API 33), users can pick a language per app in system settings. But Android only lists apps that opt in by declaring which locales they support, via a locale-config XML resource referenced from the manifest.
A Flutter app declares its languages in two places Android can't read:
l10n.yaml+ yourapp_en.arb,app_de.arb, … files (compiled into Dart code bygen-l10n)supportedLocales:onMaterialApp
Neither touches the Android resource system, so as far as the OS knows, your app supports nothing. Result: flutter localeConfig not showing in settings — because there is no localeConfig.
Step 1: Create res/xml/locale_config.xml
Create the file android/app/src/main/res/xml/locale_config.xml (you'll likely need to create the xml folder). List exactly the locales you have ARB files for:
<?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="de"/>
<locale android:name="es"/>
<locale android:name="ar"/>
<locale android:name="pt-BR"/>
<locale android:name="zh-Hant"/>
</locale-config>
Two gotchas on the names:
- ARB uses underscores, locale_config uses BCP-47 dashes.
app_pt_BR.arbbecomespt-BR;app_zh_Hant.arbbecomeszh-Hant. Language code, then optional script (ISO 15924), then optional region, joined with-. - The first useful fallback should be your source language (usually
en) — the same locale you put first insupportedLocales.
Step 2: Wire android:localeConfig in the manifest
In android/app/src/main/AndroidManifest.xml, add one attribute to the <application> tag:
<application
android:label="my_app"
android:icon="@mipmap/ic_launcher"
android:localeConfig="@xml/locale_config">
The @xml/locale_config reference must match your file name without the extension. Rebuild and reinstall — a hot restart won't update the manifest:
flutter run
# then on the device: Settings > Apps > your app > Language
You can jump straight to the picker with adb on API 33+:
adb shell am start -a android.settings.APP_LOCALE_SETTINGS -d package:com.example.my_app
What about AGP 8.1's automatic generation?
Android Gradle Plugin 8.1 added an automatic mode — generateLocaleConfig = true in the androidResources block — and Google's docs recommend it for native apps. Don't use it in Flutter. Two reasons:
- It scans the wrong place. AGP derives the locale list from your Android
res/values-*folders. Flutter translations live in ARB files compiled into your Dart program — invisible to AGP. The generated config would contain only the default locale fromresources.properties(unqualifiedResLocale=en-US), which defeats the purpose. - It breaks your manual file. If
generateLocaleConfigis on and a hand-written locale config exists, the build fails — AGP refuses to have both. So if you ever flip that flag on (or copy a native tutorial's Gradle snippet), your previously workinglocale_config.xmlbecomes a build error.
For Flutter, the manual flutter locale_config.xml + supportedLocales pair is the right setup. Leave generateLocaleConfig off.
Step 3: Keep the list in sync with your ARB files
The locale list now lives in three places that must agree:
| Place | Example |
|---|---|
| ARB files | lib/l10n/app_de.arb |
supportedLocales |
Locale('de') |
locale_config.xml |
<locale android:name="de"/> |
(If you use AppLocalizations.supportedLocales from gen-l10n for MaterialApp, that one syncs itself — the XML never will.)
Drift hurts in both directions. A locale in the XML but not in your ARB files means the user picks Polish in system settings and gets English. A locale in your ARB files but not the XML means Polish users never see it offered. Make "add the <locale> line" part of your add-a-language checklist, right next to creating the ARB file — and if juggling a growing set of app_<locale>.arb files by hand is where mistakes creep in, the FlutterLocalisation ARB editor manages all your locales in one UI and validates ICU plurals per language (it catches the dropped few/many categories Arabic and Polish actually need).
Step 4: Read the system-chosen locale back in Dart
Here's the good news: if your MaterialApp is set up normally, there is no step 4. When the user picks a language in system settings, Android restarts your activity with the new locale first in the app's locale list. Flutter's engine passes it through PlatformDispatcher.locales, and the standard resolution against supportedLocales does the rest:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// Do NOT set `locale:` here — a non-null locale overrides
// the user's per-app choice from system settings.
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomeScreen(),
);
}
}
Anywhere you need the resolved locale (date formats, API Accept-Language headers, analytics):
final locale = Localizations.localeOf(context); // e.g. Locale('de')
final raw = WidgetsBinding.instance.platformDispatcher.locales; // full preference list
The SharedPreferences conflict
The big pitfall: many apps built an in-app language switcher that persists the choice in SharedPreferences and sets MaterialApp.locale on startup. On Android 13+ that stored value silently overrides the system picker — the user changes the language in settings and your app ignores it.
The clean fix: only pass a non-null locale: when the user has explicitly chosen a language inside your app, and clear that stored value if they use the system setting (or drop the in-app switcher on Android 13+ entirely and let the OS own it):
MaterialApp(
// null = follow the system / per-app language setting
locale: userExplicitlyChoseInApp ? savedLocale : null,
...
)
Troubleshooting: still not showing?
- Device below Android 13. The per-app picker only exists on API 33+. On older versions users still get your app in the device-wide language.
- Wrong file reference.
android:localeConfig="@xml/locale_config"must match the filenamelocale_config.xmlexactly. - Old install. Manifest changes need a full rebuild and reinstall, not hot reload/restart.
- Picker shows the language but the app stays English. The locale is in your XML but missing from
supportedLocales/ARB — Android offered it, Flutter can't resolve it. locale:is hardcoded onMaterialApp— see the SharedPreferences section above.- Build suddenly fails after a Gradle edit mentioning locale config: you (or a template upgrade) enabled
generateLocaleConfigwhile the manual XML exists. Remove the flag.
Ship it in every language, correctly
The XML file is the easy part — keeping six, twelve, twenty app_<locale>.arb files complete and plural-correct is the real work of Android per-app language preferences in Flutter. FlutterLocalisation gives you an ARB editor and translation management for all your locales, with ICU plural validation that flags the categories each language genuinely requires — so the language a user picks in Android's settings actually works end to end. Try FlutterLocalisation free.