本文介绍了一种使Flutter应用程序多语言的方法,并允许用户选择除智能手机设置中定义的另一种工作语言之外的其他工作语言。
难度:中级
国际化已经多次解释,可以在这里找到关于这个主题的Flutter官方文档。
因为我想正确地理解它,但是因为我需要扩展它以满足我的应用程序的要求,所以我决定写下面的文章来分享我的经验并给你一些提示。
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_zh.json
translations_delegate.dart
+- test
现在,我们需要制作这两个文件,这是资产的一部分。
为此,您必须编辑pubspec.yaml文件并将其添加到assets:部分,如下所示:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter:
assets:
- lib/locale/i18n_en.json
- lib/locale/i18n_zh.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('zh', ''),
],
home: new Scaffold(
appBar: new AppBar(
title: new Text('My Title'),
),
body: new Container(
),
),
);
}
}
在/ 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 _localizedValues;
static Translations of(BuildContext context){
return Localizations.of(context, Translations);
}
String text(String key) {
return _localizedValues[key] ?? '** $key not found';
}
static Future 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 {
const TranslationsDelegate();
@override
bool isSupported(Locale locale) => ['en','fr'].contains(locale.languageCode);
@override
Future load(Locale locale) => Translations.load(locale);
@override
bool shouldReload(TranslationsDelegate old) => false;
}
本课程的主要目标是:
这些文件是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 supportedLanguages = ['en','fr'];
// Returns the list of supported Locales
Iterable supportedLocales() => supportedLanguages.map((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 {
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{
@override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: new Text(Translations.of(context).text('main_title')),
),
body: new Container(),
);
}
}
现在我们需要对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 _localizedValues;
static Translations of(BuildContext context){
return Localizations.of(context, Translations);
}
String text(String key) {
return _localizedValues[key] ?? '** $key not found';
}
static Future 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 {
const TranslationsDelegate();
@override
bool isSupported(Locale locale) => applic.supportedLanguages.contains(locale.languageCode);
@override
Future load(Locale locale) => Translations.load(locale);
@override
bool shouldReload(TranslationsDelegate old) => false;
}
class SpecificLocalizationDelegate extends LocalizationsDelegate {
final Locale overriddenLocale;
const SpecificLocalizationDelegate(this.overriddenLocale);
@override
bool isSupported(Locale locale) => overriddenLocale != null;
@override
Future load(Locale locale) => Translations.load(overriddenLocale);
@override
bool shouldReload(LocalizationsDelegate old) => true;
}
这是蛋糕上的樱桃!
要强制使用其他工作语言,在应用程序的源代码中的任何位置都需要一行代码:
applic.onLocaleChanged(new Locale('fr',''));
感谢Global 应用程序实例,我们可以调用主应用程序方法onLocaleChange(Locale locale),它将创建一个Delegate的新实例,该实例将使用新语言强制Translations类的新实例。
然后将刷新整个应用程序。
我确信其他解决方案存在,也许有更好的代码。
本文的唯一目的是分享一个对我有用的解决方案,我希望也可以帮助其他人。