ponshu-room-lite/docs/ARB_MIGRATION_GUIDE.md

13 KiB
Raw Blame History

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個のキー
};

問題点:

  1. ✗ ファイルが肥大化 (200キー超えると500行以上)
  2. ✗ 翻訳者がDartコードを触る必要がある
  3. ✗ パラメータ埋め込みが弱い ("Welcome, ${name}" は手動実装)
  4. ✗ 複数形対応が困難 (1 item vs 2 items)
  5. ✗ IDE補完が弱い (タイポに気づきにくい)

ARB形式のメリット

// 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 を作成

# 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. 手動テスト

  1. アプリを起動
  2. 設定で言語を「English」に変更
  3. 全画面を確認
  4. パラメータ付き翻訳が正しく動作するか確認

📊 移行前後の比較

項目 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"}
  }
}

📚 参考資料


チェックリスト

移行完了時に確認:

  • 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実装で十分)