|
1 | | -# flutter_intl |
| 1 | +# 以$t形式使用flutter多语言 |
| 2 | + |
| 3 | +## 前言 |
| 4 | +关于flutter国际化的具体介绍,大家可以移步[国际化Flutter App](https://flutterchina.club/tutorials/internationalization/) |
| 5 | + |
| 6 | +本文主要介绍在flutter如何使用多语言并且如何像web使用i18n一样使用多语言($t)和实现语言切换,这对前端开发人员会更加友好。 |
| 7 | + |
| 8 | +## 项目地址 |
| 9 | +[flutter-ui](https://github.com/efoxTeam/flutter-ui), 这是包含flutter组件介绍的开源项目,欢迎star |
| 10 | + |
| 11 | +[flutter_intl](https://github.com/efoxTeam/flutter-demo/tree/master/flutter_intl) 本教程的项目源码,欢迎star |
| 12 | + |
| 13 | +## 效果 |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +## 如何使用 |
| 19 | +### 添加依赖 |
| 20 | +* 在pubspec.yaml中引入依赖 |
| 21 | +``` dart |
| 22 | +dependencies: |
| 23 | + flutter_localizations: |
| 24 | + sdk: flutter |
| 25 | +``` |
| 26 | +* 执行 |
| 27 | +``` dart |
| 28 | +flutter packages get |
| 29 | +``` |
| 30 | +### 新建文件locale |
| 31 | +``` dart |
| 32 | +locale |
| 33 | + |-en.json |
| 34 | + |-zh.json |
| 35 | +``` |
| 36 | +多语言的文件 |
| 37 | +* en.json |
| 38 | +``` dart |
| 39 | +{ |
| 40 | + "title_page": "i18n", |
| 41 | + "title_appbar": "i18n", |
| 42 | + "content": { |
| 43 | + "currentLanguage": "The current language is English", |
| 44 | + "zh": "zh", |
| 45 | + "en": "en" |
| 46 | + } |
| 47 | +} |
| 48 | +``` |
| 49 | +* zh.json |
| 50 | +``` dart |
| 51 | +{ |
| 52 | + "title_page": "国际化例子", |
| 53 | + "title_appbar": "国际化例子", |
| 54 | + "content": { |
| 55 | + "currentLanguage": "当前语言是中文", |
| 56 | + "zh": "中文", |
| 57 | + "en": "英文" |
| 58 | + } |
| 59 | +} |
| 60 | +``` |
| 61 | +### lib下新建lang |
| 62 | +``` dart |
| 63 | +lang |
| 64 | + |- config.dart |
| 65 | + |- index.dart |
| 66 | +``` |
| 67 | +* config.dart |
| 68 | +``` dart |
| 69 | +import 'package:flutter/material.dart'; |
| 70 | + |
| 71 | +class ConfigLanguage { |
| 72 | + static List<Locale> supportedLocales = [ |
| 73 | + Locale('zh', 'CH'), |
| 74 | + Locale('en', 'US') |
| 75 | + ]; |
| 76 | + |
| 77 | + static Map<String, dynamic> supportLanguage = { |
| 78 | + "zh": {"code": "zh", "country_code": "CH"}, |
| 79 | + "en": {"code": "en", "country_code": "US"}, |
| 80 | + }; |
| 81 | + |
| 82 | + static dynamic defaultLanguage = { |
| 83 | + "code": "zh", |
| 84 | + "country_code": "CH" |
| 85 | + }; |
| 86 | +} |
| 87 | +``` |
| 88 | +config.dart作用主要是将配置性的内容统一到一个文件中 |
| 89 | +* index.dart |
| 90 | +``` dart |
| 91 | +import 'package:flutter/material.dart'; |
| 92 | +import 'dart:convert'; |
| 93 | +import 'package:flutter/services.dart' show rootBundle; |
| 94 | +import 'package:flutter/foundation.dart' show SynchronousFuture; |
| 95 | +import 'package:flutter_intl/lang/config.dart' as I18NConfig; |
| 96 | +class AppLocalizations { |
| 97 | + Locale _locale; |
| 98 | + static Map<String, dynamic> jsonLanguage; // 语言包 |
| 99 | + static AppLocalizations _inst; // inst |
| 100 | + |
| 101 | + AppLocalizations(this._locale); |
| 102 | + |
| 103 | + // 初始化 localizations |
| 104 | + static Future<AppLocalizations> init(Locale locale) async { |
| 105 | + _inst = AppLocalizations(locale); |
| 106 | + await getLanguageJson(); |
| 107 | + return _inst; |
| 108 | + } |
| 109 | + |
| 110 | + // 获取语言包 |
| 111 | + static Future getLanguageJson() async { |
| 112 | + Locale _tmpLocale = _inst._locale; |
| 113 | + print('获取语言包的语种; ${_tmpLocale.languageCode}'); |
| 114 | + String jsonLang; |
| 115 | + try { |
| 116 | + jsonLang = await rootBundle.loadString('locale/${_tmpLocale.languageCode}.json'); |
| 117 | + } catch (e) { |
| 118 | + print('出错了'); |
| 119 | + _inst._locale = Locale(I18NConfig.ConfigLanguage.defaultLanguage['code']); |
| 120 | + jsonLang = await rootBundle.loadString('locale/${I18NConfig.ConfigLanguage.defaultLanguage['code']}.json'); |
| 121 | + } |
| 122 | + jsonLanguage = json.decode(jsonLang); |
| 123 | + print("当前语言: ${_inst._locale}"); |
| 124 | + print("数据: $jsonLanguage"); |
| 125 | + } |
| 126 | + |
| 127 | +// $t 封装,目的是为了可以使用$t来获取多语言数据 |
| 128 | + static String $t(String key) { |
| 129 | + var _array = key.split('.'); |
| 130 | + var _dict = jsonLanguage; |
| 131 | + var retValue = ''; |
| 132 | + try { |
| 133 | + _array.forEach((item) { |
| 134 | + if(_dict[item].runtimeType == Null) { |
| 135 | + retValue = key; |
| 136 | + return; |
| 137 | + } |
| 138 | + if (_dict[item].runtimeType != String) { |
| 139 | + _dict = _dict[item]; |
| 140 | + } else { |
| 141 | + retValue = _dict[item]; |
| 142 | + } |
| 143 | + }); |
| 144 | + retValue = retValue.isEmpty ? _dict : retValue; |
| 145 | + } catch (e) { |
| 146 | + print('i18n exception'); |
| 147 | + print(e); |
| 148 | + retValue = key; |
| 149 | + } |
| 150 | + |
| 151 | + return retValue ?? ''; |
| 152 | + } |
| 153 | + |
| 154 | +} |
| 155 | + |
| 156 | + |
| 157 | +// 实现LocalizationsDelegate协议,用于初始化Localizations类 |
| 158 | +class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> { |
| 159 | + final Locale locale; |
| 160 | + AppLocalizationsDelegate([this.locale]); |
| 161 | + |
| 162 | + @override |
| 163 | + bool isSupported(Locale locale) { |
| 164 | + return I18NConfig.ConfigLanguage.supportLanguage.keys |
| 165 | + .toList() |
| 166 | + .contains(locale.languageCode); |
| 167 | + } |
| 168 | + |
| 169 | +// 在这里初始化Localizations类 |
| 170 | + @override |
| 171 | + Future<AppLocalizations> load(Locale _locale) async { |
| 172 | + print('将要加载的语言: $_locale'); |
| 173 | + return await AppLocalizations.init(_locale); |
| 174 | + // return SynchronousFuture<AppLocalizations>( |
| 175 | + // AppLocalizations(_locale) |
| 176 | + // ); |
| 177 | + } |
| 178 | + |
| 179 | + @override |
| 180 | + bool shouldReload(LocalizationsDelegate<AppLocalizations> old) { |
| 181 | + // flase时,不执行上述重写函数 |
| 182 | + return false; |
| 183 | + } |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +详情请看代码注释~~~~~~~~~ |
| 188 | + |
| 189 | +概况一下就是 |
| 190 | + |
| 191 | +实现一个LocalizationsDelegate协议和实现一个Localizations类,然后引入到main.dart中的MaterialApp中 |
| 192 | + |
| 193 | +### 处理main.dart文件 |
| 194 | +``` dart |
| 195 | +import 'package:flutter/material.dart'; |
| 196 | +import 'package:flutter_localizations/flutter_localizations.dart'; |
| 197 | +import 'package:flutter_intl/lang/index.dart' |
| 198 | + show AppLocalizations, AppLocalizationsDelegate; |
| 199 | +import 'package:flutter_intl/lang/config.dart' show ConfigLanguage; |
| 200 | + |
| 201 | + |
| 202 | +void main () => runApp(MainApp()); |
| 203 | +GlobalKey<_ChangeLocalizationsState> changeLocalizationsStateKey = new GlobalKey<_ChangeLocalizationsState>(); |
| 204 | +class MainApp extends StatefulWidget { |
| 205 | + @override |
| 206 | + _MainAppState createState() => _MainAppState(); |
| 207 | +} |
| 208 | + |
| 209 | +class _MainAppState extends State<MainApp> { |
| 210 | + // 定义全局 语言代理 |
| 211 | + AppLocalizationsDelegate _delegate; |
| 212 | + |
| 213 | + @override |
| 214 | + void initState() { |
| 215 | + // TODO: implement initState |
| 216 | + _delegate = AppLocalizationsDelegate(); |
| 217 | + super.initState(); |
| 218 | + } |
| 219 | + @override |
| 220 | + Widget build(BuildContext context) { |
| 221 | + return MaterialApp( |
| 222 | + // locale: Locale('zh', 'CH'), |
| 223 | + localeResolutionCallback: (deviceLocale, supportedLocal) { |
| 224 | + print('当前设备语种 deviceLocale: $deviceLocale, 支持语种 supportedLocale: $supportedLocal}'); |
| 225 | + // 判断传入语言是否支持 |
| 226 | + Locale _locale = supportedLocal.contains(deviceLocale) ? deviceLocale : Locale('zh', 'CN'); |
| 227 | + return _locale; |
| 228 | + }, |
| 229 | + onGenerateTitle: (context) { |
| 230 | + // 设置多语言代理 |
| 231 | + // AppLocalizations.setProxy(setState, _delegate); |
| 232 | + return AppLocalizations.$t('title_page'); |
| 233 | + }, |
| 234 | + // localizationsDelegates 列表中的元素时生成本地化集合的工厂 |
| 235 | + localizationsDelegates: [ |
| 236 | + GlobalMaterialLocalizations.delegate, // 为Material Components库提供本地化的字符串和其他值 |
| 237 | + GlobalWidgetsLocalizations.delegate, // 定义widget默认的文本方向,从左往右或从右往左 |
| 238 | + _delegate |
| 239 | + ], |
| 240 | + supportedLocales: ConfigLanguage.supportedLocales, |
| 241 | + initialRoute: '/', |
| 242 | + routes: { |
| 243 | + '/': (context) => |
| 244 | + // Home() |
| 245 | + Builder(builder: (context) { |
| 246 | + return ChangeLocalizations( |
| 247 | + key: changeLocalizationsStateKey, |
| 248 | + child: Home() |
| 249 | + ); |
| 250 | + }) |
| 251 | + } |
| 252 | + ); |
| 253 | + } |
| 254 | +} |
| 255 | + |
| 256 | +class Home extends StatelessWidget { |
| 257 | + @override |
| 258 | + Widget build(BuildContext context) { |
| 259 | + Locale locale = Localizations.localeOf(context); |
| 260 | + return Scaffold( |
| 261 | + appBar: AppBar(title: Text('${AppLocalizations.$t('title_appbar')}'),), |
| 262 | + body: ListView( |
| 263 | + children: <Widget>[ |
| 264 | + Container( |
| 265 | + margin: EdgeInsets.only(top: 60), |
| 266 | + alignment: Alignment.center, |
| 267 | + child: Text('${locale.languageCode} ${locale.toString()}'), |
| 268 | + ), |
| 269 | + Container( |
| 270 | + alignment: Alignment.center, |
| 271 | + child: Text('${AppLocalizations.$t('content.currentLanguage')}'), |
| 272 | + ), |
| 273 | + Wrap( |
| 274 | + spacing: 8.0, |
| 275 | + alignment: WrapAlignment.center, |
| 276 | + children: <Widget>[ |
| 277 | + ActionChip( |
| 278 | + backgroundColor: Theme.of(context).primaryColor, |
| 279 | + onPressed: () { |
| 280 | + changeLocalizationsStateKey.currentState.changeLocale(Locale('en', 'US')); |
| 281 | + }, |
| 282 | + label: Text('${AppLocalizations.$t('content.en')}'), |
| 283 | + ), |
| 284 | + ActionChip( |
| 285 | + backgroundColor: Theme.of(context).primaryColor, |
| 286 | + onPressed: () { |
| 287 | + changeLocalizationsStateKey.currentState.changeLocale(Locale('zh', 'CH')); |
| 288 | + }, |
| 289 | + label: Text('${AppLocalizations.$t('content.zh')}'), |
| 290 | + ) |
| 291 | + ], |
| 292 | + ) |
| 293 | + ], |
| 294 | + ) |
| 295 | + ); |
| 296 | + } |
| 297 | +} |
| 298 | + |
| 299 | +class ChangeLocalizations extends StatefulWidget { |
| 300 | + final Widget child; |
| 301 | + ChangeLocalizations({Key key, this.child}):super(key: key); |
| 302 | + @override |
| 303 | + _ChangeLocalizationsState createState() => _ChangeLocalizationsState(); |
| 304 | +} |
| 305 | + |
| 306 | +class _ChangeLocalizationsState extends State<ChangeLocalizations> { |
| 307 | + Locale _locale; |
| 308 | + @override |
| 309 | + void initState() { |
| 310 | + super.initState(); |
| 311 | + } |
| 312 | + @override |
| 313 | + void didChangeDependencies() async { |
| 314 | + super.didChangeDependencies(); |
| 315 | + // 获取当前设备的语言 |
| 316 | + _locale = Localizations.localeOf(context); |
| 317 | + print('设备语言: $_locale'); |
| 318 | + } |
| 319 | + changeLocale(Locale locale) { |
| 320 | + setState(() { |
| 321 | + _locale = locale; |
| 322 | + }); |
| 323 | + } |
| 324 | + @override |
| 325 | + Widget build(BuildContext context) { |
| 326 | + return Localizations.override( |
| 327 | + context: context, |
| 328 | + locale: _locale, |
| 329 | + child: widget.child, |
| 330 | + ); |
| 331 | + } |
| 332 | +} |
| 333 | +``` |
| 334 | +* 在MaterialApp中指定localizationsDelegate和supportedLocales |
| 335 | + * localeResolutionCallback:在应用获取用户设置的语言区域时回调,可以根据需要return对应的Locale |
| 336 | + * onGenerateTitle: 返回对应的多语言应用标题 |
| 337 | + * localizationsDelegates:localizationsDelegates 列表中的元素时生成本地化集合的工厂 |
| 338 | + * supportedLocales: app支持的语言种类 |
| 339 | +* Home类就是显示的类 |
| 340 | + * Localizations.localeOf(context).languageCode可以获取当前app的语言类型 |
| 341 | + * AppLocalizations.$t('content.currentLanguage')像web一样玩耍多语言内容 |
| 342 | + |
| 343 | +到这里就已经可以愉快的玩耍多语言了,用户设置不同的语言就会加载不同的语言包 |
| 344 | + |
| 345 | +下面实现在app内的语言切换 |
| 346 | + |
| 347 | +* ChangeLocalizations类使用Localizations的override方法,代码如上 |
| 348 | + * 使用GlobalKey调用ChangeLocalizations的内部方法,GlobalKey<_ChangeLocalizationsState> changeLocalizationsStateKey = new GlobalKey<_ChangeLocalizationsState>(); 我们也可以将GlobalKey放入到provide中,这样可以实现多个页面进行changeLocalizationsStateKey的访问 |
| 349 | + * 修改语言调用changeLocale方法,changeLocalizationsStateKey.currentState.changeLocale(Locale('en', 'US')); |
| 350 | + |
| 351 | + |
| 352 | +## 最后 |
| 353 | +欢迎更多学习flutter的小伙伴加入QQ群 Flutter UI: 798874340 |
| 354 | + |
| 355 | +敬请关注我们正在开发的: [efoxTeam/futter-ui](https://github.com/efoxTeam/flutter-ui) |
| 356 | + |
| 357 | +[作者](https://github.com/DIVINER-onlys) |
| 358 | + |
| 359 | + |
| 360 | + |
| 361 | + |
| 362 | + |
| 363 | + |
| 364 | + |
| 365 | + |
| 366 | + |
2 | 367 |
|
3 | | -A new Flutter project. |
4 | 368 |
|
5 | | -## Getting Started |
6 | 369 |
|
7 | | -This project is a starting point for a Flutter application. |
8 | 370 |
|
9 | | -A few resources to get you started if this is your first Flutter project: |
10 | 371 |
|
11 | | -- [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) |
12 | | -- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) |
13 | 372 |
|
14 | | -For help getting started with Flutter, view our |
15 | | -[online documentation](https://flutter.io/docs), which offers tutorials, |
16 | | -samples, guidance on mobile development, and a full API reference. |
|
0 commit comments