跳到主內容

國際化 Flutter 應用

如何國際化你的 Flutter 應用。

如果你的應用可能會部署給說其他語言的使用者,那麼你需要將其國際化。這意味著你需要以一種能夠為應用支援的每種語言或區域設定本地化文字和佈局等值的方式來編寫應用。Flutter 提供了有助於國際化的元件和類,並且 Flutter 庫本身也已國際化。

本頁涵蓋了使用 MaterialAppCupertinoApp 類來本地化 Flutter 應用所需的概念和工作流,因為大多數應用都是以這種方式編寫的。不過,使用更底層的 WidgetsApp 類編寫的應用也可以使用相同的類和邏輯進行國際化。

Flutter 本地化簡介

#

本節提供了一個關於如何建立和國際化新的 Flutter 應用的教程,以及目標平臺可能需要的任何額外設定。

你可以在 gen_l10n_example 中找到此示例的原始碼。

設定國際化應用:flutter_localizations 包

#

預設情況下,Flutter 僅提供美式英語的本地化。要新增對其他語言的支援,應用必須指定額外的 MaterialApp(或 CupertinoApp)屬性,幷包含一個名為 flutter_localizations 的包。

首先,使用 flutter create 命令在你選擇的目錄中建立一個新的 Flutter 應用。

flutter create <name_of_flutter_app>

要使用 flutter_localizations,請將該包作為依賴項新增到你的 pubspec.yaml 檔案中,同時新增 intl 包。

flutter pub add flutter_localizations --sdk=flutter
flutter pub add intl:any

這會建立一個包含以下條目的 pubspec.yaml 檔案。

yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

然後匯入 flutter_localizations 庫,併為你的 MaterialAppCupertinoApp 指定 localizationsDelegatessupportedLocales

dart
import 'package:flutter_localizations/flutter_localizations.dart';
dart
return const MaterialApp(
  title: 'Localizations Sample App',
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    Locale('en'), // English
    Locale('es'), // Spanish
  ],
  home: MyHomePage(),
);

引入 flutter_localizations 包並新增上述程式碼後,MaterialCupertino 包現在應該可以在支援的區域設定之一中正確本地化了。元件應該會適應本地化的訊息,並調整為正確的從左到右或從右到左的佈局。

嘗試將目標平臺的區域設定切換為西班牙語 (es),訊息應該會被本地化。

基於 WidgetsApp 的應用類似,只是不需要 GlobalMaterialLocalizations.delegate

建議使用完整的 Locale.fromSubtags 建構函式,因為它支援 scriptCode,儘管 Locale 的預設建構函式也是完全有效的。

localizationsDelegates 列表中的元素是生成本地化值集合的工廠。GlobalMaterialLocalizations.delegate 為 Material Components 庫提供本地化字串和其他值。GlobalWidgetsLocalizations.delegate 為元件庫定義預設的文字方向,即從左到右或從右到左。

關於這些應用屬性、它們所依賴的型別,以及國際化 Flutter 應用的典型結構,本頁提供了更多資訊。

覆蓋區域設定 (Locale)

#

Localizations.overrideLocalizations 元件的工廠建構函式,它允許應對(通常很少見的)應用的一部分需要本地化為與裝置配置的區域設定不同的情況。

要觀察這種行為,請新增對 Localizations.override 的呼叫以及一個簡單的 CalendarDatePicker

dart
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text(widget.title)),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // Add the following code
          Localizations.override(
            context: context,
            locale: const Locale('es'),
            // Using a Builder to get the correct BuildContext.
            // Alternatively, you can create a new widget and Localizations.override
            // will pass the updated BuildContext to the new widget.
            child: Builder(
              builder: (context) {
                // A toy example for an internationalized Material widget.
                return CalendarDatePicker(
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1900),
                  lastDate: DateTime(2100),
                  onDateChanged: (value) {},
                );
              },
            ),
          ),
        ],
      ),
    ),
  );
}

熱過載應用,CalendarDatePicker 元件應該會以西班牙語重新渲染。

新增自定義的本地化訊息

#

