Summary: in this tutorial, you’ll learn how develop a flutter app that supports multiple languages by implementing internationalization (i18n) and localization (l10n).
Step 1. Create a new app #
First, open your terminal and run the following command to create a new flutter app:
flutter create flutter_l10n_demoStep 2. Open the project #
Open the project in your favorite editor like VS Code or Android Studio.
Step 3. Adding dependencies #
Add localization dependencies in pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.20.2Code language: YAML (yaml)Also:
flutter:
uses-material-design: true
generate: trueCode language: JavaScript (javascript)Run the following command to update dependencies:
flutter pub getCode language: JavaScript (javascript)Step 4. Configure l10n.yaml #
Create a l10n.yaml file at the project root to control generation:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getters: false
untranslated-messages-file: build/untranslated.jsonCode language: YAML (yaml)Step 5. Create ARB Translation Files #
Create a directory for your localization files:
lib/l10n/ARB stands for Application Resource Bundle. ARB file is a JSON-based file format used by Flutter’s localization system to store translatable strings and their metadata.
Here’s what makes ARB important:
- Purpose: Holds key-value pairs for localized text (e.g.,
"hello": "Hello"). - Metadata: Includes descriptions and placeholder info for translators (e.g.,
@helloblock). - Locale-specific: Each ARB file corresponds to a language/locale (e.g.,
app_en.arb,app_vi.arb). - ICU syntax support: Handles pluralization, gender, and dynamic placeholders. More on this later.
Add your default (template) ARB and other locales:
lib/l10n/app_en.arb
{
"@@locale": "en",
"appTitle": "Multi-language App",
"@appTitle": {
"description": "The application title shown in the AppBar."
},
"hello": "Hello",
"@hello": {
"description": "A friendly greeting on the home screen."
},
"welcome": "Welcome to our app",
"@welcome": {
"description": "A welcome message explaining the app."
},
"chooseLanguage": "Choose language",
"@chooseLanguage": {
"description": "Label for the language selection UI."
},
"todayIs": "Today is {date}",
"@todayIs": {
"description": "Sentence showing today's date formatted for the current locale.",
"placeholders": {
"date": {
"type": "String",
"example": "December 13, 2025"
}
}
}
}Code language: JSON / JSON with Comments (json)lib/l10n/app_es.arb
{
"@@locale": "es",
"appTitle": "Aplicación multilingüe",
"@appTitle": {
"description": "El título de la aplicación mostrado en la barra superior."
},
"hello": "Hola",
"@hello": {
"description": "Un saludo amistoso en la pantalla principal."
},
"welcome": "Bienvenido a nuestra aplicación",
"@welcome": {
"description": "Mensaje de bienvenida que explica la aplicación."
},
"chooseLanguage": "Elige idioma",
"@chooseLanguage": {
"description": "Etiqueta para el selector de idioma."
},
"todayIs": "Hoy es {date}",
"@todayIs": {
"description": "Frase que muestra la fecha de hoy formateada para el idioma actual.",
"placeholders": {
"date": {
"type": "String",
"example": "13 de diciembre de 2025"
}
}
}
}Code language: JSON / JSON with Comments (json)lib/l10n/app_ja.arb
{ "@@locale": "ja", "appTitle": "多言語アプリ", "@appTitle": { "description": "AppBar に表示されるアプリのタイトル。" }, "hello": "こんにちは", "@hello": { "description": "ホーム画面に表示する挨拶。" }, "welcome": "このアプリへようこそ", "@welcome": { "description": "アプリの説明を兼ねた歓迎メッセージ。" }, "chooseLanguage": "言語を選択", "@chooseLanguage": { "description": "言語選択 UI のラベル。" }, "todayIs": "今日は {date} です", "@todayIs": { "description": "現在のロケールでフォーマットされた日付を表示する文。", "placeholders": { "date": { "type": "String", "example": "2025年12月13日(土)" } } } }Code language: JSON / JSON with Comments (json)Tip: Use consistent keys across ARB files. The
@@localeis optional but recommended.
Step 6. Generate the Localization Code #
Run the official generator:
flutter gen-l10nThis will produce a Dart file (by default at lib/gen/app_localizations.dart, containing the AppLocalizations class and supportedLocales.
Note: You can re-run this command anytime you add or change ARB strings.
Step 7. Wire Up MaterialApp #
Use the generated localizations in your app:
import 'package:flutter/material.dart';
import 'package:flutter_l10n_demo/homepage.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
// path depends on l10n.yaml
import 'l10n/app_localizations.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
static void setLocale(BuildContext context, Locale locale) {
final _MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state?.updateLocale(locale);
}
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Locale? _locale;
void updateLocale(Locale locale) {
setState(() => _locale = locale);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Multi-language App',
locale: _locale,
localizationsDelegates: const [
AppLocalizations.delegate, // ✅ generated
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales, // ✅ generated
home: const HomePage(),
);
}
}
Code language: Dart (dart)Step 8: Use Translations in Widgets #
import 'package:flutter/material.dart';
import 'package:flutter_l10n_demo/l10n/app_localizations.dart';
import 'package:flutter_l10n_demo/language_switcher.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(loc.appTitle)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(loc.hello, style: const TextStyle(fontSize: 24)),
const SizedBox(height: 8),
Text(loc.welcome),
const SizedBox(height: 24),
const LanguageSwitcher(),
],
),
),
);
}
}
Code language: Dart (dart)Step 9. Add a Language Switcher #
import 'package:flutter/material.dart';
import 'package:flutter_l10n_demo/main.dart';
class LanguageSwitcher extends StatelessWidget {
const LanguageSwitcher({super.key});
@override
Widget build(BuildContext context) {
final current = Localizations.localeOf(context);
return DropdownButton<Locale>(
value: _normalize(current),
items: const [
DropdownMenuItem(value: Locale('en'), child: Text('English')),
DropdownMenuItem(value: Locale('es'), child: Text('Español')),
DropdownMenuItem(value: Locale('ja'), child: Text('Japanese')),
],
onChanged: (locale) {
if (locale != null) {
MyApp.setLocale(context, locale);
}
},
);
}
// Ensures we compare only languageCode
Locale _normalize(Locale l) => Locale(l.languageCode);
}Code language: Dart (dart)Common Pitfalls & Fixes #
- Forgot to add
AppLocalizations.delegate: The app won’t find strings—make sure it’s inlocalizationsDelegates. - Didn’t run
flutter gen-l10n: You’ll get missing symbol errors. Re-run after ARB changes. - Locale not switching: Ensure you set
locale:onMaterialAppand rebuild (e.g., usingsetState).
flutter gen-l10n command #
You need to run flutter gen-l10n every time you change any ARB file such as adding, removing, or editting keys becuase:
- The
gen-l10ntool reads your ARB files and generates Dart code (AppLocalizationsclass) with strongly typed getters for each key. - If you add a new key or modify placeholders, the generated code must be updated to reflect those changes.
- Flutter does not auto-regenerate on file save (unless you use IDE plugins or build scripts).
Options #
- Option 1: Run manually: flutter gen-l10n
- Option 2: Automate with IDE. For example, in VS Code, you can use the Flutter Intl plugin (auto-generates on save).
- Option 3: Add a build step by using
flutter pub run build_runner watchwith a custom script. - Option 4: Add to CI/CD pipeline so it runs before build.
💡 Tip: If you forget to run it, you’ll see errors like:
AppLocalizationsmissing new keysNoSuchMethodErrorwhen callingloc.newKey
Formatting Messages #
ICU stands for International Components for Unicode. In the context of Flutter localization, ICU refers to the ICU MessageFormat syntax used in .arb files for handling dynamic text, plurals, genders, and complex language rules.
ICU MessageFormat is a standardized way to write localized messages that include:
- Placeholders for variables (e.g.,
{name}) – allow you to insert variables like names or numbers. - Pluralization rules – different words forr singular and plurals like 1 box and 2 boxes.
- Gender selection – different words for male, female and other.
- Nested conditions.
When using ICU syntax, you should be aware of:
- Syntax errors: If you miss braces or use wrong keywords, it will cause build errors.
- Complexity: When using nested rules, it may become hard to read and maintain.
- Not runtime-editable: ICU is embeded within .arb fille so youo need to run command (
flutter gen-l10n) to generate code.
ICU syntax basics #
Simple placeholder #
"hellUser": "Hi {name}"Code language: JavaScript (javascript)Usage:
l10n.heloUser('John');Code language: JavaScript (javascript)Output:
Hi JohnPluralization #
"itemsCount": "{count, plural, =0{No items} =1{One item} other{{count} items}}"Code language: JavaScript (javascript)In this syntax:
countis the variable.pluralkeyword applies language-specific rules.=0,=1,otherdefine cases.
For example:
l10n.itemsCount(5); Code language: CSS (css)Output:
"5 items"Code language: JSON / JSON with Comments (json)Gender Selection #
"userGender": "{gender, select, male{He} female{She} other{They}} liked your post."Code language: JavaScript (javascript)For example::
l10n.userGender('male');Code language: JavaScript (javascript)Output::
"He liked your post."Code language: JSON / JSON with Comments (json)Nested Example #
"complexMessage": "{gender, select, male{{count, plural, one{He has one item} other{He has {count} items}}} female{{count, plural, one{She has one item} other{She has {count} items}}} other{{count, plural, one{They have one item} other{They have {count} items}}}}"Code language: JavaScript (javascript)Summary #
- Use the
flutter gen-l10ncommand to generate localized code. - Use ICU syntax to format messages.