Flutter多语言切换

3,160 阅读4分钟

前言

在过去的开发中往往不会过多考虑多语言切换功能,但是随着功能不断的迭代,用户需求不断更新,当我们要实现多语言切换的需求时,发现需要修改的地方数之不尽,难度系数非常大,往往会考虑成本过高而最终放弃,基于此我们将在项目初期保留多语言切换功能,并且支持多语言扩展,以满足不同的用户需求

方案一:Intl实现多语言切换

安装 Intl 插件

安装完成再重启AndroidStudio后生效

添加项目依赖

flutter_localizations:

sdk: flutter

自动生成目录

点击Tools->Flutter Intl -> Initialze for the Project

此时会自动在pubspec.yaml文件中添加 flutter_intl: enabled: true,并且生成 lib->generated-> intl 和 l10n 目录以及相对应的文件,请勿手动修改

添加语言支持

Tools -> Flutter Intl -> Add locale

选择对应语言,例如zh,en。如果有繁体支持,港台分别为zh_TW/zh_HK.
选定后,会在l10n文件夹下自动创建对应文件,例如 intl_en.arb/intl_zh.arb

项目代码应用

编辑arb文件

intl_en.arb

{
  "test" : "测试"
}

intl_zh.arb

{
  "test" : "Test"
}

中英文切换

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        // 切换中英文
        locale: const Locale('zh', ''),
        // locale: const Locale('en', ''),
        localizationsDelegates: const [
          S.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate
        ],
        supportedLocales: [
          const Locale('zh', ''),
          ...S.delegate.supportedLocales
        ],
        title: 'Flutter Demo',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const MyHomePage(title: 'Flutter Demo Home Page'));
  }

调用字符串

S.of(context).test

或者

S.current.test

以上中英文切换随着系统语言的切换而切换,我们也可以不随着系统切换,而是在我们自己的应用内通过用户手动进行切换

手动多语言切换

/// 字符串资源
class StringLocale extends ChangeNotifier {
  Locale get value => _locale;

  Locale _locale = const Locale('zh', 'HK');

  // 中文(默认)
  static const String languageCN = 'zh_Hk';

  // 英文
  static const String languageEN = 'en_US';

  /// 初始化
  init() {
    String language =
        SPUtil().getString(SpKey.appLanguage, defaultValue: languageCN);
    if (language == languageCN) {
      // 中文
      _locale = const Locale('zh', 'HK');
    } else if (language == languageEN) {
      // 英文
      _locale = const Locale('en', 'US');
    }
  }

  /// 设置语言
  setLocale(Locale locale, String language) {
    _locale = locale;
    SPUtil().put(SpKey.appLanguage, language);
    notifyListeners();
  }

  /// 获取实例
  static S create() {
    return S.current;
  }
}
初始化
@override
Widget build(BuildContext context) {
  // 屏幕适配,设计稿中设备的屏幕尺寸,单位dp (375x667)
  return ScreenUtilInit(
      designSize: Size(375, 667),
      builder: () => Consumer<String591>(
            builder: (context, locale, child) {
              locale.init();
              return MaterialApp(
                localizationsDelegates: const [
                  S.delegate,
                  GlobalMaterialLocalizations.delegate,
                  GlobalCupertinoLocalizations.delegate,
                  GlobalWidgetsLocalizations.delegate
                ],
                locale: locale.value,
                supportedLocales: const [
                  Locale('zh', 'HK'),
                  Locale('en', 'US'),
                ],
                home: SplashPage(),
              );
            },
          ));
}

切换成中文

Provider.of<StringLocale>(context, listen: false)
    .setLocale(const Locale('zh', 'HK'), StringLocale.languageCN);

切换成英文

Provider.of<StringLocale>(context, listen: false) 
    .setLocale(const Locale('en', 'US'), StringLocale.languageEN);

调用的方式

String591.create().

方案二:自定义实现多语言切换

代码实现

字符串基类

