Flutter 国际化与本地化实战指南

一、国际化概述

国际化(Internationalization,简称i18n)是指应用程序能够支持多种语言和地区的能力。本地化(Localization,简称l10n)则是为特定地区或语言调整应用程序的过程。

Flutter 提供了完整的国际化支持,主要通过以下包实现:

  • flutter_localizations - 官方本地化支持
  • intl - 国际化工具库

二、配置国际化环境

2.1 添加依赖

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

dev_dependencies:
  flutter_test:
    sdk: flutter

2.2 配置 MaterialApp

import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter i18n Demo',
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en', 'US'),  // 英语
        Locale('zh', 'CN'),  // 中文简体
        Locale('ja', 'JP'),  // 日语
        Locale('ko', 'KR'),  // 韩语
      ],
      home: const HomePage(),
    );
  }
}

三、创建国际化资源文件

3.1 创建 arb 文件

创建 lib/l10n 目录,并添加以下文件:

app_en.arb - 英语资源

{
  "@@locale": "en",
  "helloWorld": "Hello World",
  "welcome": "Welcome to our app",
  "greeting": "Hello {name}",
  "counter": "{count, plural, =0{No items} =1{One item} other{{count} items}}",
  "date": "{date, date, short}",
  "time": "{time, time, short}"
}

app_zh.arb - 中文资源

{
  "@@locale": "zh",
  "helloWorld": "你好世界",
  "welcome": "欢迎使用我们的应用",
  "greeting": "你好 {name}",
  "counter": "{count, plural, =0{没有项目} =1{一个项目} other{{count} 个项目}}",
  "date": "{date, date, short}",
  "time": "{time, time, short}"
}

app_ja.arb - 日语资源

{
  "@@locale": "ja",
  "helloWorld": "こんにちは世界",
  "welcome": "アプリへようこそ",
  "greeting": "こんにちは {name}",
  "counter": "{count, plural, =0{項目なし} =1{1つの項目} other{{count} 項目}}",
  "date": "{date, date, short}",
  "time": "{time, time, short}"
}

3.2 配置 pubspec.yaml

flutter:
  generate: true
  assets:
    - lib/l10n/

3.3 生成代码

运行以下命令生成国际化代码:

flutter pub get
flutter pub run intl_utils:generate

四、使用国际化字符串

4.1 基础用法

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    
    return Scaffold(
      appBar: AppBar(title: Text(l10n.helloWorld)),
      body: Center(
        child: Column(
          children: [
            Text(l10n.welcome),
            Text(l10n.greeting(name: 'John')),
          ],
        ),
      ),
    );
  }
}

4.2 复数处理

Text(l10n.counter(count: 0));  // 没有项目
Text(l10n.counter(count: 1));  // 一个项目
Text(l10n.counter(count: 5));  // 5 个项目

4.3 日期和时间格式化

Text(l10n.date(date: DateTime.now()));
Text(l10n.time(time: DateTime.now()));

五、动态切换语言

5.1 创建语言状态管理

class LocaleProvider extends ChangeNotifier {
  Locale _locale = const Locale('en');

