Flutter初识(三):国际化配置

一、前言:

        所谓国际化,就是中英文切换,甚至更多的语种切换。我在网上搜了一部分,大都是两个语种的切换,而且实时切换还得额外插件。所以我直接在登录页增加一个按钮,点击之后切换。效果如下:

        

二、插件安装

  1. 首先编辑器的插件:打开Plugins,安装Flutter.Intl(需要翻墙,主要是国际化自动配置使用)
  2. 代码插件:打开pubspec.yaml,配置flutter_localization、provider、get、flutter_intl:
    name: my_test_app
    description: "A new Flutter project."
    publish_to: 'none' # Remove this line if you wish to publish to pub.dev
    
    version: 1.0.0+1
    
    environment:
      sdk: ^3.5.1
    
    
    dependencies:
      flutter:
        sdk: flutter
      flutter_localizations:
        sdk: flutter
      provider: ^6.1.2 # 全局的状态管理,用于切换本地化语言
    
    
      # The following adds the Cupertino Icons font to your application.
      # Use with the CupertinoIcons class for iOS style icons.
      cupertino_icons: ^1.0.8
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      get: ^4.6.5
      flutter_svg: ^2.0.10
      shared_preferences: ^2.0.15
    
      intl: ^0.19.0
      intl_utils: ^2.4.0
    
      # The "flutter_lints" package below contains a set of recommended lints to
      # encourage good coding practices. The lint set provided by the package is
      # activated in the `analysis_options.yaml` file located at the root of your
      # package. See that file for information about deactivating specific lint
      # rules and activating additional ones.
      flutter_lints: ^4.0.0
    
    
    flutter:
    
      # The following line ensures that the Material Icons font is
      # included with your application, so that you can use the icons in
      # the material Icons class.
      uses-material-design: true
    
      # To add assets to your application, add an assets section, like this:
      assets:
        - assets/images/  # 这将包括该目录下的所有文件
      
    flutter_intl:
      enabled: true
      class_name: S
      main_locale: en
      output_dir: lib/language/generated # 模板代码生成路径
      arb_dir: lib/language/l10n # .arb 文件存放的路径
    

配置完成之后执行 flutter pub get

三、增加arb文件 

        pubspec.yaml中配置的flutter_intl中有备注arb文件存放路径,直接在 lib 下新建 language 文件夹,然后再新建 l10n 文件夹,在里面建两个文件 intl_en.arb 与 intl_zh.arb

        

// intl_en.arb
{
  "home": "Home",
  "settingLanguage": "Set Language",
  "languageName_en": "English",
  "languageName_zh": "Simplified Chinese",
  "languageName_unknown": "Unknown",

  "login_pageName": "Login Page",
  "login_title": "Test App Demo",
  "login_userName": "Username",
  "login_userName_empty": "Username can't be empty!",
  "login_password": "Password",
  "login_password_empty": "Password can't be empty!",
  "login_btn": "Login"
}
// intl_zh.arb
{
  "home": "首页",
  "settingLanguage": "语言设置",
  "languageName_en": "英语",
  "languageName_zh": "简体中文",
  "languageName_unknown": "未知语种",

  "login_pageName": "登录页",
  "login_title": "App测试",
  "login_userName": "用户名",
  "login_userName_empty": "用户名不能为空!",
  "login_password": "密码",
  "login_password_empty": "密码不能为空!",
  "login_btn": "登录"
}

四、配置模版文件(generated)

        直接使用编辑器的插件实现

执行完之后便生成了如下圈出的文件

五、修改项目入口的Widget(main.dart)

        直接上全部代码:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'language/generated/l10n.dart';
import 'page/login_page.dart';

void main() {
  runApp(
      // 入口文件改造
      ChangeNotifierProvider(
        create: (context) => LocaleProvider(),
        child: const MyApp(),
      ),
  );
}

class LocaleProvider with ChangeNotifier {
  Locale _locale = const Locale('zh', ''); // 设置默认语言
  Locale get locale => _locale;

