Flutterの多言語対応、riverpodを使用して便利に
Flutter の多言語化対応
はじめに
本記事では、Flutter アプリケーションの多言語化対応を段階的に実装する方法を詳しく解説します。基本的な多言語化の実装から始め、最後に Riverpod を導入して状態管理を改善します。
バージョンは最新のものをご使用ください。(本記事は 2024/08/16 の最新のものです)
使用パッケージの紹介
パート 1: 基本的な多言語化の実装
1. 依存関係の設定
まず、pubspec.yaml
ファイルに必要な依存関係を追加します:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.19.0
flutter:
generate: true
2. l10n.yaml の設定
プロジェクトのルートディレクトリに l10n.yaml
ファイルを作成し、以下の内容を追加します:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
3. ARB ファイルの作成
lib/l10n
ディレクトリに app_en.arb
と app_ja.arb
ファイルを作成します:
app_en.arb:
{
"helloWorld": "Hello World!",
"welcomeMessage": "Welcome to Flutter!"
}
app_ja.arb:
{
"helloWorld": "こんにちは、世界!",
"welcomeMessage": "Flutterへようこそ!"
}
4. flutter gen-l10n の使用
arv ファイルを作成した後、 flutter gen-l10n
コマンドを使用して、必要な Dart コードを生成します。
プロジェクトのルートディレクトリで以下のコマンドを実行します:
flutter gen-l10n
このコマンドは、l10n.yaml
ファイルの設定に基づいて動作します。
生成されたファイル
コマンドを実行すると、通常以下のようなファイルが生成されます:
-
app_localizations.dart
: メインの生成ファイルで、AppLocalizations
クラスが含まれています。 -
app_localizations_en.dart
,app_localizations_ja.dart
: 各言語用の翻訳を含むファイル。
重要: 生成されたファイルの場所はプロジェクトの設定によって異なる場合があります。以下のような場所に生成されることがあります:
/your_project_root/.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart
生成後、プロジェクト内で生成されたファイルの正確な場所を確認することをお勧めします。
生成されたコードの使用
生成された AppLocalizations
クラスを使用するには、以下のようにインポートします:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
そして、ウィジェット内で以下のように使用します:
Text(AppLocalizations.of(context)!.helloWorld)
注意点
- ARB ファイルを変更するたびに
flutter gen-l10n
コマンドを再実行する必要があります。 - 生成されたファイルはバージョン管理システム(例:Git)にコミットしないことをお勧めします。
- プロジェクトの設定によっては、生成されたファイルの場所が異なる場合があります。必ず実際の生成場所を確認してください。
トラブルシューティング
flutter gen-l10n
コマンドがエラーを返す場合:
-
l10n.yaml
ファイルの設定が正しいか確認してください。 - ARB ファイルが正しい形式で、指定された場所に存在するか確認してください。
- Flutter SDK が最新版であることを確認してください。
- 生成されたファイルの実際の場所を確認し、必要に応じてインポートパスを調整してください。
flutter gen-l10n
コマンドは、多言語化プロセスの重要な部分です。このコマンドを適切に使用することで、アプリケーションの多言語化が大幅に簡素化され、効率的になります。ただし、生成されたファイルの場所が予期せぬところにある可能性があるため、常に実際の生成場所を確認することが重要です。
5. main.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 {
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'), // デフォルト言語を英語に設定
localeResolutionCallback: (locale, supportedLocales) {
if (locale == null) {
return supportedLocales.first;
}
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
return supportedLocale;
}
}
return supportedLocales.first;
},
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.helloWorld),
),
body: Center(
child: Text(AppLocalizations.of(context)!.welcomeMessage),
),
);
}
}
パート 2: Riverpod の導入
1. Riverpod の依存関係追加
pubspec.yaml
ファイルに以下の依存関係を追加します:
dependencies:
flutter_riverpod: ^2.1.3
riverpod_annotation: ^2.0.2
shared_preferences: ^2.0.15
dev_dependencies:
build_runner: ^2.3.3
riverpod_generator: ^2.1.4
2. LocaleProvider の実装
lib/providers/locale_provider.dart
ファイルを作成し、以下の内容を追加します:
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'locale_provider.g.dart';
class LocaleNotifier extends _$LocaleNotifier {
static const String _kLanguageCode = 'languageCode';
Locale build() {
_loadLocale();
return const Locale('en');
}
Future<void> _loadLocale() async {
final prefs = await SharedPreferences.getInstance();
final String languageCode = prefs.getString(_kLanguageCode) ?? 'en';
state = Locale(languageCode);
}
Future<void> setLocale(Locale locale) async {
if (!['en', 'ja'].contains(locale.languageCode)) return;
state = locale;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_kLanguageCode, locale.languageCode);
}
}
3. Riverpod のコード生成
Riverpod を使用することで、より簡潔で再利用しやすいコードを書くことができます。
build_runner の実行
LocaleProvider を作成した後、以下のコマンドを実行して Riverpod の生成コードを作成します:
dart run build_runner build --delete-conflicting-outputs
このコマンドは、@riverpod
アノテーションが付いたクラスに対して必要なコードを生成します。
--delete-conflicting-outputs
オプションの説明:
- このオプションは、既存の生成ファイルと競合がある場合に、古いファイルを削除して新しいファイルを生成します。
- 特に、コードの大幅な変更や、生成ファイルの手動修正を行った後に有用です。
- ただし、生成ファイルに手動で加えた変更がある場合、それらの変更が失われる可能性があるので注意が必要です。
生成されるファイル
コマンドを実行すると、locale_provider.g.dart
というファイルが生成されます。このファイルには、LocaleNotifier クラスの実装に必要な補助コードが含まれています。
注意点
- コードを変更するたびに
dart run build_runner build --delete-conflicting-outputs
コマンドを再実行する必要があります。 - 継続的に監視して自動生成したい場合は、以下のコマンドを使用できます:このコマンドは変更を監視し、必要に応じて自動的にファイルを再生成します。
dart run build_runner watch
トラブルシューティング
生成に問題がある場合:
-
pubspec.yaml
ファイルに必要な依存関係(riverpod_generator
とbuild_runner
)が正しく記載されているか確認してください。 -
part
ディレクティブが正しく記述されているか確認してください(例:part 'locale_provider.g.dart';
)。 - クラス名の前に
@riverpod
アノテーションが正しく付けられているか確認してください。 - 生成プロセスで問題が発生する場合は、プロジェクトをクリーンしてから再試行してください:
flutter clean flutter pub get dart run build_runner build --delete-conflicting-outputs
Riverpod のコード生成を活用することで、ボイラープレートコードを減らし、より保守性の高いコードを書くことができます。また、コンパイル時の型チェックが強化され、ランタイムエラーのリスクを減らすことができます。--delete-conflicting-outputs
オプションを使用することで、生成プロセスがよりクリーンになり、競合を避けることができます。
4. main.dart の更新
main.dart
ファイルを以下のように更新します:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:your_app/providers/locale_provider.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeNotifierProvider);
return MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: locale,
localeResolutionCallback: (locale, supportedLocales) {
if (locale == null) {
return supportedLocales.first;
}
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode) {
return supportedLocale;
}
}
return supportedLocales.first;
},
home: MyHomePage(),
);
}
}
5. 言語切り替えウィジェットの更新
LanguageSwitcher
ウィジェットを以下のように更新します:
class LanguageSwitcher extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeNotifierProvider);
return DropdownButton<String>(
value: locale.languageCode,
items: [
DropdownMenuItem(value: 'en', child: Text('English')),
DropdownMenuItem(value: 'ja', child: Text('日本語')),
],
onChanged: (String? languageCode) {
if (languageCode != null) {
ref.read(localeNotifierProvider.notifier).setLocale(Locale(languageCode));
}
},
);
}
}
主な設定の解説
-
localizationsDelegates:
- これらのデリゲートは、アプリケーションに必要な翻訳リソースを提供します。
-
AppLocalizations.delegate
: アプリ固有の翻訳を提供します。 -
GlobalMaterialLocalizations.delegate
: Material Design ウィジェットの標準的な翻訳を提供します。 -
GlobalWidgetsLocalizations.delegate
: 基本的な Flutter ウィジェットの翻訳を提供します。 -
GlobalCupertinoLocalizations.delegate
: iOS スタイルのウィジェットの翻訳を提供します。
-
supportedLocales:
- アプリケーションがサポートする言語とリージョンのリストを指定します。
-
AppLocalizations.supportedLocales
を使用することで、ARB ファイルで定義したすべての言語が自動的に含まれます。
-
locale:
- 現在のアプリケーションの言語設定を指定します。
- Riverpod の
localeNotifierProvider
から取得した値を使用しています。
-
localeResolutionCallback:
- デバイスの言語設定に基づいて、アプリケーションが使用する適切な言語を選択するためのコールバック関数です。
- デバイスの言語がサポートされている場合はその言語を使用し、そうでない場合は最初のサポートされている言語を使用します。
新しい文字の追加
アプリケーションの開発が進むにつれて、新しい文字を追加する必要が出てきます。以下に、新しい文字を追加する手順を説明します。
1. ARB ファイルの更新
まず、lib/l10n
ディレクトリ内の各言語の ARB ファイルに新しい文字を追加します。
例えば、「設定」という文字を追加する場合:
app_en.arb:
{
"helloWorld": "Hello World!",
"welcomeMessage": "Welcome to Flutter!",
"settings": "Settings"
}
app_ja.arb:
{
"helloWorld": "こんにちは、世界!",
"welcomeMessage": "Flutterへようこそ!",
"settings": "設定"
}
2. flutter gen-l10n の実行
ARB ファイルを更新したら、以下のコマンドを実行して翻訳ファイルを再生成します:
flutter gen-l10n
3. Riverpod 関連ファイルの再生成 (任意)
Riverpod を使用している場合、念のため以下のコマンドも実行します:
dart run build_runner build --delete-conflicting-outputs
4. 新しい文字の使用
生成されたファイルが更新されたら、新しい文字を以下のように使用できます:
Text(AppLocalizations.of(context)!.settings)
注意点
- すべての言語の ARB ファイルに新しい文字を追加することを忘れないでください。
- 文字キー(例:
settings
)は全ての ARB ファイルで同じである必要があります。 - 大規模なプロジェクトでは、未翻訳の文字を追跡するシステムを導入することを検討してください。
効率的な作業のためのヒント
-
ARB ファイルのテンプレート(通常は英語版)を最新の状態に保ち、他の言語のファイルはこれをベースに更新します。
-
翻訳作業を定期的に行い、大量の未翻訳文字が蓄積しないようにします。
-
コンテキストや説明を ARB ファイルに含めることで、翻訳者がより正確に翻訳できるようサポートします:
{ "settings": "Settings", "@settings": { "description": "Label for the settings menu item" } }
-
必要に応じて、プレースホルダーを使用して動的な文字を作成します:
{ "greeting": "Hello, {name}!", "@greeting": { "description": "Greeting message with user's name", "placeholders": { "name": { "type": "String", "example": "John" } } } }
使用例:
Text(AppLocalizations.of(context)!.greeting('Alice'))
新しい翻訳文字を追加する際は、これらの手順に従うことで、アプリケーションの多言語化を効率的に管理できます。
効率化
生成されたファイルが更新されたら、新しい文字を使用できます。AppLocalizations
のインスタンスを効率的に使用するには、以下のアプローチを推奨します:
class MyLocalizedWidget extends StatelessWidget {
Widget build(BuildContext context) {
// AppLocalizations のインスタンスを一度取得
final l10n = AppLocalizations.of(context)!;
return Column(
children: [
Text(l10n.helloWorld),
Text(l10n.welcomeMessage),
Text(l10n.settings),
// 他のローカライズされたテキスト
],
);
}
}
このアプローチの利点:
- コードの可読性が向上します。
-
AppLocalizations.of(context)!
の繰り返しを避けられます。 - パフォーマンスが僅かに向上する可能性があります(特に多数のローカライズされたテキストを使用する場合)。
注意点:
- この方法は、同じ
build
メソッド内で複数のローカライズされたテキストを使用する場合に特に有効です。 - 異なるウィジェットや、
build
メソッド外でローカライズされたテキストを使用する場合は、それぞれの場所でAppLocalizations.of(context)!
を呼び出す必要があります。
プレースホルダーを含む文字の使用例:
Text(l10n.greeting('Alice'))
この方法を採用することで、コードはより簡潔になり、管理しやすくなります。特に多数のローカライズされたテキストを使用するウィジェットでは、この方法が効果的です。
参考リンク
Discussion