新增 flutter_localizations 包後,你可以配置本地化。要將本地化文字新增到你的應用中,請完成以下說明:

  1. 新增 intl 包作為依賴項,引入由 flutter_localizations 鎖定的版本。

    flutter pub add intl:any
    
  2. 開啟 pubspec.yaml 檔案並啟用 generate 標誌。此標誌位於 pubspec 檔案的 flutter 部分。

    yaml
    # The following section is specific to Flutter.
    flutter:
      generate: true # Add this line
    
  3. 在 Flutter 專案的根目錄下新增一個新的 yaml 檔案。將此檔案命名為 l10n.yaml 幷包含以下內容:

    yaml
    arb-dir: lib/l10n
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart
    

    此檔案配置了本地化工具。在本例中,你執行了以下操作:

    • 應用資源包 (.arb) 輸入檔案放置在 ${FLUTTER_PROJECT}/lib/l10n 中。.arb 檔案為你的應用提供本地化資源。
    • 將英語模板設定為 app_en.arb
    • 告訴 Flutter 在 app_localizations.dart 檔案中生成本地化。
  4. ${FLUTTER_PROJECT}/lib/l10n 中,新增 app_en.arb 模板檔案。例如:

    json
    {
      "helloWorld": "Hello World!",
      "@helloWorld": {
        "description": "The conventional newborn programmer greeting"
      }
    }
    
  5. 在同一個目錄下新增另一個名為 app_es.arb 的包檔案。在此檔案中,新增相同訊息的西班牙語翻譯。

    json
    {
        "helloWorld": "¡Hola Mundo!"
    }
    
  6. 現在,執行 flutter pub getflutter run,程式碼生成會自動執行。你應該可以在透過 arb-diroutput-dir 選項指定的路徑目錄中找到生成的檔案。此外,你也可以執行 flutter gen-l10n 在不執行應用的情況下生成相同的檔案。

  7. MaterialApp 的建構函式呼叫中,匯入 app_localizations.dart 並新增 AppLocalizations.delegate

    dart
    import 'l10n/app_localizations.dart';
    
    dart
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: [
        AppLocalizations.delegate, // Add this line
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // English
        Locale('es'), // Spanish
      ],
      home: MyHomePage(),
    );
    

    AppLocalizations 類還提供了自動生成的 localizationsDelegatessupportedLocales 列表。你可以使用這些列表,而不必手動提供它們。

    dart
    const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
    );
    
  8. Material 應用啟動後,你可以在應用的任何位置使用 AppLocalizations

    dart
    appBar: AppBar(
      // The [AppBar] title text should update its message
      // according to the system locale of the target platform.
      // Switching between English and Spanish locales should
      // cause this text to update.
      title: Text(AppLocalizations.of(context)!.helloWorld),
    ),
    

此程式碼生成一個 Text 元件,如果目標裝置的區域設定設定為英語,則顯示 "Hello World!";如果設定為西班牙語,則顯示 "¡Hola Mundo!"。在 arb 檔案中,每個條目的鍵用作 getter 的方法名,而該條目的值包含本地化訊息。

gen_l10n_example 使用了此工具。

要本地化你的裝置應用描述,請將本地化字串傳遞給 MaterialApp.onGenerateTitle

dart
return MaterialApp(
  onGenerateTitle: (context) => DemoLocalizations.of(context).title,

佔位符、複數和選擇

#

你還可以透過使用 佔位符 的特殊語法將應用值包含在訊息中,從而生成方法而不是 getter。佔位符必須是有效的 Dart 識別符號名稱,它會成為 AppLocalizations 程式碼中生成方法的位置引數。透過將佔位符名稱用大括號括起來來定義它,如下所示:

json
"{placeholderName}"

在應用的 .arb 檔案的 placeholders 物件中定義每個佔位符。例如,要定義一個帶有 userName 引數的問候訊息,請將以下內容新增到 lib/l10n/app_en.arb

json
"hello": "Hello {userName}",
"@hello": {
  "description": "A message with a single parameter",
  "placeholders": {
    "userName": {
      "type": "String",
      "example": "Bob"
    }
  }
}

此程式碼片段向 AppLocalizations.of(context) 物件添加了一個 hello 方法呼叫,該方法接受一個 String 型別的引數;hello 方法返回一個字串。重新生成 AppLocalizations 檔案。

用以下程式碼替換傳遞給 Builder 的程式碼:

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    // Returns 'Hello John'
    Text(AppLocalizations.of(context)!.hello('John')),
  ],
);

