📱

【Flutter】Localization(L10n)対応

2021/09/08に公開
1

動作確認環境

Flutter 2.2.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f4abaa0735 (5 weeks ago) • 2021-07-01 12:46:11 -0700
Engine • revision 241c87ad80
Tools • Dart 2.13.4

経緯

FlutterでLocalizationする際、公式が出しているintlを導入することが多い思うが、自分が調べて行った際にサイト毎で微妙に手順が違っていて混乱したので、次から迷わないようにまとめておこうと思った次第。

Step1 パッケージの導入

1-1. pubspec.yamlに記述追加

Internationalizing Flutter appsのサイトを元に、Flutterプロジェクトのpubspec.yamlに追記する

https://flutter.dev/docs/development/accessibility-and-localization/internationalization

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  flutter_localizations: # Add this line
    sdk: flutter         # Add this line

  intl: ^0.17.0          # Add this line

flutter:
  generate: true         # Add this line

要素の説明

  • flutter_localizations: 英語以外の言語のLocalizationに対応するためのパッケージ
  • intl: 日時や数字等をLocalizationするためのパッケージ
    • 記事作成時点では0.17.0が最新。必要に応じてversionは書き換え。
  • generate: generateフラグ
    • Localizationコードの自動生成時にtrueになっていないとerrorになる

1-2. flutter pub getを叩く

Flutterプロジェクトのrootで、flutter pub getを叩き、パッケージをプロジェクトに取り込む。

Step2 Localization用のyaml作成

2-1. l10n.yamlを作成

Flutterプロジェクトのrootにl10n.yamlを作成する。
(引数付きでflutter gen-l10nを叩けばコマンドでも生成可能)

2-2. l10n.yamlに記述

以下の記述は、自分用に変更等を加えているので、後述の説明とともに適宜書き換えて保存。

l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: l10n.dart
output-class: L10n
synthetic-package: false

要素の説明

  • arb-dir: Localizationで自動生成するフォルダの指定
    • arbは、Application Resource Bundleの略
    • default値は、lib/l10n
  • template-arb-file: Localizationで自動生成するファイルの元となるresourceファイル
    • default値は、app_en.arb
  • output-localization-file: Localization処理の定義をするファイル名
    • default値は、app_localizations.dart
    • default値だと長い命名になるので、l10n.dart等短めに指定しておく方が良い
  • output-class: output-localization-fileのファイル内で自動生成して定義されるclass名
    • default値は、AppLocalizations
    • default値だと長い命名になるので、L10n等短くなるよう指定しておく方が良い
  • output-dir: Localizationコードが自動生成されるフォルダ
    • default値は、--arb-dirと同じフォルダになる
    • --arb-dirと同じで良いため、yamlに記載はしていない
  • preferred-supported-locales: supportedLocalesで最初に来る言語を指定できる
    • 指定がない場合、生成された言語のアルファベット順になる
    • アプリのLocalizeが端末言語に対応していない時、デフォルト言語として指定するために使う
    • 指定した言語のarbファイルにのみLocalization定義を書くと、自動生成された言語別ファイルでも同じ内容が記載される
      • 全言語で同一文字列を使う時に役立つ…かもしれない?
  • synthetic-package: コードを合成パッケージ(?)として生成するかプロジェクトの指定フォルダに生成するかどうか
    • default値は、true
    • trueの時、合成パッケージを生成するらしいが、使ったことないのでわからない。
    • falseの時、output-dirで指定したフォルダにLocalizationコードを生成する

Step3 Localization用のフォルダと定義を作成

3-1. l10nフォルダを作成

Step2のl10n.yamlarb-dirの指定したlib/l10nの通りの場所、名前でフォルダを作成

3-2. app_en.arbファイルを作成

作成したフォルダにl10n.yamltemplate-arb-fileに指定したapp_en.arbの名前のファイルを作成

3-3. app_en.arbに記述

arbは、JSONとほぼ同じ記述形式になっている。

app_en.arb
{
  "@@locale": "en",
  
  "helloWorld": "Hello World!",
  "@helloWorld": {
    "description": "The conventional newborn programmer greeting"
  }
}

要素の説明

  • @@locale: ファイルが扱うlocale
    • localeのため、enでなくen_USでも良い
  • helloWorld: プロジェクトから呼び出すLocalizeKey
    • lowerCamelCaseで記述
    • 値にあたるHello World!がLocalize時に読み出される
  • @helloWorld: helloWorldに対するアノテーションのようなもの
    • description、引数を取る時の型、フォーマット等の指定が行える

3-4. コード生成

flutter gen-l10nを叩いて、Localizationのコードを生成する。
ここまでで記述や作成位置に問題なければ、l10n.yamlの内容を元にLocalizationファイルが生成される。

Step4 プロジェクトに生成したコードを適用

4-1. MaterialAppに記述追加

MaterialApplocalizationsDelegates及びsupportedLocalesを記載する。
その際、生成したコードのimportも忘れずに行う。