/// 字符串基类
abstract class BaseString {
  String get home;
  String get list;
  String get more;
}

字符串中文类

/// 字符串-中文类
class CNString extends BaseString {

  @override
  String get home => '首页';

  @override
  String get list => '列表';

  @override
  String get more => '更多';

}

字符串英文类

/// 字符串英文类
class ENString extends BaseString {
  @override
  String get home => 'Home';

  @override
  String get list => 'List';

  @override
  String get more => 'More';
}

代码使用

多语言切换

实现方案一:

/// 字符串资源
class StringModel {
  // 中文
  static const String languageCN = 'CN';

  // 英文
  static const String languageEN = 'EN';

  // 中文字符串对象
  static final BaseString cnString = CNString();

  // 英文字符串对象
  static final BaseString enString = ENString();

  /// 切换语言(cn:中文版; en:英文版)
  static changeLanguage(String language) {
    if (null != language && language.isNotEmpty) {
      SpUtil().put(SpKey.SP_APP_LANGUAGE, language);
    }
  }

  /// 获取语言类型(cn:中文版; en:英文版)
  static String getLanguageType() {
    return SpUtil().getString(SpKey.SP_APP_LANGUAGE, defaultValue: languageCN);
  }

  /// 工厂模式,考虑多语言扩展性,字符串复用性等等
  static BaseString create() {
    String language =
        SpUtil().getString(SpKey.SP_APP_LANGUAGE, defaultValue: languageCN);
    if (language == languageEN) {
      // 英文版
      return enString;
    } else {
      // 中文版
      return cnString;
    }
  }

  /// 返回一个字符串(弃用,该方法不利于后续扩展和维护)
  static getString({String cn = '', String en = ''}) {
    String language =
        SpUtil().getString(SpKey.SP_APP_LANGUAGE, defaultValue: languageCN);
    if (language == languageEN) {
      // 英文版
      return en;
    } else {
      // 中文版
      return cn;
    }
  }
}

实现方案二:

  # 状态管理
  # https://pub.dev/packages/provider/install
  provider: ^6.0.1
  // 程序入口
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => StringModel()),
      ],
      child: const MyApp(),
    ),
  );
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

/// 字符串资源
class StringModel extends ChangeNotifier {
  // 中文
  static const String languageCN = 'CN';

  // 英文
  static const String languageEN = 'EN';

  late BaseString _baseString;

  // BaseString get baseString => _baseString;

  StringModel() {
    String language =
        SpUtil().getString(SpKey.appLanguage, defaultValue: languageCN);
    if (language == languageEN) {
      // 英文版
      _baseString = ENString();
    } else {
      // 中文版
      _baseString = CNString();
    }
  }

  /// 多语言切换
  void updateLanguage(String language) {
    if (language == languageEN) {
      // 英文版
      _baseString = ENString();
    } else {
      // 中文版
      _baseString = CNString();
    }
    SpUtil().put(SpKey.appLanguage, language);
    notifyListeners();
  }

  /// 创造字符串对象
  /// 通过Provider.of<T>(context)方法,可以在以Provider为根节点的子树中获取到T的对象
  static BaseString create(BuildContext context, {bool? listen}) {
    if (null == listen) {
      return Provider.of<StringModel>(context)._baseString;
    } else {
      return Provider.of<StringModel>(context, listen: listen)._baseString;
    }
  }
}

字符串调用

// 第一种实现,重启app后生效:
StringModel.updateLanguage(language)  // 切换语言
StringModel.create().home  // 调用方法案例 

// 第二种实现,直接生效:
Provider.of<StringModel>(context, listen: false).updateLanguage(language) // 切换语言
StringModel.create(context).home  // 调用方法案例

总结

不论是使用第三方插件intl还是自定义实现,都能够满足多语言切换功能。它们将不同的语言文件进行区分隔离,有利于多语言切换的扩展和维护,并且在一定程度上提高了代码的可阅读性。