Flutter实现本地化多语言


内置组件本地化

  1. 添加依赖

    Flutter默认组件的本地化只需要添加

    flutter_localizations:
      sdk: flutter

    使用命令行添加就是

    flutter pub add flutter_localizations --sdk=flutter
  2. 修改入口组件 main.dart

    import 'package:flutter_localizations/flutter_localizations.dart';
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // English
        Locale('zh'), // Chinese
      ],
      home: MyHomePage(),
    );

    这样Flutter内置组件所显示的语言就能和系统语言保持一致,如果想要手动修改,只需要添加参数

    return const MaterialApp(
      ...
      locale: Locale("zh"),
    );

    如果想要修改指定的组件,需要使用到 Localizations.override

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Localizations.override(
                context: context,
                locale: const Locale('zh'),
                child: Builder(
                  builder: (context) {
                    return CalendarDatePicker(
                      initialDate: DateTime.now(),
                      firstDate: DateTime(1900),
                      lastDate: DateTime(2100),
                      onDateChanged: (value) {},
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      );
    }

自定义本地化

根据官方文档,可以利用 flutter_localozationsintl 来实现。但是如果根据官方文档,那可不是一般的麻烦,而是相当麻烦,操作步骤包括但不限于

  • 修改 pubsepc.yaml

  • 添加 l10n.yaml

  • 添加 .arb 文件

当然,这一切也不能说是很麻烦,毕竟只要第一次改好,后续只用修改 .arb 文件就能自动运行,但根据这个流程生成的多语言本地化,我觉得有几个问题:

  1. 自动生成的 .dart 文件是保存在 {项目目录}/.dart_tool/flutter_gen/gen_l10n 目录下的,这会导致项目默认的代码没有多语言相关,只有一些不知所谓的 .arb 文件,毕竟与多语言相关的代码都是自动生成的

  2. 也是最重要的一点, 我为什么要在默认语言下重复定义一个不能带空格,只允许定义符合 dart方法名称 的关键字以供调用,比如一个简单的字符串

    Text("Hello World")

    我必须在默认语言定义一个

    // app_es.arb
    "helloWorld": "Hello World"

    然后在其它语言定义

    // app_zh.arb
    "helloWorld": "你好 世界"

    最后再修改默认的调用

    Text(AppLocations.of(context).helloWorld)

    我为什么不能直接使用原有的字符串呢,这样就不用再为默认的语言添加额外的翻译,比如

    Text(AppLocations.of(context).tr("Hello World"))

所以,我仔细研究了一下,大抵不用如此麻烦

自定义本地化(非代码自动生成)

  1. 首先添加 l10n.dart

    import 'dart:async';
    
    import 'package:flutter/foundation.dart';
    import 'package:flutter/widgets.dart';
    import 'package:flutter_localizations/flutter_localizations.dart';
    import 'package:intl/intl.dart' as intl;
    
    import 'l10n_en.dart';
    import 'l10n_zh.dart';
    
    abstract class L10n {
      L10n(String locale)
      : localeName = intl.Intl.canonicalizedLocale(locale.toString());
    
      final String localeName;
    
      static L10n? of(BuildContext context) {
        return Localizations.of<L10n>(context, L10n);
      }
    
      static const LocalizationsDelegate<L10n> delegate = _L10nDelegate();
    
      static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
      <LocalizationsDelegate<dynamic>>[
        delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ];
    
      static const List<Locale> supportedLocales = <Locale>[
        Locale('en'),
        Locale('zh')
      ];
    
      String tr(String key);
    }
    
    class _L10nDelegate extends LocalizationsDelegate<L10n> {
      const _L10nDelegate();
    
      @override
      Future<L10n> load(Locale locale) {
        return SynchronousFuture<L10n>(lookupL10n(locale));
      }
    
      @override
      bool isSupported(Locale locale) =>
      <String>['en', 'zh'].contains(locale.languageCode);
    
      @override
      bool shouldReload(_L10nDelegate old) => false;
    }
    
    L10n lookupL10n(Locale locale) {
      // Lookup logic when only language code is specified.
      switch (locale.languageCode) {
        case 'en':
        return L10nEn();
        case 'zh':
        return L10nZh();
      }
    
      throw FlutterError(
        'L10n.delegate failed to load unsupported locale "$locale". This is likely '
        'an issue with the localizations generation tool. Please file an issue '
        'on GitHub with a reproducible sample app and the gen-l10n configuration '
        'that was used.');
    }

    上述代码其实是由根据官方文档自动生成的 .dart 文件转化而来

  2. 添加默认语言的翻译

    // l10n_en.dart
    import 'l10n.dart';
    
    class L10nEn extends L10n {
      L10nEn([String locale = 'en']) : super(locale);
    
      @override
      String tr(String key) {
        return translations[key] ?? key;
      }
    }
    
    const translations = {};

    对的,你没有看错, translations 甚至可以是空的,这样就不用重复定义默认语言的翻译

  3. 添加其它语言的翻译

    // l10n_zh.dart
    import 'l10n.dart';
    
    class L10nZh extends L10n {
      L10nZh([String locale = 'zh']) : super(locale);
    
      @override
      String tr(String key) {
        return translations[key] ?? key;
      }
    }
    
    const translations = {
      "Settings": "设置",
      "Basic Settings": "基础设置",
      "Theme": "主题",
      "Language": "语言",
      "About": "关于",
      "Help": "帮助",
    };
  4. 修改 main.dart 入口组件

    import 'app/l10n/l10n.dart';
    
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: L10n.localizationsDelegates,
      supportedLocales: L10n.supportedLocales,
      home: MyHomePage(),
    );

    这样就能使用自定义的翻译了

本地化多语言的使用

最常用的是在 Text 组件里

Text(L10n.of(context)!.tr("Hello World"))

但是所有的字符都要添加 L10n.of(context)! 未免有些麻烦,所以我增加了自定义扩展

  • BuildContext

    extension L10nContext on BuildContext {
      String tr(String key) {
        final t = L10n.of(this);
        if (t == null) {
          return key;
        }
        return t.tr(key);
      }
    }

    使用方式

    Text(context.tr("Hello World"))
  • String

    extension L10nString on String {
      String tr(BuildContext context) {
        final t = L10n.of(context);
        if (t == null) {
          return this;
        }
        return t.tr(this);
      }
    }

    使用方式

    Text("Hello World".tr(context))
  • Text

    extension L10nText on Text {
      Text tr(BuildContext context) {
        final t = L10n.of(context);
        if (t == null || data == null) {
          return this;
        }
        return Text(t.tr(data ?? ''),
          key: key,
          style: style,
          strutStyle: strutStyle,
          textAlign: textAlign,
          textDirection: textDirection,
          locale: locale,
          softWrap: softWrap,
          overflow: overflow,
          textScaler: textScaler,
          maxLines: maxLines,
          semanticsLabel: semanticsLabel,
          textWidthBasis: textWidthBasis);
      }
    }

    使用方式

    Text("Hello World").tr(context)

    如此,就能最大限度的较少对原有代码的侵略性修改

优化多语言选择

我这里选用的是 riverpod 进行状态管理,首先定义一个本地语言的状态

final localeProvider = StateProvider<String>((ref) {
  return "zh";
});

接着修改 main.dart

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final localeCode = ref.watch(localeProvider);

    return MaterialApp(
      locale: L10n.delegate.isSupported(localeCode) ? Locale(localeCode) : null,
      ...
    );
  }
}

这样就能很方便地修改应用的显示语言

ref.read(localeProvider.notifier).state = "zh";

参考文档

作者: honmaple
链接: https://honmaple.me/articles/2024/10/fluttershi-xian-ben-di-hua-duo-yu-yan.html
版权: CC BY-NC-SA 4.0 知识共享署名-非商业性使用-相同方式共享4.0国际许可协议
wechat
alipay

加载评论