你也可以使用數字佔位符來指定多個值。不同的語言有不同的方式來表示複數。該語法還支援指定單詞 如何 複數化。一個 複數化 訊息必須包含一個 num 引數,指示在不同情況下如何複數化該單詞。例如,英語將 "person" 複數化為 "people",但這還不夠。message0 的複數可能是 "no people" 或 "zero people"。messageFew 的複數可能是 "several people"、"some people" 或 "a few people"。messageMany 的複數可能是 "most people"、"many people" 或 "a crowd"。只有更通用的 messageOther 欄位是必需的。以下示例顯示了可用的選項:

json
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"

前面的表示式會被替換為與 countPlaceholder 的值對應的訊息變體(message0message1 等)。只有 messageOther 欄位是必需的。

以下示例定義了一個將單詞 "wombat" 複數化的訊息:

json
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}",
"@nWombats": {
  "description": "A plural message",
  "placeholders": {
    "count": {
      "type": "num",
      "format": "compact"
    }
  }
}

透過傳入 count 引數來使用複數方法:

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'no wombats'
    Text(AppLocalizations.of(context)!.nWombats(0)),
    // Returns '1 wombat'
    Text(AppLocalizations.of(context)!.nWombats(1)),
    // Returns '5 wombats'
    Text(AppLocalizations.of(context)!.nWombats(5)),
  ],
);

與複數類似,你也可以基於 String 佔位符選擇值。這最常用於支援有性別的語言。語法如下:

json
"{selectPlaceholder, select, case{message} ... other{messageOther}}"

下一個示例定義了一個根據性別選擇代詞的訊息:

json
"pronoun": "{gender, select, male{he} female{she} other{they}}",
"@pronoun": {
  "description": "A gendered message",
  "placeholders": {
    "gender": {
      "type": "String"
    }
  }
}

透過將性別字串作為引數傳遞來使用此功能:

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'he'
    Text(AppLocalizations.of(context)!.pronoun('male')),
    // Returns 'she'
    Text(AppLocalizations.of(context)!.pronoun('female')),
    // Returns 'they'
    Text(AppLocalizations.of(context)!.pronoun('other')),
  ],
);

請記住,當使用 select 語句時,引數和實際值之間的比較是區分大小寫的。也就是說,AppLocalizations.of(context)!.pronoun("Male") 預設會使用 "other" 情況,並返回 "they"。

轉義語法

#

有時,你必須使用標記,例如 {},作為普通字元。要讓解析器忽略此類標記,請透過將以下內容新增到 l10n.yaml 來啟用 use-escaping 標誌:

yaml
use-escaping: true

解析器會忽略任何用一對單引號括起來的字元序列。要使用普通的單引號字元,請使用一對連續的單引號。例如,以下文字被轉換為 Dart String

json
{
  "helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
}

生成的字串如下:

dart
"Hello! {Isn't} this a wonderful day?"

包含數字和貨幣的訊息

#

包括代表貨幣價值的數字在內的數字,在不同的區域設定中顯示方式非常不同。flutter_localizations 中的本地化生成工具使用了 intl 包中的 NumberFormat 類,根據區域設定和所需格式來格式化數字。

intdoublenum 型別可以使用以下任何 NumberFormat 建構函式:

訊息“格式”值1200000 的輸出
compact"1.2M"
compactCurrency*"$1.2M"
compactSimpleCurrency*"$1.2M"
compactLong"1.2 million"
currency*"USD1,200,000.00"
decimalPattern"1,200,000"
decimalPatternDigits*"1,200,000"
decimalPercentPattern*"120,000,000%"
percentPattern"120,000,000%"
scientificPattern"1E6"
simpleCurrency*"$1,200,000"

表中帶星號的 NumberFormat 建構函式提供可選的命名引數。這些引數可以指定為佔位符的 optionalParameters 物件的值。例如,要為 compactCurrency 指定可選的 decimalDigits 引數,請對 lib/l10n/app_en.arb 檔案進行以下更改:

json
"numberOfDataPoints": "Number of data points: {value}",
"@numberOfDataPoints": {
  "description": "A message with a formatted int parameter",
  "placeholders": {
    "value": {
      "type": "int",
      "format": "compactCurrency",
      "optionalParameters": {
        "decimalDigits": 2
      }
    }
  }
}

包含日期的訊息

#

日期字串根據區域設定和應用需求有許多不同的格式化方式。

型別為 DateTime 的佔位符值使用 intl 包中的 DateFormat 進行格式化。