main.dart
import 'package:flutter/material.dart';
+import 'package:new_project/l10n/l10n.dart';

...

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
+      localizationsDelegates: L10n.localizationsDelegates,
+      supportedLocales: L10n.supportedLocales,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

MaterialApp側の要素の説明

  • localizationsDelegates: Localization Widgetのdelegate
    • supportedLocalesを元に、Localizationの処理周りを担う
    • localizationsDelegatesを記載する時、supportedLocalesも必須となる。
  • supportedLocales: Localizationをサポートしている言語

Localizationコードの要素の説明

  • L10n.localizationsDelegates: Localizationのdelegateを1つにまとめたもの
    • 以下のdelegateを含んでいるため、この1行だけ書けば良い
      • L10n.delegate
      • GlobalMaterialLocalizations.delegate
      • GlobalCupertinoLocalizations.delegate
      • GlobalWidgetsLocalizations.delegate
  • L10n.supportedLocales: Localizationのサポート言語
    • arbファイルで定義した言語を元にコード自動生成時に追加される

※自動生成コードのclass名をL10nにした場合で記載

4-2. Localizationで生成されたコードの適用

L10n.of(context)でinstanceを作ってLocalizeKeyを呼ぶことでLocalizationされたテキストが呼ばれる。

main.dart
...

class MyHomePage extends StatelessWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  
  Widget build(BuildContext context) {
+    final l10n = L10n.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
+        child: Text(l10n.helloWorld),
      ),
    );
  }
}

Step5

5-1. .gitignoreに記載

自動生成されるコードは差分がでやすく、コミット対象に不向きなため、.gitignoreに記述してコミットから除外する。

.gitignore
**/l10n/*.dart

5-2. analysis_options.yamlから除外(lintを使っている人のみ対象)

自動生成されるコードをlintに対象すると、修正できないwarningやerrorが出ることもあるためlintから除外する。

analysis_options.yaml
analyzer:
  exclude:
    - '**/l10n/*.dart'

Step6 他の言語追加

6-1. arbファイルの作成

追加したい言語のarbファイルをl10n.yamltemplate-arb-fileに記載と同じ命名で作成する
日本語のLocalizeを追加する場合を例に説明とapp_ja.arbを作成する

app_ja.arbファイルの中身は、app_en.arbをコピペし、各値を日本語向けに書き換える

app_ja.arb
{
  "@@locale": "ja",

  "helloWorld": "こんにちは世界!",
  "@helloWorld": {
    "description": "新米プログラマーの挨拶"
  }
}

6-2. iOSプロジェクトで言語追加

XcodeでこのFlutterプロジェクトのiosフォルダにあるRunner.xcworkspaceを開く。
左のカラムでInfo.plistを選択し、LocalizationsKeyにJapaneseを追加する。
(LocalizationsKeyがない時は、+を押してKey自体を追加する。)

6-3. 端末の言語設定変更と確認

端末言語を日本語にしてアプリを起動してみると、Localizeされた画面が表示されることを確認!

Step7 英語以外をデフォルト言語に設定

7-1. l10n.yamlに追記

アプリでLocalizeに端末の言語が対応していない時、英語がLocalizeされていれば、英語がデフォルト言語となるようだ。
英語がLocalizeされていない時は、何が基準でデフォルト言語になっているか動作確認等してみたものの不明。

日本語をデフォルト言語にする場合、まずStep2-2のl10n.yamlpreferred-supported-localesと言語コードを追記する

l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: l10n.dart
output-class: L10n
synthetic-package: false
+preferred-supported-locales: ja

7-2. MaterialAppに記述追加

MaterialApplocaleResolutionCallbackを追加する。
callback処理内で、Localizeしている言語(supportedLocales)に端末言語(locale)が含まれているか確認する。
この時、localeには言語コード+国コードが含まれているので、言語コードだけのLocale instanceを生成して比較。
含まれていれば、そのlocaleをそのまま返す。
含まれていなければ、supportedLocalesの最初にあるlocaleを返す。
(この最初にくるlocale7-1の手順で設定している)

main.dart
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: L10n.localizationsDelegates,
      supportedLocales: L10n.supportedLocales,
+      localeResolutionCallback: (locale, supportedLocales) {
+        if (locale != null) {
+          final _locale = Locale(locale.languageCode);
+          if (supportedLocales.contains(_locale)) {
+            return _locale;
+          }
+        }
+        return supportedLocales.first;
+      },
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

MaterialApp側の要素の説明

  • localeResolutionCallback: localesupportedLocalesが渡ってきて、callbackで返した言語がアプリに反映される
    • locale: アプリ起動時の端末言語の優先度が1番高いものが来る。
      • nullの時は、アプリ起動時に端末言語をまだ取得できていない状態
    • supportedLocales: MaterialAppsupportedLocalesで渡している値

余談

同一言語他国家のLocalizeには本記事の方法では対応しきれないことが出てくると思われる。
en_USen_UKをそれぞれLocalizeする方法は、また別途調査して書ければと思う。

参考にしたページ

Discussion