Flutter で L10n (Localization) する

#L10n (Localization) とは？
端末の言語設定によって、アプリに表示する言語を変更する仕組みです。
Flutter では公式でこの機能が提供されています。
ちなみに、 L10n の 10 という数字は、 L と n の間に 10文字ある10です。
a11y も accessibility も同様の考え方です。


#Flutter での実装方法
Flutter では公式ドキュメントに L10n 対応方法が載ってあるので、それに沿って書いていこうと思います。
[Internationalizing Flutter apps](https://flutter.dev/docs/development/accessibility-and-localization/internationalization)

### 1. flutter_localizations のインポート

```diff
dependencies:
  flutter:
    sdk: flutter
+ flutter_localizations:
+   sdk: flutter
```

### 2. l10n.yaml ファイルの生成
```diff
+ arb-dir: lib/l10n
+ template-arb-file: app_ja.arb
+ output-class: L10n
+ output-localization-file: l10n.dart
```

- `arb-dir`: 生成するl10nファイルのディレクトリを指定
- `template-arb-file`: l10nできる文言一覧を定義したファイル
- `output-class`: 生成するクラス名
- `output-localization-file`: l10nの定義が書かれたファイル名

### 3. app_ja.arb ファイルの生成
`template-arb-file` に設定したファイルを作成します。
ここで各言語に合わせた表記の定義を行います。

```app_ja.arb
{
  "@@locale": "ja",
  "my_page": "マイページ",
    "@my_page": {
        "description": "マイページ"
      }
}
```

また、英語も設定したい場合は英語用ファイルも作成します。

```app_en.arb
{
  "@@locale": "en",
  "my_page": "myPage"
}
```

### 4. L10nクラスをコード生成

以下のコマンドで `L10n` クラスをコード生成します。

```terminal
$ flutter gen-l10n
```

このコマンドで L10n するために必要なコードが自動で生成されます。
本当に便利ですね。

L10nのベースとなるクラス。

```lib/l10n/l10n.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;

import 'l10n_en.dart';
import 'l10n_ja.dart';

...略...

abstract class L10n {
  L10n(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());

  final String localeName;

  static L10n? of(BuildContext context) {
    return Localizations.of<L10n>(context, L10n);
  }

  static const LocalizationsDelegate<L10n> delegate = _L10nDelegate();

...略...

  static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
    delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ];

  /// A list of this localizations delegate's supported locales.
  static const List<Locale> supportedLocales = <Locale>[
    Locale('en'),
    Locale('ja')
  ];

  /// マイページ
  ///
  /// In ja, this message translates to:
  /// **'マイページ'**
  String get my_page;
}

class _L10nDelegate extends LocalizationsDelegate<L10n> {
  const _L10nDelegate();

  @override
  Future<L10n> load(Locale locale) {
    return SynchronousFuture<L10n>(_lookupL10n(locale));
  }

  @override
  bool isSupported(Locale locale) => <String>['en', 'ja'].contains(locale.languageCode);

  @override
  bool shouldReload(_L10nDelegate old) => false;
}

L10n _lookupL10n(Locale locale) {
  


// Lookup logic when only language code is specified.
switch (locale.languageCode) {
  case 'en': return L10nEn();
    case 'ja': return L10nJa();
}


  throw FlutterError(
    'L10n.delegate failed to load unsupported locale "$locale". This is likely '
    'an issue with the localizations generation tool. Please file an issue '
    'on GitHub with a reproducible sample app and the gen-l10n configuration '
    'that was used.'
  );
}

```

自動生成される日本語用のファイル

```lib/l10n/l10n_ja.dart
import 'l10n.dart';

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

  @override
  String get my_page => 'マイページ';
}
```

自動生成される英語用のファイル

```lib/l10n/l10n_en.dart
import 'l10n.dart';

/// The translations for English (`en`).
class L10nEn extends L10n {
  L10nEn([String locale = 'en']) : super(locale);

  @override
  String get my_page => 'myPage';
}

```

### 5. MaterialApp の引数に Localaizations を渡す

`MaterialApp`の `localizationsDelegates` と `supportedLocales` に
先ほどコード生成で作成した値をそれぞれ入れる。

```hoge.dart
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      localizationsDelegates: L10n.localizationsDelegates,
      supportedLocales: L10n.supportedLocales,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
```

ちなみに渡している値は以下のように定義されています。

```lib/l10n/l10n.dart
  /// A list of this localizations delegate along with the default localizations
  /// delegates.
  ///
  /// Returns a list of localizations delegates containing this delegate along with
  /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
  /// and GlobalWidgetsLocalizations.delegate.
  ///
  /// Additional delegates can be added by appending to this list in
  /// MaterialApp. This list does not have to be used at all if a custom list
  /// of delegates is preferred or required.
  static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
    delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ];

  /// A list of this localizations delegate's supported locales.
  static const List<Locale> supportedLocales = <Locale>[
    Locale('en'),
    Locale('ja')
  ];
```

### 6. `arb`ファイルで定義した値を利用する

以下のようにして使うことができます。

```hoge.dart
L10n.of(context)!.my_page // String
```

## 最後に
公式でL10n対応できるのはすごいですね。
コード生成までしてくれるのが本当に便利。
Swiftで対応しようとした場合、swiftGen というライブラリを使って L10n対応するのが定番だと思いますが、公式で用意されているのは嬉しいですね。

ちなみに、[arb](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification) ファイルは `Application Resource Bundle` の略で以下のように説明されています。

> Application Resource Bundle (abbr. ARB) is a localization resource format that is simple (based on JSON), extensible (vocabulary can be added without affecting existing tools and usage), and directly usable (applications can access the resource directly from this format without converting to another form).
In ARB, localizable resources are encoded as a JSON object. Each resource will have a resource entry identified by resource key, and an optional resource attribute entry with resource attribute key.

このあたりも学んでいきたいですね。

少しでも役に立ったという方は `LGTM` していただけると嬉しいです。