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

引言

国际化(Internationalization)和本地化(Localization)是构建全球化应用的关键。Flutter 提供了强大的国际化支持,让应用能够轻松支持多种语言和地区。本文将深入探讨 Flutter 国际化的各种技巧和最佳实践。

基础配置

添加依赖

dependencies:
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.0

配置 MaterialApp

import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Internationalization',
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', 'US'),
        const Locale('zh', 'CN'),
        const Locale('ja', 'JP'),
      ],
      home: HomePage(),
    );
  }
}

高级技巧一:创建本地化资源

使用 intl 包

// l10n/messages_all.dart
import 'dart:async';
import 'package:intl/intl.dart';
import 'package:intl/message_lookup_by_library.dart';

final MessageLookup messages = MessageLookup();

class MessageLookup extends MessageLookupByLibrary {
  @override
  String? lookupMessage(
    String? messageText,
    String? locale,
    String? name,
    List<Object>? args,
    String? meaning,
    Map<String, String>? examples,
  ) {
    return messageText;
  }
}

创建本地化文件

// l10n/intl_en.arb
{
  "helloWorld": "Hello World!",
  "welcome": "Welcome to Flutter",
  "greeting": "Hello {name}!",
  "count": "{count, plural, zero{No items} one{1 item} other{{count} items}}"
}

// l10n/intl_zh.arb
{
  "helloWorld": "你好世界!",
  "welcome": "欢迎来到 Flutter",
  "greeting": "你好 {name}!",
  "count": "{count, plural, zero{没有项目} one{1个项目} other{{count}个项目}}"
}

高级技巧二:使用 flutter_localizations

创建自定义 LocalizationsDelegate

class AppLocalizations {
  final Locale locale;
  
  AppLocalizations(this.locale);
  
  static AppLocalizations? of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }
  
  static const LocalizationsDelegate<AppLocalizations> delegate =
      _AppLocalizationsDelegate();
  
  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;
  }
}

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: [
    AppLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('en', 'US'),
    const Locale('zh', 'CN'),
    const Locale('ja', 'JP'),
  ],
)

高级技巧三:动态切换语言

创建语言切换服务

class LanguageService extends ChangeNotifier {
  Locale _currentLocale = const Locale('en', 'US');
  
  Locale get currentLocale => _currentLocale;
  
  void setLocale(Locale locale) {
    _currentLocale = locale;
    notifyListeners();
  }
}

使用 GetX 切换语言

// 在 GetX 中配置
GetMaterialApp(
  locale: Get.deviceLocale,
  fallbackLocale: const Locale('en', 'US'),
  translations: MyTranslations(),
)

// 切换语言
Get.updateLocale(const Locale('zh', 'CN'));

高级技巧四:格式化日期和数字

日期格式化

// 使用 intl 包
final date = DateTime.now();
final formatter = DateFormat('yyyy-MM-dd HH:mm:ss', 'zh_CN');
print(formatter.format(date)); // 2024-01-15 14:30:00

数字格式化

final number = 1234567.89;
final formatter = NumberFormat('#,##0.00', 'zh_CN');
print(formatter.format(number)); // 1,234,567.89

货币格式化

final amount = 1234.56;
final formatter = NumberFormat.currency(
  locale: 'zh_CN',
  symbol: '¥',
);
print(formatter.format(amount)); // ¥1,234.56

高级技巧五:RTL 支持

配置 RTL

MaterialApp(
  locale: const Locale('ar'),
  supportedLocales: [
    const Locale('ar'),
  ],
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
)

检测文本方向

final direction = Directionality.of(context);
if (direction == TextDirection.rtl) {
  // RTL 布局
} else {
  // LTR 布局
}

实战案例:多语言应用

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final localizations = AppLocalizations.of(context);
    
    return Scaffold(
      appBar: AppBar(
        title: Text(localizations?.translate('welcome') ?? 'Welcome'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(localizations?.translate('helloWorld') ?? 'Hello World'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 切换语言
                final locale = Localizations.localeOf(context);
                if (locale.languageCode == 'en') {
                  Get.updateLocale(const Locale('zh', 'CN'));
                } else {
                  Get.updateLocale(const Locale('en', 'US'));
                }
              },
              child: Text(localizations?.translate('changeLanguage') ?? 'Change Language'),
            ),
          ],
        ),
      ),
    );
  }
}

实战案例:动态语言切换

class LanguageSelector extends StatelessWidget {
  final List<Map<String, String>> languages = [
    {'code': 'en', 'name': 'English'},
    {'code': 'zh', 'name': '中文'},
    {'code': 'ja', 'name': '日本語'},
  ];
  
  @override
  Widget build(BuildContext context) {
    return DropdownButton<String>(
      value: Get.locale?.languageCode,
      items: languages.map((lang) {
        return DropdownMenuItem(
          value: lang['code'],
          child: Text(lang['name']!),
        );
      }).toList(),
      onChanged: (value) {
        if (value != null) {
          Get.updateLocale(Locale(value));
        }
      },
    );
  }
}

常见问题与解决方案

Q1:如何支持地区变体?

A:使用完整的 locale 代码:

supportedLocales: [
  const Locale('en', 'US'),
  const Locale('en', 'GB'),
  const Locale('zh', 'CN'),
  const Locale('zh', 'TW'),
],

Q2:如何处理复数形式?

A:使用 ICU 消息格式:

{
  "items": "{count, plural, zero{No items} one{1 item} other{{count} items}}"
}

Q3:如何获取设备语言?

A:使用 WidgetsBinding.instance.window.locale

final deviceLocale = WidgetsBinding.instance.window.locale;

最佳实践

1. 集中管理翻译资源

// 错误:硬编码字符串
Text('Hello World')

// 正确:使用本地化
Text(AppLocalizations.of(context)?.translate('helloWorld') ?? 'Hello World')

2. 使用 ARB 文件

{
  "helloWorld": "Hello World!",
  "@helloWorld": {
    "description": "The classic hello world message"
  }
}

3. 支持 RTL 语言

Directionality(
  textDirection: TextDirection.rtl,
  child: Text('مرحبا بالعالم'),
)

总结

国际化是构建全球化应用的重要环节。通过本文的学习,你应该能够:

  1. 配置基本的国际化支持
  2. 创建多语言资源文件
  3. 实现动态语言切换
  4. 格式化日期、数字和货币
  5. 支持 RTL 语言

掌握这些技巧,能够帮助你构建更加友好的全球化应用。

更多推荐