🦋

Flutterの多言語化

2024/05/11に公開

必須というよりは、実験、というか悪戦苦闘

『四次元年表』は英語でつくっている。
でも友人に「使ってみて」と頼むと、英語じゃーん、と言われる。
フランス人にも、「英語じゃーん」と言われた。
そうか、みんなやっぱり、英語がキライか。

データベースは英語運用なので、入力も出力も英語なのはどうしようもないけれど、
せめて、使い方、だの、PrivacyPolicyだのは、多言語化してみるか。
本体をいきなり触るのは怖いので、
ちょうど共同開発中のモバイル版で挑戦。

10分というのはウソではなかった、前半は。

主に参考にしたのはこの記事。
https://zenn.dev/amuro/articles/27799da3afc40e
確かに多言語化対応③まではすいすいと進んだ。
が、多言語化対応④「アプリへの実装」で沼った。
ようするに、アプリの仕様が違うから、どこにどう実装するかで悩んだわけ。

  • 記事では設定ページをつくって、リストから選ぶ形になっているが、私はトップ画面に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ファイルは、
日本語バージョンだけつくってから、爺様に丸投げしたので、楽ちんだった。

次回はもうちょっと要領よくできるかな・・・

四次元年表
https://app.laporte.academy
三次元・四次元表示
https://tempo-spaco.web.app
四次元年表の使い方
https://www.youtube.com/@laporte_academy

Flutter大学

Discussion