Translating your Flutter app is essential for reaching a global audience. Flutter provides excellent built-in support for localization through the intl package and ARB (Application Resource Bundle) files. This guide will walk you through the process of translating a Flutter app using Dart.
Understanding Flutter Localization
Flutter uses ARB files (Application Resource Bundle) to store translations. These JSON-like files contain key-value pairs for each language. Flutter’s intl package handles the localization automatically based on the user’s device language settings.
Step 1: Add Dependencies
First, add the necessary packages to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.19.0
flutter:
generate: true
Run flutter pub get to install the packages.
Step 2: Configure l10n.yaml
Create a l10n.yaml file in your project root to configure localization:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
Step 3: Create ARB Files
Create a lib/l10n directory and add ARB files for each language:
lib/l10n/app_en.arb (English - default):
{
"@@locale": "en",
"appName": "My App",
"@appName": {
"description": "The application name"
},
"welcomeMessage": "Welcome to our app!",
"@welcomeMessage": {
"description": "Welcome message displayed on home screen"
},
"buttonSubmit": "Submit",
"@buttonSubmit": {
"description": "Submit button label"
},
"errorNetwork": "Network error. Please try again.",
"@errorNetwork": {
"description": "Network error message"
},
"itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}",
"@itemsCount": {
"description": "Number of items",
"placeholders": {
"count": {
"type": "int"
}
}
},
"userGreeting": "Hello, {name}! You have {messageCount, plural, =0{no messages} one{1 message} other{{messageCount} messages}}.",
"@userGreeting": {
"description": "Greeting with user name and message count",
"placeholders": {
"name": {
"type": "String"
},
"messageCount": {
"type": "int"
}
}
}
}
lib/l10n/app_es.arb (Spanish):
{
"@@locale": "es",
"appName": "Mi Aplicación",
"welcomeMessage": "¡Bienvenido a nuestra aplicación!",
"buttonSubmit": "Enviar",
"errorNetwork": "Error de red. Por favor, inténtalo de nuevo.",
"itemsCount": "{count, plural, =0{No hay elementos} one{1 elemento} other{{count} elementos}}",
"userGreeting": "¡Hola, {name}! Tienes {messageCount, plural, =0{no hay mensajes} one{1 mensaje} other{{messageCount} mensajes}}."
}
lib/l10n/app_fr.arb (French):
{
"@@locale": "fr",
"appName": "Mon Application",
"welcomeMessage": "Bienvenue dans notre application!",
"buttonSubmit": "Soumettre",
"errorNetwork": "Erreur réseau. Veuillez réessayer.",
"itemsCount": "{count, plural, =0{Aucun élément} one{1 élément} other{{count} éléments}}",
"userGreeting": "Bonjour, {name}! Vous avez {messageCount, plural, =0{aucun message} one{1 message} other{{messageCount} messages}}."
}
Step 4: Generate Localization Files
Run the code generator:
flutter gen-l10n
Or use:
flutter pub run intl_utils:generate
This generates app_localizations.dart and language-specific files in .dart_tool/flutter_gen/gen_l10n/.
Step 5: Configure MaterialApp
Update your main.dart to enable localization:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Localization Demo',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'), // English
Locale('es'), // Spanish
Locale('fr'), // French
],
home: const HomePage(),
);
}
}
Step 6: Use Localized Strings
Access localized strings using AppLocalizations.of(context):
Before:
Text('Welcome to our app!')
After:
Text(AppLocalizations.of(context)!.welcomeMessage)
Or with a null-safe approach:
final l10n = AppLocalizations.of(context);
if (l10n != null) {
Text(l10n.welcomeMessage)
}
Step 7: Handle String Formatting with Variables
Use placeholders in ARB files and Flutter handles the formatting:
ARB file:
{
"userGreeting": "Hello, {name}!",
"@userGreeting": {
"placeholders": {
"name": {
"type": "String"
}
}
}
}
Dart:
Text(AppLocalizations.of(context)!.userGreeting('John'))
// English: "Hello, John!"
// Spanish: "¡Hola, John!"
Step 8: Pluralization
Flutter supports pluralization using ICU message format:
ARB file:
{
"itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}"
}
Dart:
Text(AppLocalizations.of(context)!.itemsCount(5))
// English: "5 items"
// Spanish: "5 elementos"
Text(AppLocalizations.of(context)!.itemsCount(1))
// English: "1 item"
// Spanish: "1 elemento"
Text(AppLocalizations.of(context)!.itemsCount(0))
// English: "No items"
// Spanish: "No hay elementos"
Step 9: Format Dates and Numbers
Use intl package for localized formatting:
Dates:
import 'package:intl/intl.dart';
final date = DateTime.now();
final formatter = DateFormat.yMMMMEEEEd(Intl.getCurrentLocale());
final localizedDate = formatter.format(date);
// English: "Wednesday, March 20, 2025"
// Spanish: "miércoles, 20 de marzo de 2025"
Numbers:
import 'package:intl/intl.dart';
final number = 1234.56;
final formatter = NumberFormat.decimalPattern(Intl.getCurrentLocale());
final localizedNumber = formatter.format(number);
// English: "1,234.56"
// Spanish: "1.234,56"
Currency:
import 'package:intl/intl.dart';
final amount = 1234.56;
final formatter = NumberFormat.currency(locale: Intl.getCurrentLocale());
final localizedCurrency = formatter.format(amount);
// English (US): "$1,234.56"
// Spanish (ES): "1.234,56 €"
Step 10: Handle Right-to-Left (RTL) Languages
Flutter automatically handles RTL layouts. Use Directionality widget if needed:
Directionality(
textDirection: TextDirection.rtl, // or TextDirection.ltr
child: YourWidget(),
)
Or detect automatically:
final locale = Localizations.localeOf(context);
final isRTL = locale.languageCode == 'ar' || locale.languageCode == 'he';
Step 11: Test Your Localization
Using Flutter DevTools:
- Run your app
- Open DevTools
- Go to Inspector tab
- Change locale in device settings
Using Device Settings:
- Go to device Settings > Language & Region
- Change the device language
- Restart your app
Programmatically (for testing):
import 'package:flutter/material.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Locale _locale = const Locale('en');
void _changeLanguage(Locale locale) {
setState(() {
_locale = locale;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: _locale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
],
home: LanguageSelector(
onLanguageChanged: _changeLanguage,
),
);
}
}
Best Practices
1. Use Descriptive Keys
Bad:
{
"msg1": "Submit"
}
Good:
{
"buttonSubmit": "Submit",
"@buttonSubmit": {
"description": "Submit button label"
}
}
2. Provide Context in ARB Files
Always include @ metadata entries to help translators:
{
"deleteButton": "Delete",
"@deleteButton": {
"description": "Button to delete an item. Shown in the item detail view."
}
}
3. Avoid String Concatenation
Bad:
Text(AppLocalizations.of(context)!.hello + ' ' + name)
Good:
{
"helloUser": "Hello, {name}!",
"@helloUser": {
"placeholders": {
"name": {"type": "String"}
}
}
}
Text(AppLocalizations.of(context)!.helloUser(name))
4. Use Pluralization Correctly
Always use ICU plural format for countable items:
{
"messagesCount": "{count, plural, =0{No messages} one{1 message} other{{count} messages}}"
}
5. Test String Lengths
Some languages are longer than others. Design your UI to accommodate:
- German and Finnish: 30-50% longer than English
- Asian languages: May need more vertical space
- Arabic and Hebrew: RTL layout considerations
6. Handle Missing Translations
Flutter falls back to the default locale if a translation is missing. Always provide a fallback:
final l10n = AppLocalizations.of(context);
final text = l10n?.welcomeMessage ?? 'Welcome'; // Fallback
Common Pitfalls
1. Forgetting to Run Code Generation
After updating ARB files, always run:
flutter gen-l10n
2. Not Including All Required Delegates
Make sure to include all four delegates:
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
3. Hardcoding Format Strings
Bad:
Text('\$${amount.toStringAsFixed(2)}')
Good:
final formatter = NumberFormat.currency(locale: Intl.getCurrentLocale());
Text(formatter.format(amount))
4. Not Testing Edge Cases
Test with:
- Very long strings
- Special characters
- Numbers and dates
- Plural forms (0, 1, many)
- RTL languages
Advanced: Dynamic Language Switching
To allow users to change language within the app:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LocaleProvider extends ChangeNotifier {
Locale _locale = const Locale('en');
Locale get locale => _locale;
Future<void> loadLocale() async {
final prefs = await SharedPreferences.getInstance();
final languageCode = prefs.getString('language_code') ?? 'en';
_locale = Locale(languageCode);
notifyListeners();
}
Future<void> setLocale(Locale locale) async {
if (_locale == locale) return;
_locale = locale;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('language_code', locale.languageCode);
notifyListeners();
}
}
Usage:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LocaleProvider()..loadLocale(),
child: Consumer<LocaleProvider>(
builder: (context, localeProvider, _) {
return MaterialApp(
locale: localeProvider.locale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
],
home: const HomePage(),
);
},
),
);
}
}
Using with GetX (Alternative)
If you’re using GetX for state management:
import 'package:get/get.dart';
class LocaleController extends GetxController {
var locale = const Locale('en').obs;
void changeLocale(Locale newLocale) {
locale.value = newLocale;
Get.updateLocale(newLocale);
}
}
Conclusion
Localizing your Flutter app is straightforward when you follow these steps:
- Add
flutter_localizationsandintlpackages - Create ARB files for each language in
lib/l10n/ - Configure
l10n.yamland run code generation - Set up
localizationsDelegatesinMaterialApp - Use
AppLocalizations.of(context)throughout your app - Handle pluralization with ICU message format
- Format dates and numbers using
intlpackage - Test thoroughly in all supported languages
By following these practices, you’ll create an app that provides a native experience for users worldwide, significantly expanding your potential user base.
Streamline Your Flutter Localization Workflow
Managing translations for multiple languages can become complex as your app grows. Consider using a translation management platform to:
- Collaborate with translators
- Keep translations in sync with your codebase
- Automate the translation workflow
- Maintain consistency across all languages
- Generate ARB files automatically
- Integrate with your CI/CD pipeline
Ready to take your Flutter app global? Explore AZbox’s localization platform and streamline your translation workflow: