Flutter: Internationalization & Localization
One app for the world — internationalization lets your code speak the user's language and use the user's currency.
📋 Prerequisites: You should already be familiar with
- Lesson 16: Theming & Styling System
1. What You'll Learn
- flutter_localizations + intl package: ARB file workflow and code generation
- localizationsDelegates and supportedLocales configuration
- Dynamic language switching: runtime locale switching (Riverpod-managed)
- ICU formatting: numbers (thousand separators), currency (USD/EUR/CNY), dates, plurals
- ShopApp: Chinese/English/Japanese trilingual + USD/CNY/JPY multi-currency switching
2. A Real Story of Global Expansion Chaos
(1) The Pain Point: Hard-Coded Values Confuse Global Users
Bob's ShopApp only supports English and USD. Alice (a Chinese user) sees "$9,999.00" and assumes 9999 USD, when it should be 9999 CNY. Charlie (a Japanese user) sees "1,500" and can't tell if it's one thousand five hundred or fifteen hundred. Even worse, product descriptions are all in English, and non-English users have a 70% bounce rate.
(2) The ARB + ICU Formatting Solution
Flutter's l10n system uses ARB files to manage translations, and ICU formatting automatically handles regional differences in numbers/currency/dates.
import 'package:intl/intl.dart';
// ⚙️ Install dependencies: flutter pub add intl
// Locale-aware formatting
final price = NumberFormat.simpleCurrency(locale: 'zh_CN').format(9999);
// → ¥9,999.00 (CNY)
final price2 = NumberFormat.simpleCurrency(locale: 'en_US').format(1299.99);
// → $1,299.99 (USD)
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
(3) The Payoff: Bounce Rate Drops from 70% to 15%
After implementing three languages + three currencies, non-English region bounce rates dropped from 70% to 15%, and the Japanese market saw 3x order growth.
3. i18n Data Flow
graph TD
ARB[ARB Files] --> |l10n| GEN[Generated Dart]
GEN --> MAT[MaterialApp.localizationsDelegates]
MAT --> L10N[AppLocalizations]
L10N --> EN[English: $1,299.99]
L10N --> ZH[中文: ¥9,999.00]
L10N --> JA[日本語: ¥150,000]
subgraph Users
Alice2[Alice: EN / USD]
Bob2[Bob: ZH / CNY]
Charlie[Charlie: JA / JPY]
end
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
(1) Key Internationalization Terms
| Term | Description | Example |
|---|---|---|
| i18n | Internationalization (18 letters omitted) | Framework support |
| l10n | Localization (10 letters omitted) | Specific translations |
| Locale | Language + region identifier | en_US, zh_CN, ja_JP |
| ARB | Application Resource Bundle | Translation file format |
| ICU | International Components for Unicode | Formatting standard |
4. ARB File Workflow
(1) Project Configuration
# l10n.yaml (project root)
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: S
nullable-getter: false
# pubspec.yaml
flutter:
generate: true
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: ARB translation files
// lib/l10n/app_en.arb (template)
{
"appTitle": "ShopApp",
"productCount": "{count, plural, =0{No products} =1{1 product} other{{count} products}}",
"priceWithCurrency": "{price, select, USD{${price}} CNY{¥{price}} JPY{¥{price}}}",
"welcomeMessage": "Welcome, {name}!",
"lastUpdated": "Last updated: {date}",
"@productCount": {
"placeholders": { "count": { "type": "int" } }
},
"@priceWithCurrency": {
"placeholders": { "price": { "type": "String" }, "currency": { "type": "String" } }
},
"@welcomeMessage": {
"placeholders": { "name": { "type": "String" } }
},
"@lastUpdated": {
"placeholders": { "date": { "type": "DateTime" } }
}
}
// lib/l10n/app_zh.arb
{
"appTitle": "ShopApp",
"productCount": "{count, plural, =0{没有商品} other{{count} 件商品}}",
"priceWithCurrency": "{price, select, USD{\\${price}} CNY{¥{price}} JPY{¥{price}}}",
"welcomeMessage": "欢迎,{name}!",
"lastUpdated": "最后更新:{date}"
}
// lib/l10n/app_ja.arb
{
"appTitle": "ShopApp",
"productCount": "{count, plural, other{{count}件の商品}}",
"priceWithCurrency": "{price, select, USD{\\${price}} CNY{¥{price}} JPY{¥{price}}}",
"welcomeMessage": "ようこそ、{name}さん!",
"lastUpdated": "最終更新: {date}"
}
5. MaterialApp Configuration
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: l10n configuration
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// ⚙️ Install dependencies: flutter pub add flutter_riverpod
// ⚙️ Configure l10n: create l10n.yaml in project root, add flutter: generate: true to pubspec.yaml
// Custom class definition source:
// - localeProvider: see Section 7 LocaleNotifier in this lesson
class ShopApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeProvider);
return MaterialApp(
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
title: 'ShopApp',
home: const HomePage(),
);
}
}
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: Using translated strings
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// ⚙️ Configure l10n: create l10n.yaml in project root, run flutter gen-l10n to generate code
// In any widget
final s = AppLocalizations.of(context)!;
Text(s.appTitle) // ShopApp
Text(s.productCount(42)) // 42 products / 42 件商品
Text(s.welcomeMessage('Alice')) // Welcome, Alice! / 欢迎,Alice!
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
6. ICU Formatting
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: Currency formatting
import 'package:intl/intl.dart';
import 'package:intl/number_symbols_data.dart';
// ⚙️ Install dependencies: flutter pub add intl
class CurrencyFormatter {
static String format(double amount, {String locale = 'en_US', String? currency}) {
final format = NumberFormat.simpleCurrency(locale: locale, name: currency);
return format.format(amount);
}
}
// Usage
CurrencyFormatter.format(1299.99, locale: 'en_US') // $1,299.99
CurrencyFormatter.format(9999.00, locale: 'zh_CN') // ¥9,999.00
CurrencyFormatter.format(150000, locale: 'ja_JP') // ¥150,000
CurrencyFormatter.format(99.99, locale: 'de_DE', currency: 'EUR') // 99,99 €
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
| Locale | Number Format | Currency Format | Date Format |
|---|---|---|---|
| en_US | 1,299.99 | $1,299.99 | 07/13/2026 |
| zh_CN | 1,299.99 | ¥9,999.00 | 2026/07/13 |
| ja_JP | 1,299.99 | ¥150,000 | 2026/07/13 |
| de_DE | 1.299,99 | 1.299,99 € | 13.07.2026 |
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: Date formatting
import 'package:intl/intl.dart';
// ⚙️ Install dependencies: flutter pub add intl
String formatDate(DateTime date, {String locale = 'en_US'}) {
return DateFormat.yMMMd(locale).format(date);
}
// en_US: Jul 13, 2026
// zh_CN: 2026年7月13日
// ja_JP: 2026年7月13日
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: Plural formatting
// In ARB file
"cartItemCount": "{count, plural, =0{Your cart is empty} =1{1 item in cart} other{{count} items in cart}}"
// Usage
Text(s.cartItemCount(0)) // Your cart is empty
Text(s.cartItemCount(1)) // 1 item in cart
Text(s.cartItemCount(5)) // 5 items in cart
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
7. Dynamic Language Switching
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: Riverpod Locale management
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
// ⚙️ Install dependencies: flutter pub add flutter_riverpod riverpod_annotation shared_preferences intl
// ⚙️ Dev dependencies: flutter pub add --dev riverpod_generator build_runner
// Custom class definition source:
// - localeNotifierProvider/currencyProvider: see definitions below
@riverpod
class LocaleNotifier extends _$LocaleNotifier {
@override
Locale build() {
// Load saved preference
_loadSavedLocale();
return const Locale('en');
}
Future<void> _loadSavedLocale() async {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString('locale');
if (saved != null) {
state = Locale(saved);
}
}
Future<void> setLocale(Locale locale) async {
state = locale;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('locale', locale.languageCode);
}
}
// Settings page
class LanguageSettingsPage extends ConsumerWidget {
static const _locales = [
(Locale('en'), 'English', '🇺🇸'),
(Locale('zh'), '中文', '🇨🇳'),
(Locale('ja'), '日本語', '🇯🇵'),
];
@override
Widget build(BuildContext context, WidgetRef ref) {
final current = ref.watch(localeNotifierProvider);
return Scaffold(
appBar: AppBar(title: const Text('Language')),
body: ListView(children: _locales.map((item) {
final (locale, name, flag) = item;
return ListTile(
leading: Text(flag, style: const TextStyle(fontSize: 24)),
title: Text(name),
trailing: current == locale ? const Icon(Icons.check, color: Colors.green) : null,
onTap: () => ref.read(localeNotifierProvider.notifier).setLocale(locale),
);
}).toList()),
);
}
}
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
8. Complete Example: ShopApp Localized Price Widget
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
// ⚙️ Install dependencies: flutter pub add flutter_riverpod riverpod_annotation shared_preferences intl
// ⚙️ Dev dependencies: flutter pub add --dev riverpod_generator build_runner
// Custom class definition source:
// - localeNotifierProvider: see Section 7 LocaleNotifier in this lesson
// - currencyProvider: see CurrencyNotifier below
class LocalizedPrice extends ConsumerWidget {
final double amount;
final TextStyle? style;
const LocalizedPrice({super.key, required this.amount, this.style});
@override
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeNotifierProvider);
final currency = ref.watch(currencyProvider);
final formatted = _formatPrice(amount, locale.languageCode, currency);
return Text(formatted, style: style ?? const TextStyle(fontSize: 18, fontWeight: FontWeight.bold));
}
String _formatPrice(double amount, String languageCode, String currencyCode) {
final locale = switch (languageCode) {
'zh' => 'zh_CN',
'ja' => 'ja_JP',
_ => 'en_US',
};
return NumberFormat.simpleCurrency(locale: locale, name: currencyCode).format(amount);
}
}
// Currency provider
@riverpod
class CurrencyNotifier extends _$CurrencyNotifier {
@override
String build() {
_loadSaved();
return 'USD';
}
Future<void> _loadSaved() async {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString('currency');
if (saved != null) state = saved;
}
Future<void> setCurrency(String code) async {
state = code;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('currency', code);
}
}
// Usage in product card
LocalizedPrice(amount: product.price) // Auto-formats based on user's locale & currency
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
❓ FAQ
flutter gen-l10n or simply flutter run (which auto-triggers generation).locale: Locale('ar')). Ensure you use Directionality and start/end instead of left/right.📖 Summary
- ARB files manage translations; flutter gen-l10n auto-generates type-safe Dart code
- MaterialApp configures localizationsDelegates + supportedLocales
- ICU formatting auto-handles locale differences in numbers/currency/dates/plurals
- Riverpod + SharedPreferences manage language and currency preferences
- NumberFormat.simpleCurrency enables multi-currency formatting
📝 Exercises
- Basic (Difficulty ⭐): Configure l10n.yaml and ARB files, implement Chinese/English bilingual switching with at least 10 translated strings.
- Intermediate (Difficulty ⭐⭐): Add Japanese support, implement ICU plural formatting (cart item count), and auto-adapt thousand-separator numbers.
- Challenge (Difficulty ⭐⭐⭐): Implement a complete ShopApp multi-language system: Chinese/English/Japanese trilingual + USD/CNY/JPY tri-currency + date format localization + settings page switching + SharedPreferences persistence.