有 41 種格式變體,透過它們的 DateFormat 工廠建構函式名稱來標識。在以下示例中,helloWorldOn 訊息中出現的 DateTime 值使用 DateFormat.yMd 進行格式化:

json
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
  "description": "A message with a date parameter",
  "placeholders": {
    "date": {
      "type": "DateTime",
      "format": "yMd"
    }
  }
}

在區域設定為美式英語的應用中,以下表達式將產生 "7/9/1959"。在俄語區域設定中,它將產生 "9.07.1959"。

dart
AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9))

針對 iOS 進行本地化:更新 iOS 應用包

#

雖然本地化由 Flutter 處理,但你需要在 Xcode 專案中新增支援的語言。這確保了你在 App Store 中的條目能夠正確顯示支援的語言。

要配置你的應用支援的區域設定,請按照以下說明操作:

  1. 開啟你專案的 ios/Runner.xcodeproj Xcode 檔案。

  2. Project Navigator 中,選擇 Projects 下的 Runner 專案檔案。

  3. 在專案編輯器中選擇 Info 選項卡。

  4. Localizations 部分,點選 Add 按鈕 (+) 將支援的語言和地區新增到你的專案中。當被問及選擇檔案和引用語言時,只需選擇 Finish

  5. Xcode 會自動建立空的 .strings 檔案並更新 ios/Runner.xcodeproj/project.pbxproj 檔案。這些檔案由 App Store 用於確定你的應用支援哪些語言和地區。

進一步定製的高階主題

#

本節涵蓋了自定義本地化 Flutter 應用的其他方法。

高階區域設定定義

#

某些具有多個變體的語言需要不僅僅是語言程式碼才能正確區分。

例如,完全區分中文的所有變體需要指定語言程式碼、指令碼程式碼和國家程式碼。這是因為存在簡體和繁體指令碼,以及同一指令碼型別內字元書寫方式的區域差異。

為了完整表達國家程式碼 CNTWHK 的每種中文變體,支援的區域設定列表應包括:

dart
supportedLocales: [
  Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
  ), // generic simplified Chinese 'zh_Hans'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
  ), // generic traditional Chinese 'zh_Hant'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
    countryCode: 'CN',
  ), // 'zh_Hans_CN'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
    countryCode: 'TW',
  ), // 'zh_Hant_TW'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
    countryCode: 'HK',
  ), // 'zh_Hant_HK'
],

這種顯式的完整定義確保你的應用能夠區分這些國家程式碼的所有組合,並提供完全細緻的本地化內容。如果未指定使用者的首選區域設定,Flutter 會選擇最接近的匹配項,這可能與使用者預期的存在差異。Flutter 僅解析為 supportedLocales 中定義的區域設定,併為常用語言提供指令碼程式碼區分的本地化內容。有關支援的區域設定和首選區域設定如何解析的資訊,請參閱 Localizations

雖然中文是一個主要示例,但其他語言(如法語 (fr_FR, fr_CA))也應該為了更細緻的本地化而完全區分開。

跟蹤區域設定:Locale 類和 Localizations 元件

#

Locale 類標識使用者的語言。移動裝置支援為所有應用設定區域設定,通常使用系統設定選單。國際化應用透過顯示特定於區域設定的值來響應。例如,如果使用者將裝置的區域設定從英語切換為法語,那麼原本顯示 "Hello World" 的 Text 元件將以 "Bonjour le monde" 重建。

Localizations 元件為其子元件定義區域設定以及子元件依賴的本地化資源。WidgetsApp 元件建立一個 Localizations 元件,並在系統區域設定更改時重建它。

你始終可以使用 Localizations.localeOf() 來查詢應用的當前區域設定。

dart
Locale myLocale = Localizations.localeOf(context);

指定應用的 supportedLocales 引數

#

雖然 flutter_localizations 庫支援許多語言和語言變體,但預設情況下僅提供英語翻譯。開發者需要決定具體支援哪些語言。

MaterialAppsupportedLocales 引數限制了區域設定的更改。當用戶在裝置上更改區域設定時,應用的 Localizations 元件僅在新的區域設定是此列表的成員時才會跟隨更改。如果找不到與裝置區域設定的精確匹配項,則使用具有匹配 languageCode 的第一個支援的區域設定。如果這失敗,則使用 supportedLocales 列表的第一個元素。

希望使用不同“區域設定解析”方法的應用可以提供 localeResolutionCallback。例如,要使你的應用無條件接受使用者選擇的任何區域設定:

