概要
Flutterでの標準的な多言語化対応はflutter_localizationsを使用するものです。
しかし、flutter_localizationsはOSデフォルトの言語を扱う分にはよいのですが、アプリ内で言語の切り替えを行うのには少し微妙というか、アプリ内で言語を切り替えた後の再描画(画面の更新)がやりづらく感じました。
⇛追記: そこまでやりづらくもなかった。flutter_localizationsによる多言語化対応(アプリ内で言語切り替え)
調べたところ、flutter_i18nというパッケージでは再描画の仕組みが元々用意されているようだったので、これを使ってみることにします。
await FlutterI18n.refresh(context, newLocale);
これだけで対応するロケールでの再描画が行えます。
なお、flutter_i18nは現在のバージョンが0.30.0ということもあり、まだ枯れていない部分もあると思われるのでそこは注意してください。
使いかた
大まかな手順は
- 翻訳リソースの作成
- pubspec.yamlの修正
- MaterialAppにlocalizationsDelegatesを設定
- 翻訳部分の修正
- 言語切替時に再描画
という感じになります。
翻訳リソースの作成
翻訳リソースをウェブから取ったりもできるようですが、ここでは単にローカルのファイルから取ります。
プロジェクト配下に以下のようにファイルを作成します。
assets/flutter_i18n/ja.json
assets/flutter_i18n/en.json
ディレクトリ名はこれがデフォルトですが "i18n" や "locales" などでもよいようです。
ファイル形式はJSON、YAML、TOML、XMLから選べるようですが、ここではJSONにします。
ファイル名にも国コードを含むかなど命名規則がありますが、ここでは単純に言語コードだけ設定しています。
翻訳ファイルの詳しい書き方についてはドキュメントに記載がなかったのですが、少なくともJSONで次のような書き方はできるようです。
{
"hello": "こんにちは",
"helloName": "こんにちは{name}さん"
}
上記の設定に対して、次のような呼び出しができます。
I18nText("hello")
I18nText("helloName", translationParams: {"name": yourName})
複数形(Plural)の設定などもっと複雑なこともできるようです。
pubspec.yamlの修正
pubspec.yamlに、依存性とアセットを追加します。
dependencies:
flutter_i18n: ^0.30.0
flutter:
assets:
- assets/flutter_i18n/en.json
- assets/flutter_i18n/ja.json
修正後は
flutter pub get
を忘れず行ってください。(IDEが勝手にやってくれるとは思いますが。)
MaterialAppにlocalizationsDelegatesを設定
実際にはflutter_localizationsの場合と同様にlocaleResolutionCallback
やsupportedLocales
の設定も必要になると思いますが、一旦最小構成のみ示します。
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
:
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
FlutterI18nDelegate(
// 国コードも使用する場合は useCountryCode: true を指定
translationLoader: FileTranslationLoader(),
// ここはサンプルそのまま
missingTranslationHandler: (key, locale) {
print("--- Missing Key: $key, languageCode: ${locale?.languageCode}");
},
),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
// RTLサポートが必要な場合(RTLとは?Right-to-Leftの話?)
// builder: FlutterI18n.rootAppBuilder(),
:
);
}
翻訳部分の修正
翻訳対象のText
を以下のように置き換えていきます。
I18nText("hello")
I18nText("helloName", translationParams: {"name": yourName})
Text
でなくString
が必要な場合は次のようにして取れます。
FlutterI18n.translate(context, "hello")
言語切替時に再描画
最後に、本題の再描画です。
// 英語に切り替えた際のイベント
onPressed: () async {
await FlutterI18n.refresh(context, Locale("en", ""));
setState(() {});
}
実際には切り替えた言語を設定に保存する処理などもあると思いますが、基本的にはこれだけです。
サンプルアプリ
参考にサンプルアプリの全体を貼っておきます。
{
"languageSettings": "言語設定",
"hello": "こんにちは"
}
{
"languageSettings": "Language Settings",
"hello": "Hello"
}
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
FlutterI18nDelegate(
translationLoader: FileTranslationLoader(),
missingTranslationHandler: (key, locale) {
print("--- Missing Key: $key, languageCode: ${locale?.languageCode}");
},
),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
title: 'i18n sample',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HelloPage(),
);
}
}
class HelloPage extends StatefulWidget {
const HelloPage({Key? key}) : super(key: key);
@override
_HelloPageState createState() => _HelloPageState();
}
class _HelloPageState extends State<HelloPage> {
SimpleDialogOption _changeLocaleDialogOption(
BuildContext context,
String text,
String languageCode) {
return SimpleDialogOption(
child: Text(text),
onPressed: () async {
await FlutterI18n.refresh(context, Locale(languageCode, ''));
setState(() {});
Navigator.pop(context);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title : I18nText('hello')
),
body: Center(
child: TextButton(
child: I18nText('languageSettings'),
onPressed: () => showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: I18nText('languageSettings'),
children: <Widget>[
_changeLocaleDialogOption(context, 'English', 'en'),
_changeLocaleDialogOption(context, '日本語', 'ja'),
],
);
},
),
),
),
);
}
}