【Flutter】多言語対応のための .arb ファイルの書き方

#はじめに
Flutter のバージョン 1.22.0 から正式な多言語対応機能が導入されました。
これの具体的なやり方については以下の記事が詳しいです。

[【Flutter】l10n ローカライゼーション（多言語対応）に対応する](https://qiita.com/tetsukick/items/962505028d14006af370)

この方法では、まず基準となる arb ファイルを作成し、それに基づいて Dart によるソースコードが生成されます。つまり開発者は多言語化のために arb ファイルを記述すれば良いということになるのですが、arb ファイルの書き方についての纏まった資料があまり見当たりません……。
ですので、arb についての理解を深めるとともに、今後この方法で多言語対応しようと考えている方の参考になればと、この記事を書いています。

#Application Resource Bundle
arb は json をもとにした形式で、Flutter では言語リソースの管理のために使用されています。
arb の仕様については[こちら](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification)を参照ください。
今のところ、Flutter では arb の仕様のすべてに対応しているわけではありませんが、基本的な記法についてはリンク先の通りです。

なお初歩的な話ですが arb は json を基にしてるので、[末尾のカンマ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Trailing_commas)はつけないようにしましょう。（最初のころ、これに気づかず一時間くらいハマりました……）

基本的には以下のようにIDと値の対応でリソースを管理します。

##多言語の文字列
単純に静的な文字列を表示したいだけの場合は以下のようにします。

```json:app_en.arb
{
    "helloWorld": "Hello World!"
}
```
```json:app_ja.arb
{
    "helloWorld": "ハローワールド！"
}
```
「OK」や「キャンセル」、「編集」などなど、例えばボタンに割り当てるようなシンプルなものについてはこれで事足ります。

##属性
リソースに色々な属性を付与したい場合は、@[リソースID] に属性のマップを与えます。
例えば属性として、"description" や "context" を与えることができます。 json にはコメントがないので、"description" などを設定しておくことは後々役に立ちそうです。
コード生成の際に解釈されるのは "description" と "placeholders" だけのようです。
特に "description" パラメータの内容はコード生成の際にドキュメントコメントとして書き出されるのでコーディングの際に確認することができます。

```json:app_en.arb
{
    "helloWorld": "Hello World!",
    "@helloWorld": {
        "description": "The conventional newborn programmer greeting",
        "context": "homepage"
    }
}
```


##引数を渡す
リソースに引数を利用したい場合は、変数部分を "{}" で囲い、属性の "placeholders" に引数に対応するマップを与える必要があります。逆に "placeholders" にキーを与えなければ単なる文字列として解釈されます。

```json:app_en.arb
{
    "welcome": "Welcome, {name}.",
    "@welcome": {
        "placeholders": {
            "name": {}
        }
    }
}
```
app_en.arb でこのように定義して、他の言語リソースにも同名の引数を追加します。

```json:app_ja.arb
{
    "welcome": "ようこそ、{name} さん。"
}
```
こうすれば、Dart から引数を渡してやれます。

```dart
AppLocalizations.of(context).welcome("Yukihiro")

// -> "Welcome, Yukihiro."
```

引数が複数ある場合や、引数に属性を付与する場合はこんな感じにします。

```json:app_en.arb
{
    "ages": "{name} will be {age} years old on {birthday}.",
    "@ages": {
        "type": "int",
        "placeholders": {
            "name": {
                "type": "String",
                "description": "The name of the person.",
                "example": "Yukihiro"
            },
            "age": {
                "type": "int",
                "format": "compact",
                "description": "The age of the person."
            },
            "birthday": {
                "type": "DateTime",
                "format": "MMMMd",
                "description": "The birthday of the person."
            }
        }
    }
}
```
なお、"type" は単純に変数の型を書けば良いようです。与えた文字列がそのまま型として使われます。
インポートの関係から dart:core に含まれる型なら問題なく使えます。（逆に "Number" とか書くとエラーが起きる。）
特に指定しなければ Object 型になり、表示する際は .toString() してくれます。

ただし、数値型 (num, int, double) または DateTime 型を指定した場合、"format" の設定が必要です。"format" に何が指定できるかは intl パッケージの [NumberFormat](https://api.flutter.dev/flutter/intl/NumberFormat-class.html) を参照するとわかりますが、数値型については一応下にまとめておきます。

| “format”文字列 | 1200000を入力した場合 |
|:-:|:-:|
| compact  | 1.2M  |
| compactCurrency  | $1.2M  |
| compactSimpleCurrency  | $1.2M |
| compactLong  | 1.2 million |
| currency  |USD1,200,000.00  |
| *decimalPattern  | 1,200,000  |
| decimalPercentPattern  | 120,000,000%  |
| *percentPattern  | 120,000,000%  |
| *scientificPattern  | 1E6  |
| simpleCurrency  | $1,200,000.00  |

(ただ、一部のフォーマットでは2020年12月21日時点で、指定するとエラーが起きる[問題](https://github.com/flutter/flutter/issues/67217)があります。どうやら名前付きパラメータとしてコード生成される部分が、実際にはオプショナルな位置的パラメータになっており矛盾が生じているようです。上の表では駄目そうなやつに * を付けています。)

また、DateTime 型については [DateFormat](https://api.flutter.dev/flutter/intl/DateFormat-class.html) に詳しく載っています。こちらはかなり沢山のパターンがあるようです。

##複数形
arb では、リソース値の変数は [ICU Message syntax](https://unicode-org.github.io/icu/) というものに準拠しており、引数によって条件分岐したりすることができます。これによって、単数形や複数形で表示を変えたり、性別によって分岐することができます。日本語ではあまり意識することのない部分ですが、英語を始め多くの言語でこの機能は重要です。
現状、Flutter でのコード生成は複数形の分岐のみ解釈するようです。Intl ライブラリの方には性別による分岐もあるため、今後そちらも対応していく可能性はありそうです。

書き方は以下です。

```json:app_en.arb
{
    "cat": "{catNum, plural, =0 {Oops! You have no cat.} =1 {You have a cat.} other {You have {catNum} cats.}}",
    "@cat": {
        "placeholders": {
            "catNum": {}
        }
    }
}
```
```json:app_ja.arb
{
    "cat": "{catNum, plural, =0 {おっと！ 猫がいません。} other {猫を{catNum}匹飼っています。}}"
}
```
リソース値は文字列なので、現状では一行で書かなければなりません。plural 指定された引数は int 型になります。
なお、基準となる arb ファイルで plural を指定した場合、すべての言語で plural にしなければなりません。また、１つのリソース値の中で plural を指定できる引数は最初の１個だけです。
そのほか、{num, plural, ...} の前後に文を書いても読み取ってもらえないことにもご注意ください。

もう少し複雑な例を挙げるとこんな感じです。

```json:app_en.arb
{
    "wishList": "{itemCount, plural, =0 {There's nothing on {name}'s wish list.} =1 {There is an item in {name}'s wish list} other {There are {itemCount} items in {name}'s wish list}}.",
    "@wishList": {
        "description": "Show what's on the wish list.",
        "placeholders": {
            "itemCount": {
                "description": "The number of the items in the wish list."
            },
            "name": {
                "type": "String"
            }
        }
    }
}
```
plural の分岐には、"=0", "=1", "=2", "few", "many", "other" を指定できます。
"few" や "many" の場合の具体的な値の範囲は言語によって異なるようです。

#おわりに
ここまで、多言語対応するにあたって必要そうな arb ファイルの記法を紹介しました。
現状では arb 及び ICU syntax の解釈は完全とは言えないものの、これらを駆使してアプリの多言語化自体は問題なくできそうです。今後とも Flutter の多言語対応について注視していきたいと思います。

なお、筆者もこの方法での多言語化を始めたばかりですので間違いを含む可能性があります。
なにか誤りや記述漏れ不足等ございましたらコメント欄でご指摘ください。

#参考文献
[Internationalizing Flutter apps](https://flutter.dev/docs/development/accessibility-and-localization/internationalization)
[Internationalization User Guide (PUBLICLY SHARED)](https://docs.google.com/document/d/10e0saTfAv32OZLRmONy866vnaw0I2jwL8zukykpgWBc/)
[New Localization (Flutter 1.22): i18n and l10n support](https://medium.com/flutter-community/new-localization-flutter-1-22-i18n-and-l10n-support-774d6542ee6a)
[Message Syntax](https://formatjs.io/docs/core-concepts/icu-syntax/)