  Locale get locale => _locale;

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

5.2 使用 Provider

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => LocaleProvider(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<LocaleProvider>(
      builder: (context, provider, child) {
        return MaterialApp(
          locale: provider.locale,
          localizationsDelegates: const [
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: const [
            Locale('en', 'US'),
            Locale('zh', 'CN'),
            Locale('ja', 'JP'),
          ],
          home: const HomePage(),
        );
      },
    );
  }
}

5.3 创建语言选择器

class LanguageSelector extends StatelessWidget {
  const LanguageSelector({super.key});

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

六、处理 RTL 语言

6.1 配置 RTL 支持

MaterialApp(
  ...
  supportedLocales: const [
    Locale('ar', 'SA'),  // 阿拉伯语
    Locale('he', 'IL'),  // 希伯来语
  ],
);

6.2 使用 Directionality Widget

Directionality(
  textDirection: TextDirection.rtl,
  child: Text('مرحبا بالعالم'),  // 阿拉伯语
);

6.3 自适应布局

Row(
  textDirection: Directionality.of(context),
  children: [
    Text(l10n.name),
    Text(l10n.value),
  ],
);

七、日期时间格式化

7.1 基础格式化

import 'package:intl/intl.dart';

final now = DateTime.now();

// 格式化日期
print(DateFormat.yMd().format(now));        // 12/31/2023
print(DateFormat('yyyy-MM-dd').format(now)); // 2023-12-31

// 格式化时间
print(DateFormat.jm().format(now));         // 11:59 PM
print(DateFormat.Hms().format(now));        // 23:59:59

// 完整日期时间
print(DateFormat.yMMMd().format(now));      // Dec 31, 2023
print(DateFormat.yMMMEd().format(now));     // Sun, Dec 31, 2023

7.2 本地化日期时间

// 根据当前语言环境格式化
print(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(now));

八、数字格式化

8.1 基础用法

import 'package:intl/intl.dart';

final number = 1234567.89;

print(NumberFormat().format(number));        // 1,234,567.89
print(NumberFormat.currency().format(number)); // $1,234,567.89
print(NumberFormat.percent().format(0.75));  // 75%

8.2 本地化数字

final format = NumberFormat.decimalPattern(Localizations.localeOf(context).languageCode);
print(format.format(number));

九、复数规则

9.1 基础复数

{
  "items": "{count, plural, =0{no items} =1{one item} other{{count} items}}"
}

9.2 复杂复数规则

{
  "apples": "{count, plural, zero{no apples} one{one apple} two{two apples} few{few apples} many{many apples} other{{count} apples}}"
}

十、处理地区差异

10.1 地区特定格式

// 美国格式
print(DateFormat.yMd('en_US').format(now));  // 12/31/2023

// 欧洲格式
print(DateFormat.yMd('de_DE').format(now));  // 31.12.2023

// 中国格式
print(DateFormat.yMd('zh_CN').format(now));  // 2023/12/31

10.2 货币格式

// 美元
print(NumberFormat.currency(locale: 'en_US').format(100));  // $100.00

// 欧元
print(NumberFormat.currency(locale: 'de_DE').format(100));  // 100,00 €

// 人民币
print(NumberFormat.currency(locale: 'zh_CN', symbol: '¥').format(100));  // ¥100.00

十一、测试国际化

11.1 单元测试

test('English localization', () {
  final l10n = AppLocalizationsEn();
  expect(l10n.helloWorld, 'Hello World');
  expect(l10n.greeting(name: 'Test'), 'Hello Test');
});

test('Chinese localization', () {
  final l10n = AppLocalizationsZh();
  expect(l10n.helloWorld, '你好世界');
  expect(l10n.greeting(name: '测试'), '你好 测试');
});

11.2 Widget 测试

testWidgets('Localized text displays correctly', (tester) async {
  await tester.pumpWidget(
    MaterialApp(
      locale: const Locale('zh'),
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      home: const HomePage(),
    ),
  );
  
  expect(find.text('你好世界'), findsOneWidget);
});

十二、最佳实践

12.1 组织翻译文件

lib/
├── l10n/
│   ├── app_en.arb
│   ├── app_zh.arb
│   ├── app_ja.arb
│   └── app_ko.arb
├── main.dart
└── ...

12.2 使用一致的命名规范

{
  "homeTitle": "首页",
  "homeSubtitle": "欢迎回来",
  "btnSubmit": "提交",
  "btnCancel": "取消",
  "errorNetwork": "网络错误",
  "successSave": "保存成功"
}

12.3 避免硬编码字符串

// 错误
Text('Hello World');

// 正确
Text(l10n.helloWorld);

12.4 使用翻译管理工具

  • Lokalise - 专业翻译管理平台
  • Transifex - 开源翻译管理
  • Crowdin - 企业级翻译管理

十三、实战案例:多语言应用

class InternationalizedApp extends StatelessWidget {
  const InternationalizedApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<LocaleProvider>(
      builder: (context, provider, child) {
        return MaterialApp(
          locale: provider.locale,
          localizationsDelegates: const [
            AppLocalizations.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
          ],
          supportedLocales: const [
            Locale('en', 'US'),
            Locale('zh', 'CN'),
            Locale('ja', 'JP'),
            Locale('ko', 'KR'),
          ],
          home: const HomePage(),
        );
      },
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    
    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.homeTitle),
        actions: const [LanguageSelector()],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(l10n.welcome),
            const SizedBox(height: 20),
            Text(l10n.greeting(name: '用户')),
            const SizedBox(height: 20),
            Text(l10n.counter(count: 5)),
            const SizedBox(height: 20),
            Text(l10n.date(date: DateTime.now())),
          ],
        ),
      ),
    );
  }
}

十四、总结

Flutter 国际化涉及多个方面:

  1. 配置环境 - 添加依赖和配置 MaterialApp
  2. 创建资源文件 - 使用 ARB 格式管理翻译
  3. 生成代码 - 使用 intl 工具生成本地化类
  4. 使用翻译 - 在 Widget 中引用本地化字符串
  5. 动态切换 - 实现语言切换功能
  6. RTL 支持 - 处理从右到左的语言
  7. 格式化 - 日期、时间、数字的本地化格式化

通过合理的国际化设计,可以让应用程序更好地服务于全球用户。

更多推荐