Flutter 国际化与本地化完全指南

引言

国际化与本地化是构建面向全球用户的应用程序的关键。Flutter 提供了强大的国际化支持,允许开发者轻松实现多语言支持。本文将深入探讨 Flutter 国际化的各种用法和高级技巧。

基础概念回顾

国际化 vs 本地化

  • 国际化(Internationalization):设计和开发应用程序,使其能够轻松适应不同语言和地区。
  • 本地化(Localization):为特定语言和地区定制应用程序。

基本配置

pubspec.yaml 中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.1

flutter:
  generate: true

高级技巧一:配置本地化代理

创建 LocalizationsDelegate

class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) {
    return ['en', 'zh', 'ja'].contains(locale.languageCode);
  }

  @override
  Future<AppLocalizations> load(Locale locale) async {
    AppLocalizations localizations = AppLocalizations(locale);
    await localizations.load();
    return localizations;
  }

  @override
  bool shouldReload(AppLocalizationsDelegate old) => false;
}

在 MaterialApp 中配置

MaterialApp(
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    Locale('en', 'US'),
    Locale('zh', 'CN'),
    Locale('ja', 'JP'),
  ],
  home: MyHomePage(),
)

高级技巧二:创建本地化类

基本本地化类

class AppLocalizations {
  final Locale locale;
  
  AppLocalizations(this.locale);
  
  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
  }
  
  Map<String, String> _localizedStrings = {};
  
  Future<void> load() async {
    String jsonString = await rootBundle.loadString(
      'i18n/${locale.languageCode}.json',
    );
    Map<String, dynamic> jsonMap = json.decode(jsonString);
    _localizedStrings = jsonMap.map((key, value) => MapEntry(key, value.toString()));
  }
  
  String translate(String key) {
    return _localizedStrings[key] ?? key;
  }
}

JSON 本地化文件

// i18n/en.json
{
  "welcome": "Welcome",
  "hello": "Hello",
  "message": "This is a message"
}
// i18n/zh.json
{
  "welcome": "欢迎",
  "hello": "你好",
  "message": "这是一条消息"
}

高级技巧三:使用 Intl 包

添加依赖

dependencies:
  intl: ^0.18.1

创建 ARB 文件

// app_en.arb
{
  "@welcome": {
    "description": "Welcome message"
  },
  "welcome": "Welcome",
  
  "@hello": {
    "description": "Hello message",
    "placeholders": {
      "name": {}
    }
  },
  "hello": "Hello {name}"
}

生成代码

运行命令生成本地化类:

flutter pub run intl_translation:generate_from_arb \
  --output-dir=lib/i18n \
  --no-use-deferred-loading \
  lib/localizations.dart \
  lib/i18n/intl_*.arb

高级技巧四:复数和性别

处理复数

String getPluralMessage(int count) {
  return Intl.plural(
    count,
    zero: 'No items',
    one: 'One item',
    two: 'Two items',
    few: '$count items',
    many: '$count items',
    other: '$count items',
    locale: locale.toString(),
  );
}

处理性别

String getGenderMessage(String gender) {
  return Intl.gender(
    gender,
    male: 'He is a developer',
    female: 'She is a developer',
    other: 'They are a developer',
    locale: locale.toString(),
  );
}

高级技巧五:日期和时间格式化

格式化日期

String formatDate(DateTime date) {
  return DateFormat.yMMMd(locale.toString()).format(date);
}

String formatFullDate(DateTime date) {
  return DateFormat.yMMMEd(locale.toString()).format(date);
}

格式化时间

String formatTime(DateTime time) {
  return DateFormat.jm(locale.toString()).format(time);
}

String formatFullTime(DateTime time) {
  return DateFormat.Hms(locale.toString()).format(time);
}

高级技巧六:数字格式化

格式化数字

String formatNumber(num number) {
  return NumberFormat.decimalPattern(locale.toString()).format(number);
}

格式化货币

String formatCurrency(double amount, String currencyCode) {
  return NumberFormat.currency(
    locale: locale.toString(),
    symbol: currencyCode,
  ).format(amount);
}

实战案例:切换语言

class LanguageSwitcher extends StatefulWidget {
  @override
  _LanguageSwitcherState createState() => _LanguageSwitcherState();
}

class _LanguageSwitcherState extends State<LanguageSwitcher> {
  Locale? _selectedLocale;

  void _changeLanguage(Locale locale) {
    setState(() {
      _selectedLocale = locale;
    });
    
    // 使用全局状态管理更新语言
    MyAppState.of(context).setLocale(locale);
  }

  @override
  Widget build(BuildContext context) {
    return DropdownButton<Locale>(
      value: _selectedLocale,
      onChanged: (Locale? locale) {
        if (locale != null) {
          _changeLanguage(locale);
        }
      },
      items: [
        DropdownMenuItem(
          value: Locale('en', 'US'),
          child: Text('English'),
        ),
        DropdownMenuItem(
          value: Locale('zh', 'CN'),
          child: Text('中文'),
        ),
        DropdownMenuItem(
          value: Locale('ja', 'JP'),
          child: Text('日本語'),
        ),
      ],
    );
  }
}

实战案例:动态语言切换

class MyApp extends StatefulWidget {
  static _MyAppState? of(BuildContext context) =>
      context.findAncestorStateOfType<_MyAppState>();

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale? _locale;

  void setLocale(Locale locale) {
    setState(() {
      _locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      localizationsDelegates: [
        AppLocalizationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en', 'US'),
        Locale('zh', 'CN'),
        Locale('ja', 'JP'),
      ],
      home: HomePage(),
    );
  }
}

实战案例:使用生成的本地化类

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final localizations = AppLocalizations.of(context);
    
    return Scaffold(
      appBar: AppBar(
        title: Text(localizations.translate('welcome')),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(localizations.translate('hello')),
            Text(localizations.translate('message')),
          ],
        ),
      ),
    );
  }
}

常见问题与解决方案

Q1:如何处理 RTL 语言?

A:Flutter 自动支持 RTL:

MaterialApp(
  locale: Locale('ar', 'SA'), // 阿拉伯语
  ...
)

Q2:如何检测设备语言?

A:使用 WidgetsBinding.instance.platformDispatcher.locale

Locale? deviceLocale = WidgetsBinding.instance.platformDispatcher.locale;

Q3:如何支持地区变体?

A:在 supportedLocales 中指定:

supportedLocales: [
  Locale('zh', 'CN'), // 简体中文
  Locale('zh', 'TW'), // 繁体中文
],

最佳实践

1. 使用 ARB 文件管理翻译

# 推荐
lib/
├── i18n/
│   ├── app_en.arb
│   ├── app_zh.arb
│   └── app_ja.arb

2. 集中管理翻译键

// 推荐
class TranslationKeys {
  static const welcome = 'welcome';
  static const hello = 'hello';
  static const message = 'message';
}

// 使用
Text(AppLocalizations.of(context).translate(TranslationKeys.welcome));

3. 提供默认值

// 推荐
String translate(String key) {
  return _localizedStrings[key] ?? key; // 返回键名作为默认值
}

总结

Flutter 的国际化与本地化支持非常完善。通过本文的学习,你应该能够:

  1. 配置本地化代理
  2. 创建和管理翻译文件
  3. 使用 Intl 包处理复数和性别
  4. 格式化日期、时间和数字
  5. 实现动态语言切换

掌握这些技巧,能够帮助你创建面向全球用户的应用程序。

更多推荐