  void setLocale(Locale locale) {
    if (locale.languageCode.isNotEmpty && locale != _locale) {
      _locale = locale;
      notifyListeners();
    }
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Consumer<LocaleProvider>(
        builder: (context, localeProvider, child) {
          // 原本的入口
          return MaterialApp(
            title: 'Test Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),

            // 所支持的语言
            supportedLocales: S.delegate.supportedLocales,
            locale: localeProvider.locale,
            localizationsDelegates: const [
              S.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            // 设备系统或者浏览器语言环境发生改变的时候的回调
            localeResolutionCallback:
                (Locale? locale, Iterable<Locale> supportedLocales) {
              Locale currentLocale =
              Locale.fromSubtags(languageCode: locale?.languageCode ?? "zh");
              return supportedLocales.contains(currentLocale)
                  ? currentLocale
                  : const Locale.fromSubtags(languageCode: "zh");
            },

            home: const LoginPage(),
          );
        }
    );
  }
}

六、登录页(login_page.dart)手动切换功能

        有之前两章的铺垫,这边我也不过多解释,照样全部代码

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../language/generated/l10n.dart';
import '../main.dart';
import 'home_page.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();
  final FocusNode _usernameFocusNode = FocusNode();
  final FocusNode _passwordFocusNode = FocusNode();
  String? _usernameError;
  String? _passwordError;
  bool _isObscure = true;
  final _borderRadius = 10.0;

  @override
  void initState() {
    super.initState();
    _usernameFocusNode.addListener(() {
      if (!_usernameFocusNode.hasFocus) {
        _validateUsername();
      }
    });

    _passwordFocusNode.addListener(() {
      if (!_passwordFocusNode.hasFocus) {
        _validatePassword();
      }
    });
  }

  // 用户验证
  void _validateUsername() {
    setState(() {
      if (_usernameController.text.isEmpty) {
        _usernameError = S.of(context).login_userName_empty;
      } else {
        _usernameError = null;
      }
    });
  }

  // 密码验证
  void _validatePassword() {
    setState(() {
      if (_passwordController.text.isEmpty) {
        _passwordError = S.of(context).login_password_empty;
      } else {
        _passwordError = null;
      }
    });
  }