dart
MaterialApp(
  localeResolutionCallback: (locale, supportedLocales) {
    return locale;
  },
);

配置 l10n.yaml 檔案

#

l10n.yaml 檔案允許你配置 gen-l10n 工具以指定以下內容:

  • 所有輸入檔案的位置
  • 所有輸出檔案的建立位置
  • 為你的本地化委託賦予什麼 Dart 類名

有關選項的完整列表,請在命令列執行 flutter gen-l10n --help 或參考下表:

選項描述
arb-dir 模板和翻譯 arb 檔案所在的目錄。預設值為 lib/l10n
output-dir 寫入生成的本地化類的目錄。此選項僅在你希望在 Flutter 專案的其他位置生成本地化程式碼時才相關。你還需要將 synthetic-package 標誌設定為 false。

應用必須從此目錄匯入 output-localization-file 選項中指定的檔案。如果未指定,則預設為與 arb-dir 中指定的輸入目錄相同的目錄。
template-arb-file 用作生成 Dart 本地化和訊息檔案基礎的模板 arb 檔案。預設值為 app_en.arb
output-localization-file 輸出本地化和本地化委託類的檔名。預設值為 app_localizations.dart
untranslated-messages-file 描述尚未翻譯的本地化訊息的檔案位置。使用此選項會在目標位置建立一個 JSON 檔案,格式如下:

"locale": ["message_1", "message_2" ... "message_n"]

如果未指定此選項,則會在命令列列印尚未翻譯的訊息摘要。
output-class 用於輸出本地化和本地化委託類的 Dart 類名。預設值為 AppLocalizations
preferred-supported-locales 應用的首選支援區域設定列表。預設情況下,工具按字母順序生成支援的區域設定列表。使用此標誌可預設使用不同的區域設定。

例如,如果裝置支援,傳入 [ en_US ] 以預設使用美式英語。
header 要預置到生成的 Dart 本地化檔案中的標頭檔案。此選項接受一個字串。

例如,傳入 "/// All localized files." 以將此字串預置到生成的 Dart 檔案中。

或者,檢視 header-file 選項以傳入用於較長標題的文字檔案。
header-file 要預置到生成的 Dart 本地化檔案中的標頭檔案。此選項的值是包含插入到每個生成的 Dart 檔案頂部的標題文字的檔名。

或者,檢視 header 選項以傳入用於較簡單標題的字串。

此檔案應放置在 arb-dir 中指定的目錄中。
[no-]use-deferred-loading 指定是否生成以延遲方式匯入區域設定的 Dart 本地化檔案,從而允許在 Flutter Web 中延遲載入每個區域設定。

這可以透過減小 JavaScript 包的大小來縮短 Web 應用的初始啟動時間。當此標誌設定為 true 時,特定區域設定的訊息僅在 Flutter 應用需要時才被下載和載入。對於具有許多不同區域設定和許多本地化字串的專案,延遲載入可以提高效能。對於具有少量區域設定的專案,差異可以忽略不計,並且與將本地化與應用其餘部分打包在一起相比,可能會減慢啟動速度。

請注意,此標誌不會影響移動或桌面等其他平臺。
gen-inputs-and-outputs-list 指定後,該工具會生成一個包含工具輸入和輸出的 JSON 檔案,名為 gen_l10n_inputs_and_outputs.json

這對於跟蹤生成最新本地化集時使用了 Flutter 專案的哪些檔案非常有用。例如,Flutter 工具的構建系統使用此檔案來跟蹤在熱過載期間何時呼叫 gen_l10n。

此選項的值是生成 JSON 檔案的目錄。當為 null 時,不會生成 JSON 檔案。
synthetic-package 確定生成的輸出檔案是作為合成包生成,還是在 Flutter 專案中的指定目錄中生成。此標誌預設為 true。當 synthetic-package 設定為 false 時,它預設在 arb-dir 指定的目錄中生成本地化檔案。如果指定了 output-dir,則會在那裡生成檔案。
project-dir 指定後,該工具將傳遞給此選項的路徑用作 Flutter 專案的根目錄。

當為 null 時,將使用當前工作目錄的相對路徑。
[no-]required-resource-attributes 要求所有資源 ID 包含相應的資源屬性。

預設情況下,簡單訊息不需要元資料,但強烈建議這樣做,因為這為讀者提供了訊息含義的上下文。

