# ARBファイル移行ガイド **対象**: 現在のMap-based翻訳システム → ARB (Application Resource Bundle) への移行 **推奨タイミング**: 翻訳キーが100個を超えたとき、または3言語目追加時 --- ## 🎯 なぜARBへ移行すべきか? ### 現在の実装 (Map-based) の限界 ```dart // lib/utils/translations.dart (109行) static const Map> _translations = { 'home': {'ja': 'ホーム', 'en': 'Home'}, 'save': {'ja': '保存', 'en': 'Save'}, // ... 61個のキー }; ``` **問題点:** 1. ✗ ファイルが肥大化 (200キー超えると500行以上) 2. ✗ 翻訳者がDartコードを触る必要がある 3. ✗ パラメータ埋め込みが弱い (`"Welcome, ${name}"` は手動実装) 4. ✗ 複数形対応が困難 (`1 item` vs `2 items`) 5. ✗ IDE補完が弱い (タイポに気づきにくい) ### ARB形式のメリット ```json // lib/l10n/app_ja.arb { "@@locale": "ja", "welcomeMessage": "ようこそ、{name}さん", "@welcomeMessage": { "description": "ユーザーへの歓迎メッセージ", "placeholders": { "name": { "type": "String" } } } } ``` **メリット:** 1. ✓ 翻訳者フレンドリー (JSON形式、説明付き) 2. ✓ Flutter公式標準 (長期サポート保証) 3. ✓ 型安全な自動生成コード 4. ✓ パラメータ・複数形対応 5. ✓ IDE補完が効く 6. ✓ 翻訳管理ツールと連携可能 (Crowdin, Lokaliseなど) --- ## 📋 移行手順 (推定時間: 2-3時間) ### Step 1: 設定ファイル作成 (5分) #### 1-1. `l10n.yaml` を作成 ```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` を更新 ```yaml # pubspec.yaml flutter: generate: true # この行を追加 uses-material-design: true assets: - assets/ ``` --- ### Step 2: 既存の翻訳をARBファイルに変換 (30分) #### 2-1. `lib/l10n/app_ja.arb` を作成 ```json { "@@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` を作成 ```json { "@@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) ```dart // 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ファイル生成完了'); } ``` 実行: ```bash dart run tools/convert_to_arb.dart ``` --- ### Step 3: コード生成 (5分) ```bash # 依存パッケージ取得 & コード生成 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` を更新 ```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) ```dart 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) ```dart 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('太郎')), // パラメータ対応 ); } } ``` #### 一括置換パターン ```regex # 検索 t\['(\w+)'\] # 置換 l10n.$1 ``` --- ### Step 6: 旧コードの削除 (5分) ```bash # translations.dartを削除 rm lib/utils/translations.dart # Gitでコミット git add . git commit -m "feat: Migrate to ARB-based localization" ``` --- ## 🎯 移行後の使い方 ### 基本的な使い方 ```dart // BuildContext経由でアクセス final l10n = AppLocalizations.of(context)!; Text(l10n.home) // "ホーム" or "Home" Text(l10n.save) // "保存" or "Save" ``` ### パラメータ付き翻訳 ```dart // 1つのパラメータ Text(l10n.welcomeMessage('太郎')) // "ようこそ、太郎さん" // 複数のパラメータ Text(l10n.dateRange(startDate, endDate)) ``` ### 複数形対応 ```dart // 件数に応じて自動切り替え Text(l10n.itemCount(0)) // "お酒がありません" Text(l10n.itemCount(1)) // "1件のお酒" Text(l10n.itemCount(5)) // "5件のお酒" ``` ### 日付・通貨フォーマット ```dart 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. 翻訳漏れチェック ```dart // 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: '翻訳漏れがあります'); }); ``` 実行: ```bash flutter test test/l10n_test.dart ``` ### 2. 手動テスト 1. アプリを起動 2. 設定で言語を「English」に変更 3. 全画面を確認 4. パラメータ付き翻訳が正しく動作するか確認 --- ## 📊 移行前後の比較 | 項目 | Map-based (現在) | ARB-based (移行後) | |------|------------------|-------------------| | **ファイル構成** | 1ファイル (109行) | 2ファイル (ja/en) | | **翻訳者の作業** | Dartコードを編集 | JSONファイルのみ | | **パラメータ埋め込み** | 手動実装 | 自動対応 | | **複数形** | 非対応 | 完全対応 | | **型安全性** | 弱い (String返却) | 強い (生成コード) | | **IDE補完** | なし | あり | | **翻訳管理ツール** | 非対応 | 対応 | | **学習コスト** | 低い | 中程度 | | **長期メンテナンス** | 困難 (肥大化) | 容易 | --- ## 🚨 注意事項 ### 1. BuildContextが必要 ```dart // ❌ NG: Providerから直接アクセス不可 final localeProvider = Provider((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プロバイダーから言語コードを取得して分岐: ```dart final locale = ref.watch(localeProvider); final text = locale.languageCode == 'en' ? 'Home' : 'ホーム'; ``` ### 2. 生成ファイルはGit管理不要 ```.gitignore # ARB生成ファイルは無視 lib/l10n/generated/ ``` ### 3. ビルド時に自動生成される ```bash # pubspec.yamlを変更したら必ず実行 flutter pub get ``` --- ## 🔧 トラブルシューティング ### Q1: `AppLocalizations`が見つからない **原因**: コード生成されていない **解決策**: ```bash flutter clean flutter pub get flutter pub run build_runner build --delete-conflicting-outputs ``` ### Q2: 翻訳が反映されない **原因**: ARBファイルの構文エラー **解決策**: JSONバリデーターでチェック ```bash # jqコマンドでバリデーション jq . lib/l10n/app_ja.arb ``` ### Q3: パラメータが表示されない **原因**: プレースホルダー定義が不足 **解決策**: `@` で始まるメタデータを追加 ```json "welcomeMessage": "Welcome, {name}", "@welcomeMessage": { "placeholders": { "name": {"type": "String"} } } ``` --- ## 📚 参考資料 - [Flutter公式: Internationalizing Flutter apps](https://docs.flutter.dev/ui/accessibility-and-localization/internationalization) - [ARB Format Specification](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification) - [intl Package](https://pub.dev/packages/intl) - [flutter_localizations](https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html) --- ## ✅ チェックリスト 移行完了時に確認: - [ ] 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実装で十分)