Skip to main content

Flutter 在地化完整指南

從 ARB 檔案到 RTL 支援:使用 easy_localization 在地化 Flutter 應用程式、處理每種語言的複數,並透過 AI 自動翻譯。

1

安裝 easy_localization

將 easy_localization 套件新增到 pubspec.yaml。這是最受歡迎的 Flutter i18n 套件,支援 ARB/JSON 檔案、複數和上下文擴充套件。還應從 SDK 新增 flutter_localizations,以根據語系格式化日期、數字和文字方向。

easy_localization 提供 context.tr() 和 'key'.tr() 等上下文擴充套件,可簡潔地訪問翻譯。它負責語系載入、持久化,以及開發期間翻譯檔案的熱重載。
pubspec.yaml
dependencies:
  easy_localization: ^3.0.7
  flutter_localizations:
    sdk: flutter
2

建立 ARB 翻譯檔案

Application Resource Bundle(ARB)檔案是 Flutter 的標準在地化格式。每個檔案都包含鍵值對,以及用於描述預留位置、複數規則和譯者上下文的可選元資料。在 assets/translations 目錄中為每個語系建立一個檔案。

assets/translations/en.arb
{
  "@@locale": "en",
  "appTitle": "My App",
  "@appTitle": {
    "description": "The title of the application"
  },
  "greeting": "Hello, {name}!",
  "@greeting": {
    "description": "Greeting with user name",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "Alice"
      }
    }
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Number of items in the cart",
    "placeholders": {
      "count": {
        "type": "num",
        "format": "compact"
      }
    }
  }
}
以 @ 為前綴的鍵(例如 @greeting)是元資料,用於描述其上方的字串。請包含預留位置類型和示例,幫助譯者提供準確翻譯。這些元資料鍵會在執行階段被移除,不會產生任何開銷。
3

設定應用程式

使用 EasyLocalization widget 包裝應用程式。它管理語系狀態、從資源檔案載入翻譯,並提供 MaterialApp 所需的語系委託。三個必需的委託屬性是 localizationsDelegates、supportedLocales 和 locale,均可透過上下文擴充套件獲得。

lib/main.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

  runApp(
    EasyLocalization(
      supportedLocales: [
        Locale('en'),
        Locale('ar'),
        Locale('ja'),
        Locale('de'),
      ],
      path: 'assets/translations',  // Path to your ARB/JSON files
      fallbackLocale: Locale('en'),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: HomePage(),
    );
  }
}
「Easy Localization not initialized」——此錯誤表示您忘記在 runApp() 之前呼叫 EasyLocalization.ensureInitialized()。必須在 WidgetsFlutterBinding.ensureInitialized() 之後等待它完成,再呼叫 runApp()。
4

翻譯 widget

對字串鍵使用 .tr() 擴充方法,即可在任意 widget 中取得翻譯文字。對於複數,請將數量值傳給 .plural()。easy_localization 同時提供字串擴充語法('key'.tr())和上下文方法語法(context.tr('key'))。

lib/widgets/home_page.dart
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appTitle'.tr()),   // Simple key
      ),
      body: Column(
        children: [
          // String with variable interpolation
          Text('greeting'.tr(args: ['Alice'])),

          // Named arguments
          Text('greeting'.tr(namedArgs: {'name': 'Alice'})),

          // Plural form
          Text('itemCount'.plural(3)),
        ],
      ),
    );
  }
}
如果翻譯傳回原始鍵而非翻譯文字,請檢查:1)EasyLocalization widget 應包裝 MaterialApp,而非相反;2)翻譯檔案是否已在 pubspec.yaml 的 assets 下聲明;3)EasyLocalization 中的檔案路徑是否與實際目錄結構匹配。
5

處理複數和變數

Flutter 使用 ICU MessageFormat 處理複數,這與 iOS、Android 和 Web 使用的標準相同。在 ARB 檔案中使用 {count, plural, ...} 語法定義複數形式。每種語言都需要根據 CLDR 複數規則定義自己的形式。阿拉伯語有 6 種形式,俄語有 4 種,日語有 1 種。

Plural forms by language
// English: 3 useful forms (=0, =1, other)
"itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}"

// Arabic: 6 forms (=0, =1, =2, few, many, other)
"itemCount": "{count, plural, =0{لا عناصر} =1{عنصر واحد} =2{عنصران} few{{count} عناصر} many{{count} عنصرًا} other{{count} عنصر}}"

