Flutter国际化插件dynamic_intl的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter国际化插件dynamic_intl的使用

基于原intlflutter_intl自动生成插件,使原先固定的arb变为可动态化更新的模式。也可以不用flutter_intl,默认的只要格式正确也是一样的。

支持通过版本变更来更新,以及arb增量更新。并且可以动态增加支持的语言并及时应用。

安装

pubspec.yaml文件中添加依赖:

dependencies:
  dynamic_intl: ^0.1.0

导入插件:

import 'package:dynamic_intl/dynamic_intl.dart';

初始化以及配置

继承LanguageSetting来配置对应的设置。首要就是设置对应的资源远程链接,还可以配置缓存的目录、文件名等。

import 'package:dynamic_intl/dynamic_intl.dart';

class TestSetting extends LanguageSetting {
  [@override](/user/override)
  Future<String> languageApi(String locale) async {
    return 'https://jomin-web.web.app/language/intl_$locale.arb';
  }

  [@override](/user/override)
  String get defaultLocale => 'en';

  [@override](/user/override)
  Map<String, LibraryLoader> get deferredLibraries => {
        'zh': () => Future.value(null),
        'en': () => Future.value(null),
      };
}

defaultLocale为默认的语言,最好跟flutter_intl中设置的保持一致。会根据这个默认语言来决定各个文案的占位符,很重要。 deferredLibraries支持的语言列表,格式跟官方intl中自动生成的一样,下边的checkAndDownload可以放在这里,那么在加载对应语言时就可以触发检查下载。

还可以设置在远程资源失效或没有正常获得的时候,备用的本地语言包,格式也是跟官方的一致。

import 'package:lib/generated/intl/messages_zh.dart' as messages_zh;
import 'package:lib/generated/intl/messages_en.dart' as messages_en;

  [@override](/user/override)
  MessageLookupByLibrary? defaultLocaleMessages(String localeName) {
    switch (localeName) {
      case 'zh':
        return messages_zh.messages;
      case 'en':
        return messages_en.messages;
      default:
        return null;
    }
  }

本地语言包示例

flutter_intl的配置:

flutter_intl:
  enabled: true

按照官方的来即可,也可以自己配置路径什么的(example中的配置了language路径下)。

自动生成的文件(generated下的)不需要去手动修改,messages_all已经不会用到。messages_en等,可以作为本地备用资源,在远程资源失败时可做备用。l10n.dart需要记住其中的类,后续需要注册到delegate中,但不要用它自己里面的delegate

l10n目录下的为语言包,即本地的,尽量跟远程同步,修改后及时更新到远程中或随版本更新。格式就简单的json,根据文件名区分语言。

{
  "test": "test title"
}

Arb转译器

目前arb的转译仅支持普通的Intl.message,且最多十个占位符(各规则可参考ApplicationResourceBundleSpecification),如果有需要想支持pluralgender等,或者需要更多占位符,可以继承ArbTraslation,并重写parseFile。相应的写法可自行查看代码。然后设置到LanguageSetting中。

  [@override](/user/override)
  ArbTranslation arbTranslation = NewArbTranslation();

开启arb增量更新

若是觉得语言包体积过于庞大,可以配合服务端做增量更新。即服务端把‘增量’返回,客户端会进行merge并更新(不会移除旧的)。 默认为false,需设置为true

  [@override](/user/override)
  bool get arbMergeEnable => true;

runApp前初始化完成

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LanguageUtil.instance.init(TestSetting());
  runApp(const MyApp());
}

使用多语言,WidgetsFlutterBinding.ensureInitialized()也是需要的。

注册到MaterialApp

locale可自行控制,需要配置的主要是localizationsDelegatessupportedLocales。可继承BaseLocalizationsDynamicDelegate<T>T 即为flutter_intl自动生成的类,默认是S, 具体由你决定)来获取最简单的Delegate。有自己想法的也可以自行编写,并重写supportedLocales和在load中使用initializeDynamicMessages

class AppLocalizationDynamicDelegate
    extends BaseLocalizationsDynamicDelegate<S> {
  const AppLocalizationDynamicDelegate();

  static const AppLocalizationDynamicDelegate delegate =
      AppLocalizationDynamicDelegate();

  [@override](/user/override)
  Future<S> loadS() {
    final instance = S();

    return Future.value(instance);
  }
}