複數訊息仍然需要資源屬性。
[no-]nullable-getter 指定本地化類 getter 是否可為空。

預設情況下,此值為 true,以便 Localizations.of(context) 返回一個可為空的值以實現向後相容性。如果此值為 false,則會對 Localizations.of(context) 的返回值執行空檢查,從而無需在使用者程式碼中進行空檢查。
[no-]format 指定後,在生成本地化檔案後會執行 dart format 命令。
use-escaping 指定是否啟用單引號作為轉義語法。
[no-]suppress-warnings指定後,將禁止顯示所有警告。
[no-]relax-syntax 指定後,語法會被放寬,以便特殊字元 "{" 如果後面沒有有效的佔位符,則被視為字串,"}" 如果它不關閉任何先前被視為特殊字元的 "{",則被視為字串。
[no-]use-named-parameters 是否為生成的本地化方法使用命名引數。

Flutter 國際化工作原理

#

本節涵蓋了 Flutter 本地化工作原理的技術細節。如果你計劃支援自己的一套本地化訊息,以下內容將會有所幫助。否則,你可以跳過本節。

載入和檢索本地化值

#

Localizations 元件用於載入和查詢包含本地化值集合的物件。應用透過 Localizations.of(context,type) 引用這些物件。如果裝置區域設定更改,Localizations 元件會自動載入新區域設定的值,然後重建使用它的元件。發生這種情況是因為 Localizations 的工作方式類似於 InheritedWidget。當構建函式引用繼承的元件時,就會建立對繼承的元件的隱式依賴。當繼承的元件發生更改(即 Localizations 元件的區域設定發生更改)時,其依賴上下文就會被重建。

本地化值由 Localizations 元件的 LocalizationsDelegate 列表載入。每個委託必須定義一個非同步的 load() 方法,該方法生成一個封裝了本地化值集合的物件。通常這些物件為每個本地化值定義一個方法。

在大型應用中,不同的模組或包可能會打包它們自己的本地化。這就是 Localizations 元件管理物件表(每個 LocalizationsDelegate 一個)的原因。要檢索由 LocalizationsDelegateload 方法之一生成的物件,請指定 BuildContext 和物件的型別。

例如,Material Components 元件的本地化字串由 MaterialLocalizations 類定義。此類的例項由 MaterialApp 類提供的 LocalizationDelegate 建立。它們可以使用 Localizations.of() 檢索。

dart
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);

此特定的 Localizations.of() 表示式使用頻繁,因此 MaterialLocalizations 類提供了一個便捷的簡寫:

dart
static MaterialLocalizations of(BuildContext context) {
  return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}

/// References to the localized values defined by MaterialLocalizations
/// are typically written like this:

tooltip: MaterialLocalizations.of(context).backButtonTooltip,

為應用的本地化資源定義一個類

#

組裝一個國際化的 Flutter 應用通常從封裝應用本地化值的類開始。接下來的示例是此類類的典型代表。

此應用的 intl_example 的完整原始碼。

此示例基於 intl 包提供的 API 和工具。應用本地化資源的替代類 部分描述了一個不依賴於 intl 包的示例

DemoLocalizations 類(在以下程式碼片段中定義)包含應用支援的區域設定中翻譯的應用字串(本例中只有一個)。它使用 Dart intl 包生成的 initializeMessages() 函式以及 Intl.message() 來查詢它們。

dart
class DemoLocalizations {
  DemoLocalizations(this.localeName);

  static Future<DemoLocalizations> load(Locale locale) {
    final String name =
        locale.countryCode == null || locale.countryCode!.isEmpty
        ? locale.languageCode
        : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);

    return initializeMessages(localeName).then((_) {
      return DemoLocalizations(localeName);
    });
  }

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }

  final String localeName;

  String get title {
    return Intl.message(
      'Hello World',
      name: 'title',
      desc: 'Title for the Demo application',
      locale: localeName,
    );
  }
}

基於 intl 包的類匯入一個生成的訊息目錄,該目錄為 Intl.message() 提供 initializeMessages() 函式和每個區域設定的後備儲存。訊息目錄由一個 intl 工具 產生,該工具分析原始碼以查詢包含 Intl.message() 呼叫的類。在這種情況下,那將只是 DemoLocalizations 類。

新增對新語言的支援

#