// Japanese: 1 form (other)
"itemCount": "{count, plural, other{{count}個のアイテム}}"

// German: 2 forms (=1, other)
"itemCount": "{count, plural, =1{1 Artikel} other{{count} Artikel}}"

// Russian: 3 forms (one, few, many)
"itemCount": "{count, plural, =0{Нет товаров} one{{count} товар} few{{count} товара} many{{count} товаров} other{{count} товаров}}"
Variables and named arguments
// Simple variable
"welcome": "Welcome, {name}!"

// Multiple variables
"orderStatus": "Order #{orderId} — {status}"

// Variable in plural context
"unreadMessages": "{count, plural, =0{No unread messages} =1{1 unread message from {sender}} other{{count} unread messages}}"

// In widgets:
Text('welcome'.tr(namedArgs: {'name': userName}));
Text('orderStatus'.tr(namedArgs: {'orderId': '1234', 'status': 'Shipped'}));
Text('unreadMessages'.plural(count, namedArgs: {'sender': senderName}));
切勿使用 if (count == 1) 硬編碼單數/複數邏輯。法語等語言將 0 視為單數。俄語、波蘭語和阿拉伯語具有英語完全沒有的複數形式。請始終使用 ICU 複數語法,讓框架選擇正確形式。
6

支援 RTL 語言

當語系為從右到左書寫的語言(阿拉伯語、希伯來語、波斯語、烏爾都語)時,Flutter 會自動鏡像整個版面配置。但程式碼必須使用可識別方向的 widget 和屬性,才能正確完成鏡像。請將硬編碼的 left/right 替換為對應的 start/end。

lib/widgets/adaptive_layout.dart
import 'package:flutter/material.dart';

class AdaptiveLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Scaffold(
      body: Row(
        children: [
          // Use start/end instead of left/right
          Expanded(
            child: Padding(
              padding: EdgeInsetsDirectional.only(
                start: 16.0,  // Leading edge (left in LTR, right in RTL)
                end: 8.0,     // Trailing edge
              ),
              child: Text('content'.tr()),
            ),
          ),
          // Flip icons for RTL
          Icon(
            isRtl ? Icons.arrow_back : Icons.arrow_forward,
          ),
        ],
      ),
    );
  }
}
RTL-aware widget patterns
// Use Directional widgets for RTL-aware layouts
EdgeInsetsDirectional.only(start: 16, end: 8)   // Not EdgeInsets.only(left: 16, right: 8)
AlignmentDirectional.centerStart                  // Not Alignment.centerLeft
BorderRadiusDirectional.only(topStart: Radius.circular(8))

// Automatically mirrors when locale changes to Arabic, Hebrew, etc.
// No conditional logic needed — Flutter handles text direction from locale
暫時將應用程式語系為阿拉伯語(Locale('ar'))以測試 RTL。Flutter 會鏡像整個 UI:導覽抽屜從右側打開、傳回箭頭翻轉、文字右對齊。使用 EdgeInsetsDirectional、AlignmentDirectional 和 BorderRadiusDirectional,確保自訂版面配置正確鏡像。
7

智能語系回退鏈

預設情況下,當 pt-BR 翻譯缺失時,Flutter 會直接回退到英語,並跳過完全可用的 pt-PT 翻譯。locale_chain 套件透過可設定回退鏈解決此問題。只需一行設定,無需遷移,現有 .tr() 呼叫即可正常工作。

lib/main.dart with LocaleChain
import 'package:locale_chain/locale_chain.dart';
import 'package:locale_chain/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
  LocaleChain.configure();  // Enable smart fallback chains

  runApp(
    EasyLocalization(
      supportedLocales: [
        Locale('en'),
        Locale('pt', 'BR'),
        Locale('pt', 'PT'),
        Locale('es'),
        Locale('es', 'MX'),
      ],
      path: 'assets/translations',
      assetLoader: LocaleChainAssetLoader(  // Swap in the chain loader
        baseLoader: RootBundleAssetLoader(),
      ),
      fallbackLocale: Locale('en'),
      child: MyApp(),
    ),
  );
}

// Result: pt-BR user sees pt-PT translations when pt-BR keys are missing,
// instead of falling back directly to English.
Custom fallback configuration
// Override specific chains while keeping built-in defaults
LocaleChain.configure(
  fallbacks: {
    'pt-BR': ['pt-PT', 'pt'],      // pt-BR → pt-PT → pt → en
    'es-MX': ['es-419', 'es'],     // es-MX → es-419 → es → en
    'fr-CA': ['fr'],               // fr-CA → fr → en
  },
);

