flutter多言語対応したWebページ・アプリを作る

茅ヶ岳から南アルプス方面

明けましておめでとうございます、 id:numanuma08 です。先日、flutterを使って多言語に対応したWebページの実装とデプロイをしたのでノウハウを共有します。

flutter の多言語対応事情

flutterは標準で多言語に対応しています。基本的には公式ドキュメントにある通り

  • flutter_localizationの依存を追加
  • intlの依存を追加
  • l10n.yamlに設定を記述
  • 言語ごとの arb ファイルを作成・編集して flutter gen-l10nを実行

以上の流れとなります。

docs.flutter.dev

依存関係の追加

pubspec.yamlに以下の内容を追記して、flutter pub getをします。

dependencies:
  flutter:
    sdk: flutter
  flutter_web_plugins:
    sdk: flutter

  flutter_localizations:
    sdk: flutter
  intl: ^0.18.1 # intlのバージョンは適切な物を選ぶ

l10n.yamlの記述

ファイル形式の詳細は公式ドキュメントを確認してください。最低限、日本語と英語に対応するため以下の内容を記入します。

arb-dir: lib/l10n
template-arb-file: app_en.arb

arbファイルの記述

言語ごとにarbファイルを作ります。l10n.yamlで指定した場所に作るため、今回の場合は次のファイル構成となります。

lib
├── l10n
│   ├── app_en.arb
│   └── app_ja.arb

flutter gen-l10nを実行するとarbファイルを読み込んで言語の定数を定義したdartのコードが生成されます。文字列は以下のようなコードで参照できます。

import 'package:flutter_gen/gen_l10n/app_localizations.dart';

const text = AppLocalizations.of(context)!.your_defined_text;

システムの言語設定を反映させる

flutter webアプリケーションの場合、ブラウザの言語設定を読み込んで現在の言語を調べます。ブラウザの言語設定はJavaScriptのwindow.navigator.languageで取得しますが、flutterから取得する場合はIntlクラスのsystemLocaleプロパティで取得可能です。systemLocaleは呼び出し前にfindSystemLocale()を呼び出さなければならない点に注意してください。

api.flutter.dev

import 'package:flutter/foundation.dart';
import 'package:intl/intl_standalone.dart' as intl_standalone;
import 'package:intl/intl_browser.dart' as intl_browser;

void main() async {
  if (kIsWeb) {
    await intl_browser.findSystemLocale();
  } else {
    await intl_standalone.findSystemLocale();
  }

アプリ内の言語設定変更に対応する

多言語対応アプリっぽくするため、アプリ内で好きに言語設定変更に対応します。今回は、言語設定管理クラスをChangeNotifierのサブクラスで実装し、provderライブラリでアプリ内から参照・変更できるように実装しました。

pub.dev

import 'package:flutter/material.dart';

// 言語設定を管理するクラス
// 必要に応じて言語設定を永続化・復元する
class LocaleNotifier extends ChangeNotifier {
  LocaleNotifier(this._locale);

  Locale _locale;

  Locale get locale => _locale;

  void changeLocale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

// main.dart
import 'package:intl/intl_standalone.dart' as intl_standalone;
import 'package:intl/intl_browser.dart' as intl_browser;
import 'package:provider/provider.dart';

void main() async {
  // システムの言語設定を読み込んで、アプリ内で利用可能にする
  if (kIsWeb) {
    await intl_browser.findSystemLocale();
  } else {
    await intl_standalone.findSystemLocale();
  }
  // システムの言語設定をデフォルトの言語とする
  // 今回は日本語設定意外であれば英語とした
  final Locale defaultLocale;
  if (Intl.getCurrentLocale().startsWith('ja')) {
    defaultLocale = const Locale("ja", "JP");
  } else {
    defaultLocale = const Locale("en");
  }

  runApp(
    ChangeNotifierProvider(create: (_) => LocalNotifier(defaultLocale)
    child: const MyApp()
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    // 言語設定を監視する
    final localeNotifier = context.watch<LocaleNotifier>();
    return MaterialApp.router(
      title: 'MyApp',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      // アプリ内で設定された言語を使ってUIを構成する
      locale: localeNotifier.locale,
// 以下省略

アプリ内で言語を変更する

言語変更のUIは様々な実装があると思いますが、例えばAppBarのactionsにDropDownMenuを配置する方法が考えられます。

AppBar(
  actions: [
            DropdownButton<Locale>(
              value: Locale(AppLocalizations.of(context)!.localeName),
              items: const [
                DropdownMenuItem(
                  value: Locale('en'),
                  child: Text('English'),
                ),
                DropdownMenuItem(
                  value: Locale('ja'),
                  child: Text('日本語'),
                ),
              ],
              onChanged: (newLocale) {
                if (newLocale != null) {
                    // アプリ全体の言語設定を切り替える
                    context.read<LocaleNotifier>().changeLocale(newLocale);
                }
              },
            )
]

まとめ

flutterで多言語対応をしたWebページ・アプリの実装を紹介しました。言語設定をアプリ全体の状態として持つと、好きなタイミングで設定が変更できるため、アプリの要件などによっては便利なものとなるでしょう。