需要支援未包含在 GlobalMaterialLocalizations 中的語言的應用必須做一些額外的工作:它必須為單詞或短語提供約 70 個翻譯(“本地化”),併為該區域設定提供日期模式和符號。

請參閱下文了解如何新增對新挪威語 (Nynorsk) 支援的示例。

一個新的 GlobalMaterialLocalizations 子類定義了 Material 庫依賴的本地化。還必須定義一個新的 LocalizationsDelegate 子類,它作為 GlobalMaterialLocalizations 子類的工廠。

這是完整的 add_language 示例的原始碼(減去了實際的挪威語翻譯)。

特定於區域設定的 GlobalMaterialLocalizations 子類稱為 NnMaterialLocalizations,而 LocalizationsDelegate 子類是 _NnMaterialLocalizationsDelegateNnMaterialLocalizations.delegate 的值是委託的一個例項,也是使用這些本地化的應用所需的全部內容。

委託類包括基本的日期和數字格式本地化。所有其他本地化由 NnMaterialLocalizations 中的 String 型別屬性 getter 定義,如下所示:

dart
@override
String get moreButtonTooltip => r'More';

@override
String get aboutListTileTitleRaw => r'About $applicationName';

@override
String get alertDialogLabel => r'Alert';

當然,這些是英文翻譯。要完成這項工作,你需要將每個 getter 的返回值更改為適當的挪威語字串。

Getter 返回帶有 r 字首的“原始” Dart 字串,例如 r'About $applicationName',因為有時這些字串包含帶有 $ 字首的變數。變數由引數化的本地化方法展開。

dart
@override
String get pageRowsInfoTitleRaw => r'$firstRow–$lastRow of $rowCount';

@override
String get pageRowsInfoTitleApproximateRaw =>
    r'$firstRow–$lastRow of about $rowCount';

還需要指定該區域設定的日期模式和符號,它們在原始碼中定義如下:

dart
const nnLocaleDatePatterns = {
  'd': 'd.',
  'E': 'ccc',
  'EEEE': 'cccc',
  'LLL': 'LLL',
  // ...
}
dart
const Map<String, Object?> nnDateSymbols = {
  'NAME': 'nn',
  'ERAS': <dynamic>['f.Kr.', 'e.Kr.'],

這些值需要針對該區域設定進行修改,以使用正確的日期格式。不幸的是,由於 intl 庫在數字格式化方面沒有相同的靈活性,因此必須在 _NnMaterialLocalizationsDelegate 中使用現有區域設定的格式作為替代。

dart
class _NnMaterialLocalizationsDelegate
    extends LocalizationsDelegate<MaterialLocalizations> {
  const _NnMaterialLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) => locale.languageCode == 'nn';

  @override
  Future<MaterialLocalizations> load(Locale locale) async {
    final String localeName = intl.Intl.canonicalizedLocale(locale.toString());

    // The locale (in this case `nn`) needs to be initialized into the custom
    // date symbols and patterns setup that Flutter uses.
    date_symbol_data_custom.initializeDateFormattingCustom(
      locale: localeName,
      patterns: nnLocaleDatePatterns,
      symbols: intl.DateSymbols.deserializeFromMap(nnDateSymbols),
    );

    return SynchronousFuture<MaterialLocalizations>(
      NnMaterialLocalizations(
        localeName: localeName,
        // The `intl` library's NumberFormat class is generated from CLDR data
        // (see https://github.com/dart-lang/i18n/blob/main/pkgs/intl/lib/number_symbols_data.dart).
        // Unfortunately, there is no way to use a locale that isn't defined in
        // this map and the only way to work around this is to use a listed
        // locale's NumberFormat symbols. So, here we use the number formats
        // for 'en_US' instead.
        decimalFormat: intl.NumberFormat('#,##0.###', 'en_US'),
        twoDigitZeroPaddedFormat: intl.NumberFormat('00', 'en_US'),
        // DateFormat here will use the symbols and patterns provided in the
        // `date_symbol_data_custom.initializeDateFormattingCustom` call above.
        // However, an alternative is to simply use a supported locale's
        // DateFormat symbols, similar to NumberFormat above.
        fullYearFormat: intl.DateFormat('y', localeName),
        compactDateFormat: intl.DateFormat('yMd', localeName),
        shortDateFormat: intl.DateFormat('yMMMd', localeName),
        mediumDateFormat: intl.DateFormat('EEE, MMM d', localeName),
        longDateFormat: intl.DateFormat('EEEE, MMMM d, y', localeName),
        yearMonthFormat: intl.DateFormat('MMMM y', localeName),
        shortMonthDayFormat: intl.DateFormat('MMM d'),
      ),
    );
  }

  @override
  bool shouldReload(_NnMaterialLocalizationsDelegate old) => false;
}

