Localize Your Flutter App Name on Android & iOS Home Screens
You wired up gen-l10n, translated every ARB file, and your app runs beautifully in German, Japanese, and Spanish — but on the home screen it's still called Recipes instead of Rezepte or レシピ. That's not a bug in your Flutter setup. The launcher label is the one string gen-l10n can't touch, because it lives entirely in native land: android:label on Android and CFBundleDisplayName on iOS. The OS renders it before your Dart code ever runs.
This guide gives you copy-paste steps for both platforms — including the modern Xcode String Catalog approach that most Flutter tutorials still don't cover — plus an explanation of why the name follows the device language rather than your in-app locale.
Why gen-l10n can't localize the launcher label
Flutter's localization pipeline (flutter_localizations + gen-l10n + your app_<locale>.arb files) produces Dart code that runs inside your app. The home screen, however, is drawn by the Android launcher and iOS SpringBoard — native processes that read your app's metadata directly:
- Android reads
android:labelfromAndroidManifest.xml. - iOS reads
CFBundleDisplayName(falling back toCFBundleName) fromInfo.plist.
To localize the Flutter app name for different languages, you localize those native values with native resource mechanisms. It takes about ten minutes per platform.
Android: android:label + strings.xml
Flutter's template hardcodes the label as a plain string. Switch it to a string resource, then provide per-language translations.
1. Create the default resource. The Flutter template doesn't ship a strings.xml, so create android/app/src/main/res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Recipes</string>
</resources>
2. Add one folder per language. Create sibling folders using ISO 639-1 codes — values-de, values-es, values-ja — each with its own strings.xml. For example, android/app/src/main/res/values-de/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Rezepte</string>
</resources>
Regional variants use an r prefix: values-es-rMX for Mexican Spanish.
3. Point the manifest at the resource. In android/app/src/main/AndroidManifest.xml, replace the hardcoded label with the android:label string resource reference:
<application
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher">
If any <activity> declares its own android:label, it overrides the application-level one for that activity — update it too. That's the whole Android side: the launcher now resolves app_name against the device language, falling back to values/strings.xml when there's no match.
iOS, the modern way: an Xcode String Catalog
Xcode 15 introduced String Catalogs (.xcstrings), and they're still the current mechanism in today's Xcode releases. Almost every Flutter tutorial still teaches only the legacy InfoPlist.strings route — which works (see below) — but the catalog gives you one file with a spreadsheet-style editor and per-language completion tracking.
1. Open the iOS project in Xcode — open ios/Runner.xcworkspace. You need Xcode here, not just your text editor, because the file must be registered in the project and added to the Runner target.
2. Declare your languages. Select the Runner project (not the target) → Info tab → Localizations → click + and add each language.
3. Create the catalog. File → New → File from Template… → String Catalog, and name it exactly InfoPlist. The name matters: Xcode treats InfoPlist.xcstrings as the catalog for Info.plist keys, regardless of what your plist file is called. Make sure it's added to the Runner target and saved under ios/Runner/.
4. Add the keys. In the catalog editor, click + and add CFBundleDisplayName (the home-screen name) and optionally CFBundleName (a fallback, capped at 15 characters). Fill in each language's translation.
On disk, the catalog is JSON, so it diffs cleanly in code review:
{
"sourceLanguage" : "en",
"strings" : {
"CFBundleDisplayName" : {
"extractionState" : "manual",
"localizations" : {
"en" : { "stringUnit" : { "state" : "translated", "value" : "Recipes" } },
"de" : { "stringUnit" : { "state" : "translated", "value" : "Rezepte" } },
"ja" : { "stringUnit" : { "state" : "translated", "value" : "レシピ" } }
}
}
},
"version" : "1.0"
}
A nice property for Flutter apps: at build time Xcode compiles the catalog back down to plain .strings files, so this Xcode String Catalog app-name setup works on every iOS version your app supports. The only requirement is building with Xcode 15 or later — which any Flutter app shipping today already does.
iOS, the legacy way: InfoPlist.strings
If your project (or your CI's Xcode) predates String Catalogs, the classic InfoPlist.strings approach in a Flutter project still works and is what iOS uses under the hood anyway:
- In Xcode, add your languages under Runner project → Info → Localizations, as above.
- File → New → File from Template… → Strings File, named exactly
InfoPlist.strings, saved underRunnerwith the Runner target checked. - Select the new file, open the File Inspector (right panel), click Localize…, and tick each language. Xcode creates
en.lproj/InfoPlist.strings,de.lproj/InfoPlist.strings, and so on. - Put the localized name in each one.
ios/Runner/de.lproj/InfoPlist.strings:
"CFBundleDisplayName" = "Rezepte";
"CFBundleName" = "Rezepte";
Leave the CFBundleDisplayName entry in Info.plist itself as-is — it acts as the fallback for undeclared languages.
Why the name follows the device language, not your app locale
A common surprise: you set locale: Locale('de') on MaterialApp, the whole UI flips to German, but the home screen still says Recipes. That's expected. Your MaterialApp locale only exists inside the running Flutter engine. The launcher and SpringBoard resolve the label from the system language settings, matched against the localizations your app actually declares.
On iOS that declaration matters twice over: if a language isn't in your project's localization list (and, for Flutter apps, CFBundleLocalizations in Info.plist), iOS won't pick it for the label or for your in-app strings. If your iOS build ignores translations entirely, that's a related but separate problem — see our guide to fixing a Flutter iOS app stuck in English via CFBundleLocalizations.
Testing it
- Android: run the app on an emulator, then Settings → System → Languages → move German to the top. The launcher label updates to Rezepte immediately.
- iOS: change the language in Settings → General → Language & Region on a simulator or device. If the label doesn't update, delete the app and reinstall — SpringBoard can cache the old display name.
One more distinction worth knowing: the home-screen name is independent of your store listing name. Play Store listing translations live in Play Console, and per-locale App Store names live in App Store Connect. Localizing all three keeps the name a user taps consistent with the name they downloaded.
Keep the other 99% of your strings in sync too
The launcher label is one string per language. The rest of your app is hundreds of them, spread across app_<locale>.arb files that are easy to let drift — a missing key here, a broken ICU plural there. FlutterLocalisation gives you an ARB editor so you manage translations across all your locales in a UI instead of raw JSON, with ICU plural-syntax validation that flags locales missing a plural category the language actually requires (like a dropped few or many in Polish or Arabic). There's a free tier, so you can bring your ARB files today — try FlutterLocalisation free.