LoginSignup
6
1

More than 1 year has passed since last update.

[Flutter] flutter_localizationsによる多言語化対応(アプリ内で言語切り替え)

Last updated at Posted at 2021-10-04

概要

スクリーンショット 2021-10-04 18.14.03.png

(見た目一緒なので画像は使いまわし)
ちょっと前にflutter_i18nというパッケージでの多言語対応を紹介したのですが、やっぱりもうちょっと標準的な方法でどうにかならないかと思ったので、flutter_localizationsベースでアプリ内で言語を切り替える方法を調べました。
状態管理は以下の3パターンでそれぞれやってみます。

flutter_localizationsの基本的な使い方については、各所に記事があるのでそれを参照してください。
以下は AppLocalizations クラスが既に生成されている前提で話を進めます。

arbの内容はこんな感じです。

app_en.arb
{
  "@@locale":"en",
  "languageSettings": "Language Settings",
  "hello": "Hello"
}
app_ja.arb
{
  "@@locale":"ja",
  "languageSettings": "言語設定",
  "hello": "こんにちは"
}

StatefulWidget版

とりあえずサンプルアプリ全体。
localeResolutionCallbackまわりは割愛してます。

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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> {
  Locale locale = AppLocalizations.supportedLocales.first;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      locale: locale,
      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: () {
        context.findAncestorStateOfType<_MyAppState>()!
          ..locale = Locale(languageCode, '')
          ..setState(() {});
        Navigator.pop(context);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text(AppLocalizations.of(context)!.hello)
      ),
      body: Center(
        child: TextButton(
          child: Text(AppLocalizations.of(context)!.languageSettings),
          onPressed: () =>
              showDialog(
                context: context,
                builder: (context) {
                  return SimpleDialog(
                    title: Text(AppLocalizations.of(context)!.languageSettings),
                    children: <Widget>[
                        _changeLocaleDialogOption(context, 'English', 'en'),
                        _changeLocaleDialogOption(context, '日本語', 'ja'),
                    ],
                  );
                },
              ),
        ),
      ),
    );
  }
}

重要なのは

onPressed: () {
  context.findAncestorStateOfType<_MyAppState>()!
    ..locale = Locale(languageCode, '')
    ..setState(() {});
  Navigator.pop(context);
}

ここら辺で、上位WidgetのlocaleプロパティとsetStateを呼び出して全体の再描画を行っています。が、このやりかたでよいのか正直わからないです。
でも仕方ないのかなあ?

provider版

とりあえずサンプルアプリ全体。

main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => LocaleState()),
      ],
      child: const MyApp(),
    ),
  );
}

class LocaleState with ChangeNotifier {
  Locale _locale = AppLocalizations.supportedLocales.first;

  Locale get locale => _locale;
  set locale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      locale: context.watch<LocaleState>().locale,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HelloPage(),
    );
  }
}

class HelloPage extends StatelessWidget {
  const HelloPage({Key? key}) : super(key: key);

  SimpleDialogOption _changeLocaleDialogOption(
      BuildContext context,
      String text,
      String languageCode) {
    return SimpleDialogOption(
      child: Text(text),
      onPressed: () {
        context.read<LocaleState>().locale = Locale(languageCode, '');
        Navigator.pop(context);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text(AppLocalizations.of(context)!.hello)
      ),
      body: Center(
        child: TextButton(
          child: Text(AppLocalizations.of(context)!.languageSettings),
          onPressed: () =>
              showDialog(
                context: context,
                builder: (context) {
                  return SimpleDialog(
                    title: Text(AppLocalizations.of(context)!.languageSettings),
                    children: <Widget>[
                      _changeLocaleDialogOption(context, 'English', 'en'),
                      _changeLocaleDialogOption(context, '日本語', 'ja'),
                    ],
                  );
                },
              ),
        ),
      ),
    );
  }
}

StatefulWidget版と基本的な構造は一緒ですが、localeをプロパティでなくプロバイダから取るためにChangeNotifierをミックスインしたクラスを作っています。

重要なのは

locale: context.watch<LocaleState>().locale,

ここと

onPressed: () {
  context.read<LocaleState>().locale = Locale(languageCode, '');
  Navigator.pop(context);
},

ここら辺で、setStateのように能動的に変更を通知するのではなく、プロバイダを監視させることで自動的に変更が通知されます。
onPressedLocaleState.localeセッターを呼んでいますが、この中のnotifyListeners()によって通知されるという流れです。)
StatefulWidget版よりはこっちの方がよさそうですかね。

Riverpod + Flutter Hooks版

とりあえずサンプルアプリ全体。

main.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

final localeProvider = StateProvider<Locale>(
        (ref) => AppLocalizations.supportedLocales.first);

class MyApp extends HookConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      locale: ref.watch(localeProvider),
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HelloPage(),
    );
  }
}

class HelloPage extends HookConsumerWidget {
  const HelloPage({Key? key}) : super(key: key);

  SimpleDialogOption _changeLocaleDialogOption(
      BuildContext context,
      StateController<Locale> localeController,
      String text,
      String languageCode) {
    return SimpleDialogOption(
      child: Text(text),
      onPressed: () {
        localeController.state = Locale(languageCode, '');
        Navigator.pop(context);
      },
    );
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    StateController<Locale> localeController = ref.watch(localeProvider.state);
    return Scaffold(
      appBar: AppBar(
          title: Text(AppLocalizations.of(context)!.hello)
      ),
      body: Center(
        child: TextButton(
          child: Text(AppLocalizations.of(context)!.languageSettings),
          onPressed: () =>
            showDialog(
              context: context,
              builder: (context) {
                return SimpleDialog(
                  title: Text(AppLocalizations.of(context)!.languageSettings),
                  children: <Widget>[
                    _changeLocaleDialogOption(context, localeController, 'English', 'en'),
                    _changeLocaleDialogOption(context, localeController, '日本語', 'ja'),
                  ],
                );
              },
            ),
        ),
      ),
    );
  }
}

providerでのChangeNotifier.notifyListeners()のような明示的な変更通知はなく、代わりにStateProviderを使うことで自動的に変更が通知されます。
細かい構文の違いはありますが、他は大体providerと一緒です。

これがぱっと見一番よさそうな感じがしますが、Riverpod自体がまだ枯れてなさそうなのが難点かなあ。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1