Flutterの国際化対応について第2弾
ネットで調べると国際化対応についていくつかの言語で簡単なサンプルが見つけられる。しかしほとんどの場合、localeが単純なen, ja, zhのようなものばかりである。
アジア系の我々にとって興味のあるzh_Hansのような中国語の繁体字、簡体字などを区別するサンプルは見つからない。これに対する対策を見出したので公開することにした。
参考にしたのは本家サイトのDocs内にあるInternationalizing Flutter appsとその部分をまとめたGoogle Flutter Mobile Development Quick Start Guide, Prajyot Mainkar and Salvatore Giordano, 2019, Packt Publishing. Chapter 5の後半である。
基本的な国際化対応
Flutterの国際化対応の最もFundamentalな方策はソースコードに記載する方法だと思う。それをLocale毎に振り分けるのである。
特に中国語の区分について記す。以下の二つのパターンが一般的だろう。
- 繁体、簡体の分類
- 香港の繁体、台湾の繁体、その他の簡体の分類
今回はこの2パターンと日本語、英語を区別して、優先度の最上位のものを表示するアプリを考える。
Flutterの導入は完了しているものとし、IDEにはAndroid Studio 3.4を使う。こちらも設定が終わっているものとする。主にPreference(Settings)での作業になるだろう。
新規プロジェクト作製
ちゃんと設定が終わっていれば、Android Studio 3.4で[File]-[New]-[New Flutter Project]のような選択肢が出ているはずだ。
Flutterアプリケーションを選んで適宜設定していく。KotlinやSwiftサポートはチェックする必要はない。
そして完成したプロジェクトのlibフォルダの中にmain.dartファイルがある。これを改変していくのが一番基本的な方法だろう。
ソースコードのベースは上記のInternationalizing Flutter appsから取る。こちらの文章と対比させながら解説していく。具体的にはソースコードを見てもらった方がいいと思うので、Dart言語ソースコードを記す。
import 'dart:async';
import 'package:flutter/foundation.dart' show SynchronousFuture;
import 'package:flutter_localizations/flutter_localizations.dart';
上記3つをインポートして使うことになる。
Using the bundled Localizations Delegatesの改変
class Demo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateTitle: (BuildContext context) => DemoLocalizations.of(context).title,
localizationsDelegates: [
const DemoLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale.fromSubtags(languageCode: 'en'),// English
const Locale.fromSubtags(languageCode: 'ja'),// Japanese
const Locale.fromSubtags(languageCode: 'zh'),// Chinese
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // simplified Chinese 'zh_Hans'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // traditional Chinese 'zh_Hant'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // traditional Chinese 'zh_Hant_HK'
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),// traditional Chinese 'zh_Hant_TW'
],
home: DemoApp(),
);
}
}
最後の二つはscriptCodeも含め3つ全部指定してやらないと動作しないので要注意。
Defining a class for the app’s localized resourcesの改変
String get title {
if (locale.languageCode == 'zh') {// Chinese
if (locale.scriptCode == 'Hans'){// Chinese Simplified in General
return _localizedValues[locale.scriptCode]['title'];
} else if (['HK', 'TW'].contains(locale.countryCode)) {// Chinese Traditional
if (locale.countryCode == 'TW') {// Chinese Traditional in Taiwan
return _localizedValues[locale.countryCode]['title'];
} else if (locale.countryCode == 'HK') {// Chinese Traditional in Hong Kong
return _localizedValues[locale.countryCode]['title'];
}
} else {// Chinese Traditional in Others
return _localizedValues[locale.scriptCode]['title'];
}
} else if (['en', 'ja'].contains(locale.languageCode)){// English or Japanese
return _localizedValues[locale.languageCode]['title'];
} else {
return _localizedValues[locale.languageCode]['title'];//Others (not Supported)
}
}
この部分はもっと上手く書く必要がある。ただ分類として
まず中国語であるという前提で、
1.簡体字の中国語
2.繁体字の中国語
その中で地域が台湾、香港の順に調べ
それ以外ならscriptCode、つまり繁体字の中国語
と言うことになっている。
あとは英語と日本語の場合。他の言語はこの処理に来るか来ないか分からないので、一応そのまま返却する。(isSupportedの処理がどのタイミングで行われるか不明なため。)
Specifying the app’s supported Locales parameterの改変
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(DemoLocalizations.of(context).title),
),
);
}
}
中央にtitleで指定した文字列を配置する。
An alternative class for the app’s localized resourcesの改変
class DemoLocalizations {
DemoLocalizations(this.locale);
final Locale locale;
static DemoLocalizations of(BuildContext context) {
return Localizations.of(context, DemoLocalizations);
}
static Map> _localizedValues = {
'en': {
'title': 'Locale in English',
},
'ja': {
'title': '日本語ロケール',
},
'zh': {
'title': '简体中文区域设置',
},
'HK': {
'title': '傳統中文地區設定HK',
},
'TW': {
'title': '傳統中文地區設定TW',
},
'Hans': {
'title': '简体中文区域设置',
},
'Hant': {
'title': '傳統中文地區設定',
},
};
最初のString (Key)がlanguageCode, scriptCode, countryCodeになっていて次のMapでリソース名とリソース(String)がマップされている。
KeyにanguageCode, scriptCode, countryCodeを指定し、利用時にlocale.scriptCode == ‘Hans’のような形で選別するのである。(ちょっと分かりにくい。)
対応すべき文字列が増えれば、この部分で各地域、言語でリソースを追加すればよい。大規模なアプリでなければ集約できるのでまあ、悪くはないと思う。
class DemoLocalizationsDelegate extends LocalizationsDelegate {
const DemoLocalizationsDelegate();
@override
bool isSupported(Locale locale) => (['en', 'ja'].contains(locale.languageCode)) ||
((['zh'].contains(locale.languageCode)) && (['HK','TW'].contains(locale.countryCode)) ||
((['zh'].contains(locale.languageCode)) && (['Hans','Hant'].contains(locale.scriptCode))) ||
((['zh'].contains(locale.languageCode)) && !(['HK','TW'].contains(locale.countryCode)))
);
@override
Future load(Locale locale) {
// Returning a SynchronousFuture here because an async "load" operation
// isn't needed to produce an instance of DemoLocalizations.
return SynchronousFuture(DemoLocalizations(locale));
}
@override
bool shouldReload(DemoLocalizationsDelegate old) => false;
}
isSupportedは重要で、これでTrueを返すように書かれているものにのみ動作して、他の言語については優先順位が上位であってもスルーする。
具体的には画像のような場合ドイツ語を無視して台湾の繁体字の中国語を出力する。
iOSの場合は別項目、Flutterの国際化対応について(修正版:7/13/19)に書いたとおり、追加対策が必要。
ただし、台湾、香港の区別はできないように見えた。