// Or replace all defaults with your own chains
LocaleChain.configure(
  fallbacks: {
    'zh-Hant-HK': ['zh-Hant-TW', 'zh-Hans'],
    'pt-BR': ['pt-PT', 'pt'],
  },
  mergeDefaults: false,  // Only your chains, no built-in defaults
);
locale_chain 內建葡萄牙語、西班牙語、法語、德語、意大利語、荷蘭語、挪威語和馬來語地區變體的回退鏈。pt-BR 使用者會先看到 pt-PT 內容,再看到英語;es-MX 使用者則會依次看到 es-419、es 和預設語系。
8

自動翻譯

完成在地化設定後,使用 AI 翻譯 ARB 檔案。在 IDE 中讓 AI 助手翻譯源 ARB 檔案,或在 CI/CD 管線中使用 i18n Agent CLI。ARB 元資料(預留位置、說明)可提供上下文,從而提升翻譯品質。

Terminal
# In your IDE, ask your AI assistant:
> Translate assets/translations/en.arb to Arabic, Japanese, and German

✓ ar.arb created (1.4s)
✓ ja.arb created (1.2s)
✓ de.arb created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate assets/translations/en.arb --lang ar,ja,de
採用漸進式翻譯——向源 ARB 檔案新增新鍵時,只翻譯差異內容,不要重新產生所有檔案。這樣可保留經人工審核的翻譯,並使 ARB 元資料保持不變。

自動保證翻譯品質

使用 i18n-validate 在發佈前發現缺失鍵和損壞的預留位置。真實譯文完成前,可使用 i18n-pseudo 產生偽譯文來測試 UI。

常見問題

執行階段找不到翻譯檔案

必須在 pubspec.yaml 的 assets 部分聲明 ARB/JSON 檔案。缺少此步驟時,即使檔案存在於磁盤上,Flutter 也無法在執行階段找到它們。新增:assets: - assets/translations/

ARB 檔案語法無效

ARB 檔案是嚴格的 JSON,不允許尾隨逗號、單引號或注釋。一個語法錯誤就會靜默阻止整個檔案載入。在調試翻譯問題前,請使用 JSON 檢查工具驗證 ARB 檔案。

熱重載後未顯示翻譯更改

easy_localization 會在記憶體中快取翻譯。新增新鍵或更改現有翻譯後,可能需要完整熱重啟(而非熱重載)才能生效。在開發期間,編輯翻譯檔案後請使用熱重啟(Shift+R)。

硬編碼左/右方向導致 RTL 版面配置異常

使用 EdgeInsets.only(left: 16) 而不是 EdgeInsetsDirectional.only(start: 16),會阻止 Flutter 為 RTL 語言鏡像版面配置。在程式碼庫中搜尋不帶 Directional 後綴的 EdgeInsets、Alignment 和 BorderRadius。

推薦的檔案結構

Project Structure
my_flutter_app/
├── assets/
│   └── translations/
│       ├── en.arb          # Source language (English)
│       ├── ar.arb          # Arabic (with 6 plural forms)
│       ├── de.arb          # German
│       ├── ja.arb          # Japanese
│       ├── pt-BR.arb       # Brazilian Portuguese
│       └── pt-PT.arb       # European Portuguese
├── lib/
│   ├── main.dart           # App entry with EasyLocalization
│   ├── app.dart            # MaterialApp with locale delegates
│   └── widgets/
│       └── language_switcher.dart
├── pubspec.yaml            # Dependencies
└── analysis_options.yaml

立即試用 i18n Agent

將翻譯檔案拖放到此處

JSON, YAML, PO, XML, CSV, Markdown, Properties

或點擊選擇檔案

目標語言

無需註冊即時估價

使用 locale_chain 實現語系回退

當 pt-BR 等地區語系缺少翻譯鍵時,Flutter 會直接跳到範本語言,而不會先檢查父級語系 pt。

Terminal
flutter pub add locale_chain
Configuration
import 'package:locale_chain/locale_chain.dart';

LocaleChain.configure(fallbacks: {
  'pt-BR': ['pt', 'en'],
  'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
});

LocaleChainAssetLoader(baseLoader: RootBundleAssetLoader());

查看語言回退指南,瞭解受支援框架的完整列表和 75 條內建回退鏈。 Learn more →

常見問題