国际化 - 使Flutter应用程序多语言
本文介绍了一种使Flutter应用程序多语言的方法,并允许用户选择除智能手机设置中定义的另一种工作语言之外的其他工作语言。
难度:中级
前言
国际化已经多次解释,可以在这里找到关于这个主题的Flutter官方文档。
因为我想正确地理解它,但是因为我需要扩展它以满足我的应用程序的要求,所以我决定写下面的文章来分享我的经验并给你一些提示。
要求
- 默认情况下,工作语言应该是智能手机中配置的语言(系统设置)
- 如果应用程序不支持默认语言,则“en”将成为默认语言
- 翻译存储在特定于语言的 JSON文件中,作为资产
- 用户可以从支持的语言列表中选择另一种工作语言
- 当用户选择另一种语言时,刷新整个应用程序布局以便以新选择的语言显示
外部依赖
Flutter原生支持本地化(Locale的概念)。该区域设置类是用来标识用户的语言。此类的一个属性定义了languageCode。
要使用本地化包,您需要使用flutter_localizations包。为此,您必须将其作为依赖项添加到pubspec.yaml文件中,如下所示:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
翻译文件
保存pubspec.yaml时,系统将提示您加载/导入依赖项:接受并加载/导入它们。
下一步是创建翻译文件并将其定义为资产。
根据我过去开发网站的经验,我曾经根据命名约定将这些资产存储在名为“ / locale ” 的文件夹中:i18n_ {lang} .json。我将在本文中使用相同的原理。
因此,在与/ lib相同的级别创建一个新文件夹,并将其命名为“ / locale ”。在此文件夹中,创建2个文件,分别命名为:i18n_en.json和i18n_fr.json。
然后,文件夹树应如下所示:
MyApplication
|
+- android
+- build
+- images
+- ios
+- lib
+- locale
|
+- i18n_en.json
+- i18n_fr.json
+- test</code></span></span>
现在,我们需要制作这两个文件,这是资产的一部分。
为此,您必须编辑pubspec.yaml文件并将其添加到assets:部分,如下所示:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter:
assets:
- locale/i18n_en.json
- locale/i18n_fr.json
现在是时候开始实施......履行
让我们从头开始,应用程序的主要初始化:main.dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'translations.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My Application',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: [
const TranslationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('fr', ''),
],
home: new Scaffold(
appBar: new AppBar(
title: new Text('My Title'),
),
body: new Container(
),
),
);
}
}
- 第2行:只需导入flutter本地化包
- 第3行:导入处理翻译的个人库(见后文)
- 第16-20行:是产生本地化值集合的工厂。
- GlobalMaterialLocalizations.delegate为Material Components库提供本地化字符串和其他值。
- GlobalWidgetsLocalizations.delegate为窗口小部件库定义从左到右或从右到左的默认文本方向
- const TranslationsDelegate()指向个人库来处理翻译(见后文)
- 第21-24行:支持的语言列表
在/ lib下,创建一个名为“ translations.dart ” 的新文件。
translations.dart文件的初始内容是:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
class Translations {
Translations(Locale locale) {
this.locale = locale;
_localizedValues = null;
}
Locale locale;
static Map<dynamic, dynamic> _localizedValues;
static Translations of(BuildContext context){
return Localizations.of<Translations>(context, Translations);
}
String text(String key) {
return _localizedValues[key] ?? '** $key not found';
}
static Future<Translations> load(Locale locale) async {
Translations translations = new Translations(locale);
String jsonContent = await rootBundle.loadString("locale/i18n_${locale.languageCode}.json");
_localizedValues = json.decode(jsonContent);
return translations;
}
get currentLanguage => locale.languageCode;
}
class TranslationsDelegate extends LocalizationsDelegate<Translations> {
const TranslationsDelegate();
@override
bool isSupported(Locale locale) => ['en','fr'].contains(locale.languageCode);
@override
Future<Translations> load(Locale locale) => Translations.load(locale);
@override
bool shouldReload(TranslationsDelegate old) => false;
}
- 第33-44行:个人本地化代表的定义。它的作用是实例化我们的个人翻译类(第40行),继续验证支持的语言(第37行)。
- 第6-31行:个人翻译课程的定义。
Translations类的解释
本课程的主要目标是:
- 当由TranslationsDelegate类实例化时,该类接收用于翻译的Locale。在第一次实例化时,作为参数传递给类的构造函数的Locale对应于Settings中定义的Smartphone Locale。
- 构造简单地记住这个区域设置和重置地图是将要举办的翻译在Locale.languageCode
- 在负载时调用的方法TranslationsDelegate类,初始化的新实例翻译类,加载的内容i18n_ {}语言以.json文件和转换JSON到一个地图。然后它返回该类的新实例。
- 方法(BuildContext context)的翻译用于返回指向该类实例的指针。当我们需要获取某个字符串/标签的翻译时会使用这个(见后面)
- text(String key)将用于根据Map的内容返回某个字符串/标签的翻译(请参阅后面的内容)。
- currentLanguage是一个返回语言的getter,目前正在使用。
如何构建i18n_ {language} .json文件?
这些文件是JSON文件,必须遵守JSON语法规则。
这些文件将包含一系列键/值对,如下例所示(i18n_en.json):
{
"app_title": "My Application Title",
"main_title": "My Main Title"
}
警告!:与Dart / Flutter不同的做法不同,JSON语法不接受尾随逗号!
您现在可以使用键/值对填充i18n_en.json和i18n_fr.json文件。
对于此示例,法语中的计数器部分为(i18n_fr.json):
{
"app_title": "Le titre de mon application",
"main_title": "Mon titre principal"
}
如何获得翻译?
要获取某个标签/字符串的翻译,请传递上下文和要翻译的标签,如下所示:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(Translations.of(context).text('main_title')),
...
好了,既然我们知道如何定义语言,翻译和使用翻译,我们怎样才能让用户改变工作语言?如何动态更改工作语言?
为了做到这一点,我们需要在用户选择另一种工作语言时强制刷新整个应用程序布局。因此,我们需要应用程序来处理setState()。
在继续之前,让我们重构代码,让我们创建一个名为application.dart的新文件。
该文件将用于两个目的:
- 应用程序设置库
- 分享全局
typedef void LocaleChangeCallback(Locale locale);
class APPLIC {
// List of supported languages
final List<String> supportedLanguages = ['en','fr'];
// Returns the list of supported Locales
Iterable<Locale> supportedLocales() => supportedLanguages.map<Locale>((lang) => new Locale(lang, ''));
// Function to be invoked when changing the working language
LocaleChangeCallback onLocaleChanged;
///
/// Internals
///
static final APPLIC _applic = new APPLIC._internal();
factory APPLIC(){
return _applic;
}
APPLIC._internal();
}
APPLIC applic = new APPLIC();
这是一个自我初始化的类。每次将其导入源代码时,将返回该类的同一实例。
现在让我们使应用程序“ 可刷新 ”......
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'translations.dart';
import 'application.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
SpecificLocalizationDelegate _localeOverrideDelegate;
@override
void initState(){
super.initState();
_localeOverrideDelegate = new SpecificLocalizationDelegate(null);
///
/// Let's save a pointer to this method, should the user wants to change its language
/// We would then call: applic.onLocaleChanged(new Locale('en',''));
///
applic.onLocaleChanged = onLocaleChange;
}
onLocaleChange(Locale locale){
setState((){
_localeOverrideDelegate = new SpecificLocalizationDelegate(locale);
});
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My Application',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: [
_localeOverrideDelegate,
const TranslationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: applic.supportedLocales(),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>{
@override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: new Text(Translations.of(context).text('main_title')),
),
body: new Container(),
);
}
}
- 第8-13行:要应用的第一个修改是使应用程序有状态。这将允许我们响应刷新请求并调用应用程序SetState()。
- 第19行:我们需要实例化一个新的Localization Delegate,当用户选择另一种工作语言时,它将用于强制Translations类的新实例化。
- 第19行:我们需要实例化一个新的Localization Delegate,当用户选择另一种工作语言时,它将用于强制Translations类的新实例化。
- 第24行:我们在Global APPLIC类中保存一个指向一个方法的指针,该方法将用于通过SetState()强制完整的应用程序刷新
- 第27-31行:当我们更改语言时,应用程序的核心会刷新。每次选择另一种语言时,都会创建一个新的SpecificLocalizationDelegate实例,我们将在之后看到,它将强制刷新Translations类。
- 第42行:我们注册了新的代表
- 第47行:重构。现在我们有一个Global APPLIC类来保存设置,让我们使用它。
- 第62行:让我们借此机会使用翻译库。
现在我们需要对Translations.dart文件应用一些更改...(最终版本)
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'application.dart';
class Translations {
Translations(Locale locale) {
this.locale = locale;
_localizedValues = null;
}
Locale locale;
static Map<dynamic, dynamic> _localizedValues;
static Translations of(BuildContext context){
return Localizations.of<Translations>(context, Translations);
}
String text(String key) {
return _localizedValues[key] ?? '** $key not found';
}
static Future<Translations> load(Locale locale) async {
Translations translations = new Translations(locale);
String jsonContent = await rootBundle.loadString("locale/i18n_${locale.languageCode}.json");
_localizedValues = json.decode(jsonContent);
return translations;
}
get currentLanguage => locale.languageCode;
}
class TranslationsDelegate extends LocalizationsDelegate<Translations> {
const TranslationsDelegate();
@override
bool isSupported(Locale locale) => applic.supportedLanguages.contains(locale.languageCode);
@override
Future<Translations> load(Locale locale) => Translations.load(locale);
@override
bool shouldReload(TranslationsDelegate old) => false;
}
class SpecificLocalizationDelegate extends LocalizationsDelegate<Translations> {
final Locale overriddenLocale;
const SpecificLocalizationDelegate(this.overriddenLocale);
@override
bool isSupported(Locale locale) => overriddenLocale != null;
@override
Future<Translations> load(Locale locale) => Translations.load(overriddenLocale);
@override
bool shouldReload(LocalizationsDelegate<Translations> old) => true;
}
- 第38行:为了不对支持的语言进行硬编码而进行重构
- 第47-60行:Delegate的实现,每次选择新语言时都会强制翻译类的新实例化。
如何申请改变工作语言?
这是蛋糕上的樱桃!
要强制使用其他工作语言,在应用程序的源代码中的任何位置都需要一行代码:
applic.onLocaleChanged(new Locale('fr',''));
感谢Global 应用程序实例,我们可以调用主应用程序方法onLocaleChange(Locale locale),它将创建一个Delegate的新实例,该实例将使用新语言强制Translations类的新实例。
然后将刷新整个应用程序。
结论
我确信其他解决方案存在,也许有更好的代码。
本文的唯一目的是分享一个对我有用的解决方案,我希望也可以帮助其他人。