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

1. What You'll Learn


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.

DART
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)
TEXT
> 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

100%
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
TEXT
> 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

YAML
# 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
YAML
# pubspec.yaml
flutter:
  generate: true

▶ Example

TEXT
> 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

JSON
// 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" } }
  }
}
JSON
// 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}"
}
JSON
// 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

TEXT
> 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

DART
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(),
    );
  }
}
TEXT
> 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

TEXT
> 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

DART
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!
TEXT
> 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

TEXT
> 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

DART
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 €
TEXT
> 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

TEXT
> 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

DART
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日
TEXT
> 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

TEXT
> 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

DART
// 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
TEXT
> 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

TEXT
> 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

DART
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()),
    );
  }
}
TEXT
> 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

DART
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
TEXT
> 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

Q How do ARB file changes take effect?
A Run flutter gen-l10n or simply flutter run (which auto-triggers generation).
Q Does hot reload support language switching?
A Partially. ARB content changes require hot restart (R), but Locale switching supports hot reload (r).
Q Should I use intl or manual concatenation for currency formatting?
A Use intl's NumberFormat.simpleCurrency. Manual concatenation can't handle thousand-separator and decimal-point differences across locales.
Q How to format Chinese "wan"?
A ICU standard has no "wan" unit — Chinese numbers still use thousand-separator commas (9,999 not 0.9999). Custom formatting is needed for "wan" display.
Q How to support RTL languages (Arabic)?
A Flutter automatically handles RTL layout (set locale: Locale('ar')). Ensure you use Directionality and start/end instead of left/right.
Q Who maintains translation files?
A Developers maintain the English template ARB; translation teams or AI translate other language ARBs. Recommend using translation management platforms like Lokalise/Crowdin.

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Configure l10n.yaml and ARB files, implement Chinese/English bilingual switching with at least 10 translated strings.
  2. Intermediate (Difficulty ⭐⭐): Add Japanese support, implement ICU plural formatting (cart item count), and auto-adapt thousand-separator numbers.
  3. 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.

← Previous Lesson | Next Lesson →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