🦋
Flutterの多言語化
必須というよりは、実験、というか悪戦苦闘
『四次元年表』は英語でつくっている。
でも友人に「使ってみて」と頼むと、英語じゃーん、と言われる。
フランス人にも、「英語じゃーん」と言われた。
そうか、みんなやっぱり、英語がキライか。
データベースは英語運用なので、入力も出力も英語なのはどうしようもないけれど、
せめて、使い方、だの、PrivacyPolicyだのは、多言語化してみるか。
本体をいきなり触るのは怖いので、
ちょうど共同開発中のモバイル版で挑戦。
10分というのはウソではなかった、前半は。
主に参考にしたのはこの記事。
が、多言語化対応④「アプリへの実装」で沼った。
ようするに、アプリの仕様が違うから、どこにどう実装するかで悩んだわけ。
- 記事では設定ページをつくって、リストから選ぶ形になっているが、私はトップ画面にdropdownButtonをつけたい。
- 記事ではRiverpodを使っているが、私はStatefulWidgetで済ませたい。
- dropdownButtonの表示を国旗にしたい。
などなど。
結局それって、多言語化というより、単にアプリの実装の問題か・・・。
結局こうなった
まず、main.dart、全体を統御
import ****
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // 多言語化のため
import 'package:shared_preferences/shared_preferences.dart'; // 言語設定の保存・読み込みのため
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeServerpodClient();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
static void setLocale(BuildContext context, Locale newLocale) {
_MyAppState state = context.findAncestorStateOfType<_MyAppState>()!;
state.setLocale(newLocale);
}
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Locale? _locale;
void initState() {
super.initState();
_loadSavedLanguage();
}
void setLocale(Locale newLocale) {
setState(() {
_locale = newLocale;
});
}
void _loadSavedLanguage() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? savedLanguageCode = prefs.getString('languageCode');
if (savedLanguageCode != null) {
setState(() {
_locale = Locale(savedLanguageCode);
});
}
}
Widget build(BuildContext context) {
return BlocProvider(
t: Timeline(Theme.of(context).platform),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'ChronoMap for Mobile',
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: const Color(0xFF2f4f4f),
brightness: Brightness.light,
textTheme: GoogleFonts.sawarabiMinchoTextTheme(),
),
//ここから多言語化
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: const [Locale('en'), Locale('ja'), Locale('fr')],
locale: _locale,
home: const TabWidget(),
),
);
}
}
つぎは言語選択のButtonとメソッド
fileが長くなるのがイヤで、独立させた。
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../main.dart';
class LanguageDropdownButton extends StatefulWidget {
const LanguageDropdownButton({super.key});
LanguageDropdownButtonState createState() => LanguageDropdownButtonState();
}
class LanguageDropdownButtonState extends State<LanguageDropdownButton> {
String? currentLanguage = 'ja'; // 初期値として'ja' (日本の国旗) を設定
void _changeLanguage(String languageCode) async {
Locale newLocale = Locale(languageCode);
// 言語を保存する
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('languageCode', languageCode);
MyApp.setLocale(context, newLocale); // MyApp内のsetLocaleメソッドを呼び出し
// setStateを呼び出して、UIを更新 一番詰まったのはここ
setState(() {
currentLanguage = languageCode; // 現在の言語を更新
});
print('現在のlanguageCodeは、$languageCode');
}
Widget build(BuildContext context) {
return DropdownButton<String>(
value: currentLanguage,
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
currentLanguage = newValue;
_changeLanguage(newValue); // 選択された言語コードを_changeLanguageに渡す
});
}
},
items: <String>['en', 'ja', 'fr'] // 言語コードのリスト
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value == 'en' ? '🇬🇧' : value == 'ja' ? '🇯🇵' : '🇫🇷'), // 国旗の表示
);
}).toList(),
);
}
}
最後にButtonを実装するページ。
import ***
import 'package:chronomap_mobile/utils/language_button.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';//これ入れ忘れてエラーになった。
import 'info_page.dart';
class IndexPage extends StatefulWidget {
const IndexPage({super.key});
IndexPageState createState() => IndexPageState();
}
class IndexPageState extends State<IndexPage> {
bool _isVisible = false;
void _toggleVisibility() {
setState(() {
_isVisible = !_isVisible;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
const LanguageDropdownButton(), //言語選択ボタンとメソッドはこれ1行
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const InfoPage()));
},
icon: const Icon(Icons.info_outline))
],
),
body: SingleChildScrollView(
child: Center(
child: Column(
children: [
Visibility(
visible: !_isVisible,
child: Column(
children: [
IconButton(
onPressed: _toggleVisibility,
icon: const Icon(
Icons.question_mark_sharp,
color: Colors.green),
),
Padding(
padding: const EdgeInsets.fromLTRB(60, 20, 60, 60),
child: ShadowedContainer(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.asset('assets/images/cover.png')),
)),
),
],
)
),
Visibility(
visible: _isVisible,
child: Column(
children: [
CustomTextContainer(textContent:
AppLocalizations.of(context)!.indexA, //実際に異なる言語で表示されるのはここ
),
CustomTextContainer(textContent:
AppLocalizations.of(context)!.indexB,
),
CustomTextContainer(textContent:
AppLocalizations.of(context)!.indexC,
),
IconButton(
onPressed: _toggleVisibility,
icon: const Icon(
Icons.thumb_up_alt_sharp,
color: Colors.green,
)),
]),
),
],
),
),
),
);
}
}
ちなみに差し替えるテキストのarbファイルは、
日本語バージョンだけつくってから、爺様に丸投げしたので、楽ちんだった。
次回はもうちょっと要領よくできるかな・・・
四次元年表
三次元・四次元表示 四次元年表の使い方
Discussion