Translating a Flutter app looks harder than it is. Flutter ships with everything you need. You keep your text in ARB files, one per language, and Flutter turns them into a Dart class you call from your widgets. This guide takes you from an English-only app to one that switches language with the phone's settings. It also covers the parts people often get wrong: placeholders, plurals and right-to-left languages.
1. Add the packages
In pubspec.yaml, add Flutter's localization package and intl, and turn on code generation:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: any
flutter:
generate: trueintl: any lets Flutter pick the intl version that matches your Flutter version, which avoids version clashes.
2. Tell Flutter where your text lives
Create l10n.yaml in the project root:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
nullable-getter: falseThe template file is your source language. Every other language follows its keys. The last line, nullable-getter: false, saves you from adding ! every time you look up a text.
3. Write your first ARB file
Create lib/l10n/app_en.arb:
{
"@@locale": "en",
"homeTitle": "Welcome back",
"@homeTitle": {
"description": "Title on the home screen"
},
"cartItems": "{count, plural, =0{Your cart is empty} =1{1 item in your cart} other{{count} items in your cart}}",
"@cartItems": {
"placeholders": {
"count": { "type": "num" }
}
},
"greeting": "Hello, {name}!",
"@greeting": {
"placeholders": {
"name": { "type": "String" }
}
}
}A few habits pay off later:
- Add a description to every key. Translators, and AI translation tools, see only the text. "Title on the home screen" tells them it's a short heading and not a sentence.
- Never build sentences by joining strings.
"Hello, " + namecan't be translated, because many languages put the name somewhere else. Use a placeholder. - Use plurals, not if-statements. English has two forms. Arabic has six and Polish has four. The plural format above (a standard called ICU) handles all of them. When you translate a plural, use the forms the other language needs (
zero,one,two,few,many,other), not only the=0and=1from English.
Now add a second language. Create lib/l10n/app_es.arb with the same keys:
{
"@@locale": "es",
"homeTitle": "¡Hola de nuevo!",
"cartItems": "{count, plural, =0{Tu carrito está vacío} =1{1 artículo en tu carrito} other{{count} artículos en tu carrito}}",
"greeting": "¡Hola, {name}!"
}The descriptions (@ entries) only need to be in the template file.
4. Generate the code and connect it to your app
Run flutter pub get or start the app, and Flutter generates the code for you. You can also run flutter gen-l10n yourself. Recent Flutter versions write app_localizations.dart next to your ARB files. Older ones put it in a hidden package, so update Flutter if the import below doesn't work.
Then connect it in MaterialApp:
import 'package:flutter/material.dart';
import 'l10n/app_localizations.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomeScreen(),
);
}
}localizationsDelegates also includes Flutter's own translations for built-in widgets, such as the date picker and the "Back" button labels.
5. Use the text in your widgets
// Inside a widget's build method:
final l10n = AppLocalizations.of(context);
Text(l10n.homeTitle);
Text(l10n.greeting('Sara'));
Text(l10n.cartItems(3)); // "3 items in your cart"Every key becomes a Dart property or method, so a typo in a key name stops the build instead of showing an empty label to your users.
The app now follows the phone's language. To test it, change the language on the simulator or emulator, or force one for a moment with locale: const Locale('es') on MaterialApp.
6. Don't forget iOS
On iOS, the App Store lists only the languages your Xcode project declares. Open ios/Runner.xcodeproj in Xcode, select the Runner project (under Projects, not Targets), open the Info tab and add each language under Localizations. Xcode then creates a few small files that the App Store uses to show which languages your app supports.
7. Right-to-left languages work almost for free
When the language is Arabic, Hebrew or Persian, MaterialApp flips the layout direction for you. What it can't fix is code that assumes left and right:
- Use
EdgeInsetsDirectional.only(start: 16)instead ofEdgeInsets.only(left: 16). - Use
AlignmentDirectional.centerStartinstead ofAlignment.centerLeft. - Mirror arrow icons that point "forward". Common back and forward arrows such as
Icons.arrow_backalready flip by themselves, so check any other arrow you use. Icons such as a phone or a camera don't need mirroring.
Test with a real right-to-left language early. Also, German text is often much longer than English and Arabic needs more line height, so check that buttons don't overflow.
8. Keep translations in sync as the app grows
The steps above are the easy part. The hard part starts with the next release, when you add twelve keys, change three, and need them in every language before you ship. Common ways to handle this:
- Edit the ARB files by hand. This is fine for one or two languages and a small app. It breaks down once someone else translates, or you forget which keys changed.
- Send the files to a translator. Export
app_en.arb, get the translations back, and paste them in. It works, but it's slow, and you have to keep track of what changed since last time. - Use a translation management tool. Upload your English file, translate new keys (by hand, with AI, or both), then download the other languages.
litei18n is one such tool, built for small teams. You can import and export ARB files (on paid plans, from $9 a month; the free plan works with JSON), translate missing keys with AI, and review the changes before they're published. litei18n reads each new key's description from your ARB file and writes the descriptions back when you download, so translators and the AI see them. Downloads are named to match Flutter, like app_es.arb. To change a description later, edit it in litei18n, because uploading the file again won't update it. It doesn't keep placeholder settings yet, so keep your own app_en.arb as the source and download only the other languages. If you work with an AI coding assistant such as Claude or Cursor, it can also add and translate keys for you through litei18n's MCP connection, which lets AI tools work in litei18n directly.
9. Updating text without a new app release
Flutter compiles ARB files into your app. That means fixing a typo in German, or adding a language, normally needs a new build and a review on the App Store and Google Play.
If that's a problem for you, you can load translations at runtime instead. Keep a bundled copy for offline use and the first launch, then fetch the latest version from a server when the app starts. Our over-the-air guide for Flutter shows the full code. It's about 30 lines and uses its own small class instead of AppLocalizations. That simpler approach handles plain text only: placeholders and plurals need extra code, so it suits apps whose text is mostly simple. With litei18n, over-the-air updates are on paid plans.
Quick checklist
flutter_localizationsandintladded, withgenerate: truel10n.yamlpoints to your ARB folder and template- Every key has a description
- Placeholders and plurals instead of joined strings
- Every language added under Localizations in Xcode
- Directional padding and alignment for right-to-left languages
- A plan for keeping new keys translated
That's all it takes to make a Flutter app speak more than one language. Start with one extra language. Once the setup works, adding the next one is just another ARB file.
Want to manage your ARB files in one place and translate new keys with AI? Start free with litei18n.