按照上边的写法即可。然后设置到MaterialApp

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',

      /// 当前语言
      locale: MyApp.locale,
      localizationsDelegates: const [
        /// Delegate 注册
        AppLocalizationDynamicDelegate.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],

      /// 支持的语言列表
      supportedLocales:
          AppLocalizationDynamicDelegate.delegate.supportedLocales,
      home: MyHomePage(
          title: 'Flutter Demo Home Page', updateLocale: updateLocale),
    );
  }

自定支持语言列表时,这三个delegate也是需要的,系统功能的多语言支持,记得也加上。

  GlobalMaterialLocalizations.delegate,
  GlobalWidgetsLocalizations.delegate,
  GlobalCupertinoLocalizations.delegate,

它们需要flutter_localizations

  flutter_localizations: # Add this line
    sdk: flutter

触发检查下载

通过Future checkAndDownload(String locale, [String? newVersion])可下载指定语言指定版本的语言包(版本不同则会更新)。下载完成之后可刷新UI来展示最新文案。切换语言也应该调用该方法,已存在语言包则会跳过。

    // 下载语言包,可以加版本, 无版本则每次都更新
    LanguageUtil.instance
        .checkAndDownload(MyApp.locale.languageCode, '1002')
        .then((value) {
      // 因为下载完成前会使用默认文案,下载完成后应刷新下UI
      setState(() {});
    });

若当前下载的不是默认语言,且默认语言包也没有下载保存过,那么会先下载默认语言包

版本管理需要自行设计,这里仅根据传入的版本对比来更新。

增加支持的语言列表

可通过updateLanguageLibrary动态新增支持的语言列表

updateLanguageLibrary([const Locale('it'), const Locale('de')]);

Setting中没有,则可以先检查远程列表然后通过该方法新增,新增后才可以有效切换。

关闭远程仅使用本地

setLocalLanguage设置为true,则会无视远程语言包,直接使用本地的语言包,确认defaultLocaleMessages正常配置。

LanguageUtil.instance.setLocalLanguage(true);

使用文案

即普通的S.of(context).text,觉得需要context太麻烦,可以在MaterialAppbuild的时候注册一个全局context(参考GetX的全局context),一样用。

Text(S.of(context).test)

/// 带占位符
S.of(context).textPlace('123')

切换语言

修改MaterialApplocale,然后检查语言包下载,最后刷新UI即可。需要刷新到MaterialApp这一层,可以看看example中的示例。

  void _changeLocale() async {
    var locale = MyApp.locale.languageCode == 'en'
        ? const Locale('zh')
        : const Locale('en');
    LanguageUtil.instance
        .checkAndDownload(locale.languageCode, '1002')
        .then((_) {
      // 因为下载前会使用默认文案,下载完成后应刷新下UI
      widget.updateLocale(locale);
    });
  }

最后

翻译的文件有@开头的注释消息,也没有问题,只不过默认的ArbTranslation不会处理。LanguageSetting可以在你的管理类里面继承,建议S也重新再套一层再使用,方便维护。检查下载和切换可以根据实际需求来,全部都一起下载也不是不可以,也可以配置在deferredLibraries中随系统切换时自动同步检查下载

example中有个比较简单的可以直接运行的例子,可供参考。有什么问题的话可以直接提issue。


以下是完整的示例代码:

