13 KiB
13 KiB
ARBファイル移行ガイド
対象: 現在のMap-based翻訳システム → ARB (Application Resource Bundle) への移行 推奨タイミング: 翻訳キーが100個を超えたとき、または3言語目追加時
🎯 なぜARBへ移行すべきか?
現在の実装 (Map-based) の限界
// lib/utils/translations.dart (109行)
static const Map<String, Map<String, String>> _translations = {
'home': {'ja': 'ホーム', 'en': 'Home'},
'save': {'ja': '保存', 'en': 'Save'},
// ... 61個のキー
};
問題点:
- ✗ ファイルが肥大化 (200キー超えると500行以上)
- ✗ 翻訳者がDartコードを触る必要がある
- ✗ パラメータ埋め込みが弱い (
"Welcome, ${name}"は手動実装) - ✗ 複数形対応が困難 (
1 itemvs2 items) - ✗ IDE補完が弱い (タイポに気づきにくい)
ARB形式のメリット
// lib/l10n/app_ja.arb
{
"@@locale": "ja",
"welcomeMessage": "ようこそ、{name}さん",
"@welcomeMessage": {
"description": "ユーザーへの歓迎メッセージ",
"placeholders": {
"name": { "type": "String" }
}
}
}
メリット:
- ✓ 翻訳者フレンドリー (JSON形式、説明付き)
- ✓ Flutter公式標準 (長期サポート保証)
- ✓ 型安全な自動生成コード
- ✓ パラメータ・複数形対応
- ✓ IDE補完が効く
- ✓ 翻訳管理ツールと連携可能 (Crowdin, Lokaliseなど)
📋 移行手順 (推定時間: 2-3時間)
Step 1: 設定ファイル作成 (5分)
1-1. l10n.yaml を作成
# l10n.yaml (プロジェクトルート)
arb-dir: lib/l10n
template-arb-file: app_ja.arb
output-localization-file: app_localizations.dart
output-dir: lib/l10n/generated
synthetic-package: false
1-2. pubspec.yaml を更新
# pubspec.yaml
flutter:
generate: true # この行を追加
uses-material-design: true
assets:
- assets/
Step 2: 既存の翻訳をARBファイルに変換 (30分)
2-1. lib/l10n/app_ja.arb を作成
{
"@@locale": "ja",
"_comment_navigation": "=== ナビゲーション ===",
"home": "ホーム",
"@home": {
"description": "ホームタブのラベル"
},
"scan": "スキャン",
"@scan": {
"description": "スキャンタブのラベル"
},
"sommelier": "ソムリエ",
"map": "マップ",
"myPage": "マイページ",
"promo": "販促",
"analytics": "分析",
"shop": "店舗",
"_comment_actions": "=== 共通アクション ===",
"save": "保存",
"cancel": "キャンセル",
"delete": "削除",
"close": "閉じる",
"ok": "OK",
"confirm": "確認",
"_comment_home": "=== ホーム画面 ===",
"menuCreation": "お品書き作成",
"searchPlaceholder": "銘柄・酒蔵・都道府県...",
"sort": "並び替え",
"_comment_params": "=== パラメータ付き ===",
"welcomeMessage": "ようこそ、{name}さん",
"@welcomeMessage": {
"description": "ユーザーへの歓迎メッセージ",
"placeholders": {
"name": {
"type": "String",
"example": "太郎"
}
}
},
"itemCount": "{count, plural, =0{お酒がありません} =1{{count}件のお酒} other{{count}件のお酒}}",
"@itemCount": {
"description": "お酒の件数表示",
"placeholders": {
"count": {
"type": "int",
"example": "5"
}
}
}
}
2-2. lib/l10n/app_en.arb を作成
{
"@@locale": "en",
"home": "Home",
"scan": "Scan",
"sommelier": "Sommelier",
"map": "Map",
"myPage": "My Page",
"promo": "Promo",
"analytics": "Analytics",
"shop": "Shop",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close",
"ok": "OK",
"confirm": "Confirm",
"menuCreation": "Menu Creation",
"searchPlaceholder": "Brand, Brewery, Prefecture...",
"sort": "Sort",
"welcomeMessage": "Welcome, {name}",
"itemCount": "{count, plural, =0{No sake} =1{1 sake} other{{count} sake}}"
}
2-3. 自動変換スクリプト (Optional)
// tools/convert_to_arb.dart
import 'dart:io';
import 'dart:convert';
void main() {
// translations.dartから読み込み
final translations = {
'home': {'ja': 'ホーム', 'en': 'Home'},
'save': {'ja': '保存', 'en': 'Save'},
// ... (現在の61キーをコピー)
};
// ARB形式に変換
final jaArb = {'@@locale': 'ja'};
final enArb = {'@@locale': 'en'};
translations.forEach((key, values) {
jaArb[key] = values['ja'];
enArb[key] = values['en'];
});
// ファイルに書き込み
File('lib/l10n/app_ja.arb').writeAsStringSync(
JsonEncoder.withIndent(' ').convert(jaArb)
);
File('lib/l10n/app_en.arb').writeAsStringSync(
JsonEncoder.withIndent(' ').convert(enArb)
);
print('✅ ARBファイル生成完了');
}
実行:
dart run tools/convert_to_arb.dart
Step 3: コード生成 (5分)
# 依存パッケージ取得 & コード生成
flutter pub get
# 自動的に lib/l10n/generated/app_localizations.dart が生成される
生成されるファイル:
lib/l10n/generated/
├── app_localizations.dart # メインクラス
├── app_localizations_ja.dart # 日本語実装
└── app_localizations_en.dart # 英語実装
Step 4: アプリ設定を更新 (10分)
4-1. main.dart を更新
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // 追加
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeProvider);
return MaterialApp(
// 追加: localizationsDelegates
localizationsDelegates: const [
AppLocalizations.delegate, // 追加
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
// 追加: supportedLocales
supportedLocales: const [
Locale('ja'),
Locale('en'),
],
locale: locale,
// ... 既存のコード
);
}
}
Step 5: 各画面のコードを置き換え (1-2時間)
Before (Map-based)
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userProfile = ref.watch(userProfileProvider);
final t = Translations(userProfile.locale);
return Scaffold(
appBar: AppBar(
title: Text(t['menuCreation']),
),
body: Text(t['welcomeMessage']), // パラメータ非対応
);
}
}
After (ARB-based)
class HomeScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.menuCreation),
),
body: Text(l10n.welcomeMessage('太郎')), // パラメータ対応
);
}
}
一括置換パターン
# 検索
t\['(\w+)'\]
# 置換
l10n.$1
Step 6: 旧コードの削除 (5分)
# translations.dartを削除
rm lib/utils/translations.dart
# Gitでコミット
git add .
git commit -m "feat: Migrate to ARB-based localization"
🎯 移行後の使い方
基本的な使い方
// BuildContext経由でアクセス
final l10n = AppLocalizations.of(context)!;
Text(l10n.home) // "ホーム" or "Home"
Text(l10n.save) // "保存" or "Save"
パラメータ付き翻訳
// 1つのパラメータ
Text(l10n.welcomeMessage('太郎')) // "ようこそ、太郎さん"
// 複数のパラメータ
Text(l10n.dateRange(startDate, endDate))
複数形対応
// 件数に応じて自動切り替え
Text(l10n.itemCount(0)) // "お酒がありません"
Text(l10n.itemCount(1)) // "1件のお酒"
Text(l10n.itemCount(5)) // "5件のお酒"
日付・通貨フォーマット
import 'package:intl/intl.dart';
// 日付
final formatter = DateFormat.yMd(l10n.localeName);
formatter.format(DateTime.now()); // "2026/01/20" (ja) or "1/20/2026" (en)
// 通貨
final currency = NumberFormat.currency(locale: l10n.localeName, symbol: '¥');
currency.format(1500); // "¥1,500"
🧪 移行後のテスト
1. 翻訳漏れチェック
// test/l10n_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'dart:convert';
import 'dart:io';
void test('All translation keys exist in all locales', () {
final jaFile = File('lib/l10n/app_ja.arb');
final enFile = File('lib/l10n/app_en.arb');
final jaKeys = (jsonDecode(jaFile.readAsStringSync()) as Map)
.keys
.where((k) => !k.startsWith('@') && !k.startsWith('_'))
.toSet();
final enKeys = (jsonDecode(enFile.readAsStringSync()) as Map)
.keys
.where((k) => !k.startsWith('@') && !k.startsWith('_'))
.toSet();
expect(jaKeys, equals(enKeys), reason: '翻訳漏れがあります');
});
実行:
flutter test test/l10n_test.dart
2. 手動テスト
- アプリを起動
- 設定で言語を「English」に変更
- 全画面を確認
- パラメータ付き翻訳が正しく動作するか確認
📊 移行前後の比較
| 項目 | Map-based (現在) | ARB-based (移行後) |
|---|---|---|
| ファイル構成 | 1ファイル (109行) | 2ファイル (ja/en) |
| 翻訳者の作業 | Dartコードを編集 | JSONファイルのみ |
| パラメータ埋め込み | 手動実装 | 自動対応 |
| 複数形 | 非対応 | 完全対応 |
| 型安全性 | 弱い (String返却) | 強い (生成コード) |
| IDE補完 | なし | あり |
| 翻訳管理ツール | 非対応 | 対応 |
| 学習コスト | 低い | 中程度 |
| 長期メンテナンス | 困難 (肥大化) | 容易 |
🚨 注意事項
1. BuildContextが必要
// ❌ NG: Providerから直接アクセス不可
final localeProvider = Provider<String>((ref) {
final l10n = AppLocalizations.of(???); // BuildContextがない!
return l10n.home;
});
// ✅ OK: Widget内でアクセス
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
return Text(l10n.home);
}
解決策: BuildContext不要な場面では、localeプロバイダーから言語コードを取得して分岐:
final locale = ref.watch(localeProvider);
final text = locale.languageCode == 'en' ? 'Home' : 'ホーム';
2. 生成ファイルはGit管理不要
# ARB生成ファイルは無視
lib/l10n/generated/
3. ビルド時に自動生成される
# pubspec.yamlを変更したら必ず実行
flutter pub get
🔧 トラブルシューティング
Q1: AppLocalizationsが見つからない
原因: コード生成されていない
解決策:
flutter clean
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
Q2: 翻訳が反映されない
原因: ARBファイルの構文エラー
解決策: JSONバリデーターでチェック
# jqコマンドでバリデーション
jq . lib/l10n/app_ja.arb
Q3: パラメータが表示されない
原因: プレースホルダー定義が不足
解決策: @ で始まるメタデータを追加
"welcomeMessage": "Welcome, {name}",
"@welcomeMessage": {
"placeholders": {
"name": {"type": "String"}
}
}
📚 参考資料
- Flutter公式: Internationalizing Flutter apps
- ARB Format Specification
- intl Package
- flutter_localizations
✅ チェックリスト
移行完了時に確認:
- l10n.yaml作成済み
- pubspec.yamlに
generate: true追加済み - app_ja.arb / app_en.arb作成済み
- flutter pub get実行済み
- 生成ファイル確認 (lib/l10n/generated/)
- main.dartにlocalizationsDelegates追加済み
- 全画面でt['key'] → l10n.key に置き換え済み
- 翻訳漏れテスト実行済み
- 手動テストで動作確認済み
- translations.dart削除済み
- Gitコミット済み
移行タイミングの目安:
- ✅ 今すぐ (基盤が整う)
- ✅ 翻訳キー100個超え時
- ✅ 3言語目追加時
- ❌ 現在 (まだ61キーのみ、Map実装で十分)