Flutterの多言語対応は意外に簡単だった

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認くださ</b>い</font>
* 10万文字を超える超大作になっています(笑)

---

# はじめに
* いままでおざなりにしていた多言語対応を整理

## まとめ
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)


# 多言語対応
日本のスマホ市場はかなりの大きさではありますが、それでも世界市場から見ればその一端でしかありません。
ゲーム、アプリ、ツールいずれであってもある程度のユーザ数を獲得したいならば、多言語化(最低限英語)対応が必要になってきます。

Flutterには、この多言語対応の仕組みが備わっています。
このチャプターでは、日本語/英語対応を解説していきます。

まず、今回やりたいことは以下のような、言語設定に合わせた言語で文字を出力することです。

![pic6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/e4e3653d-a81c-c57e-56b4-3363f9ce1932.png)


## 手順1 多言語用のライブラリのインストール
まず、ライブラリのインストールを行います。
後々必要になる関連ライブラリや自動ファイル生成の有効化も行っておきます。

```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations: # 多言語ライブラリの本体
    sdk: flutter  
  intl: "^0.17.0" # 多言語やフォーマッタなどの関連ライブラリ
  
flutter:
  generate: true #自動生成フラグの有効化
```

## 手順2 アプリの多言語対応の設定

```dart:main.dart
+ import 'package:flutter_localizations/flutter_localizations.dart';
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
+      localizationsDelegates: [
+        GlobalMaterialLocalizations.delegate,
+        GlobalWidgetsLocalizations.delegate,
+        GlobalCupertinoLocalizations.delegate,
+      ],
+      supportedLocales: [
+        const Locale('ja', ''), //日本語
+        const Locale('en', ''), //英語
+      ],
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

```

## 手順3 多言語対応設定ファイルの作成


```yaml:{PROJECTROOT}/l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
```
arb-dirがarbファイルが置かれるディレクトリです。
template-arb-fileがメッセージ対応のテンプレート定義です。
output-localization-fileがインポートするDartクラスファイルの定義です。


arb(ApplicationResourceBundle)ファイルは、json形式のFlutterで言語リソースを管理するためのファイルです


l10nは聞き慣れない用語かと思いますが、localizationが長いので、「l + 10文字 + n」という意味で略した単語です。
下記のようにいろいろなバリエーションがあり、ニュアンスは異なりますが、根本的な意味は同じです。
l10n → localization(地域化)
i18n → internationalization(国際化)
g11n → globalization(グローバル化)
m17n → multilingalization(多言語化)


## 手順4 言語ファイルを準備する

arbファイルをjson形式で、リソースキーとリソースバリューを組みにして記述していきます。
言語ごとに準備します。

ベースは、`template-arb-file`で指定した`app_en.arb`となり、日本語を使う時に対応するリソースIDがあるものは、日本語のリソースバリューが使えます。

```json:app_en.arb
{
  "@@locale":"en",
  "hello": "Hello {name}",
  "@hello": {
      "description": "welcome message",
      "placeholders":{
      "name":{
        "description" : "username",
        "example" : "kazutxt"
      }
    }
  },
  "login": "Login",
  "info": "Info",
  "allow": "ALLOW!",
  "deny": "DENY!"
}
```

```json:app_ja.arb
{
  "@@locale": "ja",
  "hello": "ようこそ {name}",
  "login": "ログイン",
  "info": "情報",
  "allow": "許可"
}
```
上記のようにプレースホルダーや説明/例示を残すこともできます。


denyに対応する日本語を準備していないのは意図的です。
日本語対応するものがない場合は、テンプレートの英語のほうが使われるのを確認するためです。

使える表現は、下記サイトをご確認ください。
https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification

## 手順5 ファイルの生成

`flutter pub get`を実行し、設定ファイルからdartファイルを生成。
VSCodeの場合は、pubspec.yamlを更新すると自動で実施されます。


arbファイルが不完全の場合は、ツールがクラッシュしたり、エラーログが出力されます。
エラーからは問題箇所を追いかけにくいので、こまめに保存/生成し問題が無いことを確認しながらすすめることをオススメします。

自動生成されるのは、
`.darttool/fluttergen/genl10n/applocalizations.dart`になります。

それぞれの言語に対応するファイルも生成されています。
ちなみに、同じフォルダにある日本語版は下記のような内容になっています。

```dart:app_localizations_ja.dart
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';

// ignore_for_file: unnecessary_brace_in_string_interps

/// The translations for Japanese (`ja`).
class AppLocalizationsJa extends AppLocalizations {
  AppLocalizationsJa([String locale = 'ja']) : super(locale);

  @override
  String hello(Object name) {
    return 'ようこそ ${name}';
  }

  @override
  String get login => 'ログイン';

  @override
  String get info => '情報';

  @override
  String get allow => '許可';

  @override
  String get deny => 'DENY!';
}
```


## 手順6 アプリでの利用
自動生成されたものが使えるように、インポート文とデリゲート文を追加します。

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
+        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('ja', ''), //日本語
        const Locale('en', ''), //英語
      ],
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
+            Text(
+              AppLocalizations.of(context)!.hello("kazutxt"),
+            ),
+            Text(
+              AppLocalizations.of(context)!.allow,
+            ),
+            Text(
+              AppLocalizations.of(context)!.deny,
+            ),
          ],
        ),
      ),
    );
  }
}
```

実際に使っているのは、`AppLocalizations.of(context)!.hello("kazutxt")`のような部分です。直接文字列を問い合わせるのではなく、多言語対応のクラスに問い合わせてそれぞれの言語に合わせた文字を取得しています。

## 手順7 動作の確認

全体的な流れをまとめると、下記のようになります。

![pic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/22ca2621-6fec-2b36-da4b-23e847e9e4cd.png)


最後に、もう一度結果を確認します。

![pic6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/0391734d-7ca0-f900-1501-ece3ddefbb20.png)



denyについては日本語に対応するものがないので、テンプレートのdenyで設定した"DENY!"がそのまま表示されています。