  // 登录点击事件
  void _login() async {
    if (_formKey.currentState?.validate() ?? false) {
      _validateUsername();
      _validatePassword();

      final userName = _usernameController.text;
      final password = _passwordController.text;

      if (_usernameError == null && _passwordError == null) {
        // 底部消息弹框
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('$userName 已登录')),
        );

        // 登录数据存储
        final prefs = await SharedPreferences.getInstance();
        prefs.setString('userName', userName);

        // 跳转到下一个页面
        // Navigator.push(
        //   context,
        //   MaterialPageRoute(builder: (context) => HomePage(userName: username)),
        // );

        // 跳转后替换整个导航堆栈,无返回按钮
        Navigator.pushAndRemoveUntil(
          context,
          MaterialPageRoute(
            builder: (context) => HomePage(userName: userName),
          ),
          (route) => false, // 移除前置路由
        );
      }
    }
  }

  // 选择本地语言
  void _showLanguageDialog(BuildContext context) {
    showModalBottomSheet(
      context: context,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(_borderRadius)),
      ),
      builder: (BuildContext context) {
        return Container(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: S.delegate.supportedLocales.map((locale) {
              return ListTile(
                title: Text(_getLanguageName(context, locale)),
                onTap: () {
                  Provider.of<LocaleProvider>(context, listen: false)
                      .setLocale(locale);
                  Navigator.of(context).pop();
                },
              );
            }).toList(),
          ),
        );
      },
    );
  }

  // 弹框选择展示,这个可以继续优化,不然每次增加语种这里也要增加相对应代码!
  String _getLanguageName(BuildContext context, Locale locale) {
    switch (locale.languageCode) {
      case 'en':
        return S.of(context).languageName_en; // 注意这种写法哦,arb文件
      case 'zh':
        return S.of(context).languageName_zh;
      default:
        return S.of(context).languageName_unknown;
    }
  }

  // hex颜色
  Color _hexToColor(String hexString) {
    final buffer = StringBuffer();
    if (hexString.length == 7 || hexString.length == 9) buffer.write('FF');
    buffer.write(hexString.replaceFirst('#', ''));
    return Color(int.parse(buffer.toString(), radix: 16));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          S.of(context).login_pageName, // 注意这种写法,arb文件
          style: const TextStyle(
            fontWeight: FontWeight.normal,
            fontSize: 14,
          )
        ),
        backgroundColor: _hexToColor('#037cfd'), // 标题栏颜色
        foregroundColor: Colors.white,
        centerTitle: true,
      ),
      backgroundColor: _hexToColor('#037cfd'), // 修改外部背景颜色
      body: Padding(
        padding: const EdgeInsets.all(40.0), // 设置内边距
        child: Column(
          // mainAxisAlignment: MainAxisAlignment.center, // 垂直居中
          // crossAxisAlignment: CrossAxisAlignment.center, // 水平居中
          children: [
            Text(
              S.of(context).login_title,
              style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold, color: Colors.white),
            ),
            const SizedBox(height: 20),
            Container(
              padding: const EdgeInsets.all(16.0),
              // width: screenWidth * 0.75, // 宽度设置0.75
              decoration: BoxDecoration(
                color: Colors.white, // 内部颜色
                borderRadius: BorderRadius.circular(8.0),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.1),
                    spreadRadius: 5,
                    blurRadius: 7,
                    offset: const Offset(0, 3),
                  ),
                ],
              ),
              child: Form(
                key: _formKey,
                child: Column(
                  children: [
                    SvgPicture.asset(
                      'assets/images/login.svg'
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: _usernameController,
                      focusNode: _usernameFocusNode, // 失焦事件
                      style: const TextStyle(fontSize: 12, color: Colors.black87), //文字大小、颜色
                      //输入组件
                      decoration: InputDecoration(
                        labelText: S.of(context).login_userName,
                        // hintText: 'Username',
                        errorText: _usernameError,
                        prefixIcon: Icon(Icons.person, color: _hexToColor('#7ba8c0')),
                        fillColor: Colors.grey[80], //背景颜色,
                        filled: true, //必须设置为true,fillColor才有效
                        isCollapsed: true, //相当于高度包裹的意思,必须设置为true
                        contentPadding: const EdgeInsets.symmetric(
                            horizontal: 15, vertical: 15), //内容内边距
                        enabledBorder: OutlineInputBorder(
                          //设置输入框可编辑时的边框样式
                            borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                            borderSide:
                            const BorderSide(color: Colors.white, width: 0) //输入框白色隐藏
                        ),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                          borderSide: BorderSide(color: _hexToColor('#7ba8c0'), width: 2.0),
                        ),
                      ),
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: _passwordController,
                      focusNode: _passwordFocusNode, // 失焦事件
                      obscureText: _isObscure,
                      decoration: InputDecoration(
                        labelText: S.of(context).login_password,
                        errorText: _passwordError,
                        prefixIcon: Icon(Icons.lock, color: _hexToColor('#7ba8c0'),),
                        fillColor: Colors.grey[80], //背景颜色,必须结合filled: true,才有效
                        filled: true, //必须设置为true,fillColor才有效
                        isCollapsed: true, //相当于高度包裹的意思,必须设置为true
                        contentPadding: const EdgeInsets.symmetric(
                            horizontal: 15, vertical: 15), //内容内边距,影响高度
                        enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                            borderSide: const BorderSide(color: Colors.white, width: 0) //输入框白色隐藏
                        ),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                          borderSide: const BorderSide(color: Colors.white, width: 0),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), //输入框圆角
                          borderSide: BorderSide(color: _hexToColor('#7ba8c0'), width: 2.0),
                        ),
                        suffixIcon: IconButton(
                          icon: Icon(
                            !_isObscure ? Icons.visibility : Icons.visibility_off,
                          ),
                          onPressed: () {
                            setState(() {
                              _isObscure = !_isObscure;
                            });
                          },
                        ),
                      ),
                    ),
                    const SizedBox(height: 24),
                    ElevatedButton(
                      onPressed: _login,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: _hexToColor('#037cfd'), //修改按钮背景色
                        foregroundColor: Colors.white, //文本的颜色
                        minimumSize: const Size(double.infinity, 50), // 修改按钮大小
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(_borderRadius), // 修改按钮圆角
                        ),
                      ),
                      child: Text(S.of(context).login_btn),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showLanguageDialog(context),
        tooltip: S.of(context).settingLanguage,
        child: const Icon(Icons.language),
      ),
    );
  }
}

完结撒盐!

七、参考文档

        关于 Flutter 项目的国际化,只需要做对这几步 - 简书 (jianshu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值