import 'package:dynamic_intl/dynamic_intl.dart';
import 'package:example/language/generated/l10n.dart';
import 'package:example/test_delegate.dart';
import 'package:example/test_setting.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LanguageUtil.instance.init(TestSetting());
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  static var locale = const Locale('zh');

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  void initState() {
    super.initState();
    // 可增加支持的语言列表
    // updateLanguageLibrary([const Locale('it'), const Locale('de')]);

    // 下载语言包,可以加版本, 无版本则每次都更新
    LanguageUtil.instance
        .checkAndDownload(MyApp.locale.languageCode, '1002')
        .then((value) {
      // 因为下载完成前会使用默认文案,下载完成后应刷新下UI
      setState(() {});
    });

    // 可以设置是否使用本地语言包
    // LanguageUtil.instance.setLocalLanguage(true);
    // 检查是否使用本地语言包
    LanguageUtil.instance.checkLocalLanguage();
  }

  void updateLocale(Locale locale) {
    MyApp.locale = locale;
    setState(() {});
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',

      /// 当前语言
      locale: MyApp.locale,
      localizationsDelegates: const [
        /// Delegate 注册
        AppLocalizationDynamicDelegate.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],

      /// 支持的语言列表
      supportedLocales:
          AppLocalizationDynamicDelegate.delegate.supportedLocales,
      home: MyHomePage(
          title: 'Flutter Demo Home Page', updateLocale: updateLocale),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title, required this.updateLocale})
      : super(key: key);

  final String title;
  final Function updateLocale;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _changeLocale() async {
    var locale = MyApp.locale.languageCode == 'en'
        ? const Locale('zh')
        : const Locale('en');
    LanguageUtil.instance
        .checkAndDownload(locale.languageCode, '1002')
        .then((_) {
      // 因为下载前会使用默认文案,下载完成后应刷新下UI
      widget.updateLocale(locale);
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(S.of(context).test),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              S.of(context).test,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _changeLocale,
        tooltip: MyApp.locale.languageCode,
        child: const Icon(Icons.all_inclusive),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

更多关于Flutter国际化插件dynamic_intl的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter国际化插件dynamic_intl的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用dynamic_intl插件来实现国际化的代码案例。dynamic_intl是一个强大的插件,允许你在运行时动态加载和切换语言。

第一步:添加依赖

首先,在你的pubspec.yaml文件中添加dynamic_intl的依赖:

dependencies:
  flutter:
    sdk: flutter
  dynamic_intl: ^0.x.x  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

第二步:配置国际化资源

在你的项目根目录下创建assets/locales文件夹,并在其中创建不同语言的JSON文件。例如:

  • assets/locales/en.json
  • assets/locales/zh.json

en.json内容示例:

{
  "greeting": "Hello",
  "farewell": "Goodbye"
}

zh.json内容示例:

{
  "greeting": "你好",
  "farewell": "再见"
}

第三步:加载和配置dynamic_intl

在你的main.dart文件中,配置dynamic_intl以加载这些资源文件。

import 'package:flutter/material.dart';
import 'package:dynamic_intl/dynamic_intl.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  // 初始化国际化配置
  IntlDelegate delegate = IntlDelegate(
    fallbackLocale: 'en', // 默认语言
    supportedLocales: ['en', 'zh'], // 支持的语言列表
    assetBase: 'assets/locales', // 资源文件的基础路径
    assetName: '{locale}.json', // 资源文件的命名模式
  );

  runApp(
    MaterialApp(
      localizationsDelegates: [
        delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: delegate.supportedLocales,
      locale: delegate.fallbackLocale,
      home: MyApp(),
    ),
  );
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale _currentLocale = Locale('en');

  void changeLanguage(Locale newLocale) {
    setState(() {
      _currentLocale = newLocale;
      // 通知IntlDelegate当前语言已经改变
      IntlProvider.of(context).locale = newLocale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(intl.lookup('greeting')), // 使用intl.lookup来获取国际化字符串
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(intl.lookup('greeting')),
            Text(intl.lookup('farewell')),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => changeLanguage(Locale('zh')),
              child: Text('切换到中文'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () => changeLanguage(Locale('en')),
              child: Text('切换到英文'),
            ),
          ],
        ),
      ),
    );
  }
}

注意事项

  1. IntlProviderIntlProviderdynamic_intl提供的上下文提供者,用于在widget树中提供当前的国际化配置。在这个例子中,我们通过IntlProvider.of(context).locale = newLocale;来更新当前的语言环境。

  2. intl.lookupintl.lookup方法用于获取当前语言环境下的字符串。注意,由于我们没有显式地在代码中创建Intl实例,这里的intl实际上是通过IntlProvider隐式提供的。

  3. Locale和SupportedLocales:确保在IntlDelegate中正确设置了fallbackLocalesupportedLocales

  4. 热重载和热重启:在切换语言后,你可能需要热重启应用(而不是热重载)才能看到语言的变化。

这个示例展示了如何使用dynamic_intl插件在Flutter应用中实现国际化。你可以根据需要扩展这个示例,以支持更多的语言和更复杂的应用场景。

回到顶部