🌏

【Dart / Flutter】Slangで国際化対応する

2024/01/24に公開

はじめに

こんにちは!Altive株式会社Flutterアプリ開発者の中村です。
業務内でSlangを使用した国際化対応を行いましたので、導入方法や注意点など簡単にご紹介させていただきます🌏

本記事では以下の表示になるようにご説明します📝

en jp

Slangとは

https://pub.dev/packages/slang
国際化・ローカライゼーション(i18n)のためのライブラリで、JSON、YAMLなどさまざまな形式のファイルを使用して翻訳ファイルを生成・使用できます。
またFlutterに依存しないため、どんなDartプロジェクトでも使用可能です。

依存関係の追加

pubspec.yaml
dependencies:
  flutter_localizations:
    sdk: flutter
  slang: <version>
  slang_flutter: <version> # Flutterアプリで使用する場合は追加

dev_dependencies:
  build_runner: <version> # build_runnerを使用する場合のみ追加
  slang_build_runner: <version> # build_runnerを使用する場合のみ追加

JSONファイルの作成からDartファイル生成まで

ARBからJSONに移行する場合

すでに他の手段で多言語化をしていてARBファイルがある場合は、以下のコマンドを叩けば簡単に移行できます。

dart run slang migrate arb source.arb destination.json

JSONファイル作成

各ロケールのJSONファイルを作成します。
ファイルの形式はJSON, YAML, CSV, ARBのどれでも構いませんが、今回はJSONで作成します。

ファイルの配置場所

Getting Startedによると

Most common i18n directories are assets/i18n and lib/i18n.

とのことなので、lib/i18nディレクトリを作成してこちらに配置しました。
ちなみにデフォルトではJSONファイルの配置場所は特に指定がないので、別のディレクトリ名でも大丈夫です。
ただし、ファイル名のパターンはデフォルトで.i18n.jsonとなっているので、strings_en.i18n.jsonのようにする必要があります。

今回はenjp二つのロケール用のファイルを作成しました。

lib/i18n/strings_en.i18n.json
{
  "greeting" : "Hello!",
  "welcomeMessage": "Welcome to our application.",
  "settings": {
    "language": "Language",
    "theme": "Theme",
    "notification": "Notifications"
  }
}
lib/i18n/strings_jp.i18n.json
{
  "greeting": "こんにちは!",
  "welcomeMessage": "私たちのアプリケーションへようこそ",
  "settings": {
    "language": "言語",
    "theme": "テーマ",
    "notification": "通知"
  }
}

コマンドを叩いてDartファイルを生成

各ロケールのJSONファイルを作成したら、コマンドを叩いてDartファイルを生成します。
build_runnerよりもslangの方が高速なのでおすすめです。

  • slangを使用する場合
dart run slang
  • build_runnerを使用する場合
dart run build_runner build -d

コマンドを叩くと、ディレクトリ指定がない場合はJSONファイルと同じディレクトリにstrings.g.dartが生成されます🎉

これでstrings.g.dartをimportしてDart/Flutterプロジェクトで使えるようになりました!
以下の例だと、言語がenの時にnotification、jpの時には通知と表示されます。

import 'i18n/strings.g.dart';

String notification = t.settings.notification;

slang.yaml(build.yaml)について

基本的にslang.yamlがなくても使用できるのですが、ファイル名のパータン変更などしたい時に作成する必要があります。
デフォルト設定から変更することがよくありそうな項目をいくつか紹介します。

  • base_locale
    デフォルトではenが設定されていますが、その他言語にしたい場合はここで変更できます。
    base_localeで設定しているロケールのJSONファイルが用意されていないと怒られるので、enが必要なければ他のロケールを指定してください。
  • input_directory
    JSONファイルの置き場所を指定できます。
  • input_file_pattern
    デフォルトでは.i18n.jsonなので、YAMLファイルを使用したい時に.i18n.yamlに変更する等で必要になります。
  • output_directory
    strings.g.dartの出力先を指定できます。

その他設定できる項目についてはConfigurationをご確認ください🙏🏻

デバイスのロケール情報を取得する

デバイスのロケール情報を取得してアプリに反映させるために、いくつか設定することがあります。

  • LocaleSettings.useDeviceLocaleを実行してデバイスのロケールで初期化する
  • TranslationProviderでラップする
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'i18n/strings.g.dart';
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  // デバイスのロケールで初期化
  LocaleSettings.useDeviceLocale();
  // TranslationProviderでラップ
  runApp(TranslationProvider(child: const MyApp()));
}

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      // TranslationProviderをロケールにセット
      locale: TranslationProvider.of(context).flutterLocale,
      supportedLocales: AppLocaleUtils.supportedLocales,
      localizationsDelegates: GlobalMaterialLocalizations.delegates,
      home: const MyHomePage(),
    );
  }
}

デバイスのロケール情報が取得できるようになったら、実際に使ってみましょう!
はじめにでご紹介した簡単なFlutterアプリで使用する例です。

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

  
  Widget build(BuildContext context) {
    final t = Translations.of(context);

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(t.greeting),
            Text(t.welcomeMessage),
            const Gap(30),
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 20),
              decoration: BoxDecoration(
                color: Colors.grey[200],
                borderRadius: BorderRadius.circular(10),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.settings),
                  const Gap(10),
                  TextButton(
                      onPressed: () {}, child: Text(t.settings.language)),
                  TextButton(onPressed: () {}, child: Text(t.settings.theme)),
                  TextButton(
                      onPressed: () {}, child: Text(t.settings.notification)),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

さいごに

Slangで簡単に国際化対応ができると効率的にアプリ開発ができてありがたいですね🚀
記事を最後までご覧いただきありがとうございました🙏

GitHubで編集を提案
Altiveエンジニアリングブログ

Discussion