有關本地化字串的更多資訊,請檢視 flutter_localizations README

一旦你實現了 GlobalMaterialLocalizationsLocalizationsDelegate 的特定語言子類,你就需要將該語言和委託例項新增到你的應用中。以下程式碼將應用語言設定為挪威語,並將 NnMaterialLocalizations 委託例項新增到應用的 localizationsDelegates 列表中:

dart
const MaterialApp(
  localizationsDelegates: [
    GlobalWidgetsLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    NnMaterialLocalizations.delegate, // Add the newly created delegate
  ],
  supportedLocales: [Locale('en', 'US'), Locale('nn')],
  home: Home(),
),

替代的國際化工作流

#

本節介紹了國際化 Flutter 應用的不同方法。

應用本地化資源的替代類

#

前面的示例是根據 Dart intl 包定義的。為了簡潔起見,或者為了與不同的 i18n 框架整合,你可以選擇自己的方法來管理本地化值。

minimal 應用的完整原始碼。

在以下示例中,DemoLocalizations 類直接在每個語言的 Map 中包含了它所有的翻譯:

dart
class DemoLocalizations {
  DemoLocalizations(this.locale);

  final Locale locale;

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }

  static const _localizedValues = <String, Map<String, String>>{
    'en': {'title': 'Hello World'},
    'es': {'title': 'Hola Mundo'},
  };

  static List<String> languages() => _localizedValues.keys.toList();

  String get title {
    return _localizedValues[locale.languageCode]!['title']!;
  }
}

在最小化應用中,DemoLocalizationsDelegate 略有不同。它的 load 方法返回一個 SynchronousFuture,因為不需要進行非同步載入。

dart
class DemoLocalizationsDelegate
    extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) =>
      DemoLocalizations.languages().contains(locale.languageCode);

  @override
  Future<DemoLocalizations> load(Locale locale) {
    // Returning a SynchronousFuture here because an async "load" operation
    // isn't needed to produce an instance of DemoLocalizations.
    return SynchronousFuture<DemoLocalizations>(DemoLocalizations(locale));
  }

  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
}

使用 Dart intl 工具

#

在使用 Dart intl 包構建 API 之前,請檢視 intl 包的文件。以下列表總結了本地化依賴於 intl 包的應用的過程:

演示應用依賴於一個名為 l10n/messages_all.dart 的生成原始檔,該檔案定義了應用使用的所有可本地化字串。

重建 l10n/messages_all.dart 需要兩個步驟。

  1. 以應用的根目錄為當前目錄,從 lib/main.dart 生成 l10n/intl_messages.arb

    dart run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart
    

    intl_messages.arb 檔案是一個 JSON 格式的 map,為 main.dart 中定義的每個 Intl.message() 函式包含一個條目。此檔案作為英語和西班牙語翻譯 intl_en.arbintl_es.arb 的模板。這些翻譯由你,即開發者,建立。

  2. 以應用的根目錄為當前目錄,為每個 intl_.arb 檔案生成 intl_messages_.dart,並生成匯入所有訊息檔案的 intl_messages_all.dart

    dart run intl_translation:generate_from_arb \
        --output-dir=lib/l10n --no-use-deferred-loading \
        lib/main.dart lib/l10n/intl_*.arb
    

    Windows 不支援檔名萬用字元。 請改用 intl_translation:extract_to_arb 命令生成的 .arb 檔案列表。

    dart run intl_translation:generate_from_arb \
        --output-dir=lib/l10n --no-use-deferred-loading \
        lib/main.dart \
        lib/l10n/intl_en.arb lib/l10n/intl_fr.arb lib/l10n/intl_messages.arb
    

    DemoLocalizations 類使用生成的 initializeMessages() 函式(在 intl_messages_all.dart 中定義)來載入本地化訊息,並使用 Intl.message() 來查詢它們。

更多資訊

#

如果你透過閱讀程式碼學習得最好,請檢視以下示例:

如果 Dart 的 intl 包對你來說是新的,請檢視 使用 Dart intl 工具