國際化 Flutter 應用程式

如果您的應用程式可能會部署給使用其他語言的使用者,那麼您需要對其進行國際化。這表示您需要以一種方式撰寫應用程式,讓應用程式可以針對其支援的每種語言或語言環境,將文字和版面等值進行在地化。Flutter 提供有助於國際化的工具和類別,而 Flutter 函式庫本身也已進行國際化。

此頁面涵蓋使用 MaterialAppCupertinoApp 類別在地化 Flutter 應用程式的概念和工作流程,因為大多數應用程式都是以這種方式撰寫的。但是,使用較低階層的 WidgetsApp 類別撰寫的應用程式也可以使用相同的類別和邏輯進行國際化。

Flutter 中在地化的簡介

本節提供一個教學課程,說明如何建立和國際化新的 Flutter 應用程式,以及目標平台可能需要的任何其他設定。

您可以在 gen_l10n_example 中找到此範例的原始碼。

設定國際化應用程式:Flutter_localizations 套件

預設情況下,Flutter 僅提供美國英語的在地化。若要新增對其他語言的支援,應用程式必須指定其他 MaterialApp(或 CupertinoApp)屬性,並包含名為 flutter_localizations 的套件。截至 2023 年 12 月,此套件支援 115 種語言 和語言變體。

首先,使用 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.yml 檔案

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

然後匯入 flutter_localizations 函式庫,並為您的 MaterialAppCupertinoApp 指定 localizationsDelegatessupportedLocales

import 'package:flutter_localizations/flutter_localizations.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 套件現在應已正確在地化為 115 個支援的語言環境之一。小工具應根據在地化訊息進行調整,並採用正確的由左至右或由右至左的版面配置。

嘗試將目標平台的語言環境切換為西班牙語 (es),訊息應會在地化。

基於 WidgetsApp 的應用程式類似,但不需要 GlobalMaterialLocalizations.delegate

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

localizationsDelegates 清單的元素是產生在地化值集合的工廠。 GlobalMaterialLocalizations.delegate 提供在地化字串和其他值給 Material Components 函式庫。 GlobalWidgetsLocalizations.delegate 定義小工具函式庫的預設文字方向,由左至右或由右至左。

本頁面涵蓋了關於這些應用程式屬性、它們所依賴的類型,以及國際化 Flutter 應用程式的典型結構的更多資訊。

覆寫地區設定

Localizations.overrideLocalizations 小工具的工廠建構函式,允許應用程式區段在地化到與裝置所設定地區設定不同的地區設定(通常很少見)。

若要觀察此行為,請新增呼叫 Localizations.override 和一個簡單的 CalendarDatePicker

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 區段。

    # The following section is specific to Flutter.
    flutter:
      generate: true # Add this line
  3. 將新的 yaml 檔案新增至 Flutter 專案的根目錄。將此檔案命名為 l10n.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 範本檔案。例如

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

    {
        "helloWorld": "¡Hola Mundo!"
    }
  6. 現在,執行 flutter pub getflutter run,codegen 會自動執行。您應該可以在 ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n 中找到已產生的檔案。或者,您也可以執行 flutter gen-l10n 來產生相同的檔案,而無需執行應用程式。

  7. app_localizations.dartAppLocalizations.delegate 中新增 MaterialApp 建構函式的呼叫中新增匯入陳述式

    import 'package:flutter_gen/gen_l10n/app_localizations.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 清單。您可以使用這些清單,而不必手動提供。

    const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
    );
  8. 一旦 Material 應用程式啟動,您可以在應用程式的任何地方使用 AppLocalizations

    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

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

佔位符、複數和選取

您也可以使用特殊語法將應用程式值包含在訊息中,該語法使用 佔位符 來產生方法,而不是 getter。佔位符(必須是有效的 Dart 識別名稱)會成為 AppLocalizations 程式碼中所產生方法中的位置參數。請透過將佔位符名稱包在花括號中來定義,如下所示

"{placeholderName}"

在應用程式的 .arb 檔案中,於 placeholders 物件中定義每個佔位符。例如,若要定義帶有 userName 參數的 hello 訊息,請將下列內容新增至 lib/l10n/app_en.arb

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

此程式碼片段會新增一個 hello 方法呼叫至 AppLocalizations.of(context) 物件,且此方法接受一個 String 類型的參數; hello 方法會傳回一個字串。重新產生 AppLocalizations 檔案。

將傳入 Builder 的程式碼替換為下列程式碼

// 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 欄位是必要的。下列範例顯示有哪些選項可用

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

先前的表達式會被對應於 countPlaceholder 值的訊息變異(message0message1、…)取代。只有 messageOther 欄位是必要的。

下列範例定義了一個會將「wombat」這個字詞變為複數的訊息

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

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

// 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 佔位符來選擇一個值。這最常被用於支援性別語言。語法如下

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

下一個範例定義了一個會根據性別來選擇代名詞的訊息

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

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

// 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") 預設為「其他」情況,並傳回「他們」。

跳脫語法

有時,您必須使用令牌,例如 {},作為一般字元。若要忽略這些令牌的解析,請啟用 use-escaping 旗標,方法是將下列內容新增至 l10n.yaml

use-escaping: true

剖析器會忽略用一對單引號包住的任何字元字串。若要使用一般的單引號字元,請使用一對連續的單引號。例如,下列文字會轉換為 Dart String

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

產生的字串如下

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

包含數字和貨幣的訊息

數字,包括代表貨幣值的數字,在不同的地區會顯示得非常不同。flutter_localizations 中的在地化產生工具會使用 intl 套件中的 NumberFormat 類別,根據地區和所需的格式來格式化數字。

intdoublenumber 類型可以使用下列任何 NumberFormat 建構函式

訊息「格式」值 1200000 的輸出
compact 「1.2M」
compactCurrency* 「120 萬」
compactSimpleCurrency* 「120 萬」
compactLong 「120 萬」
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 建構函式提供選用式命名參數。這些參數可以指定為 placeholder 的 optionalParameters 物件的值。例如,若要為 compactCurrency 指定選用式 decimalDigits 參數,請對 lib/l10n/app_en.arg 檔案進行下列變更

"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 的 placeholder 值會使用 intl 套件中的 DateFormat 格式化。

有 41 種格式變化,由其 DateFormat 工廠建構函式的名稱識別。在下列範例中,出現在 helloWorldOn 訊息中的 DateTime 值會使用 DateFormat.yMd 格式化

"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」。

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

iOS 本地化:更新 iOS 應用程式套件

iOS 應用程式通常會在內建於應用程式套件中的 Info.plist 檔案中定義主要的應用程式元資料,包括支援的語言環境。若要設定應用程式支援的語言環境,請使用下列說明

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

  2. 專案瀏覽器中,開啟 Runner 專案的 Runner 資料夾下的 Info.plist 檔案。

  3. 選取資訊屬性清單項目。然後從編輯器功能表中選取新增項目,並從快顯功能表中選取在地化

  4. 選取並展開新建立的 在地化 項目。針對應用程式支援的每個語言環境,新增一個新項目,並從欄位中的快顯功能表中選取您要新增的語言環境。此清單應與 supportedLocales 參數中列出的語言一致。

  5. 新增所有支援的語言環境後,儲存檔案。

進階主題以進行進一步自訂

本節涵蓋自訂在地化 Flutter 應用程式的其他方式。

進階語言環境定義

某些具有多種變體的語言需要不只語言代碼才能適當地區分。

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

為了完整表達國家代碼 CNTWHK 的所有中文變體,受支援的語言環境清單應包含

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 中定義的語言環境,並提供針對常用語言以 scriptCode 區分的在地化內容。請參閱 Localizations,以取得有關如何解析受支援語言環境和偏好語言環境的資訊。

雖然中文是一個主要的範例,但其他語言(例如法文 (fr_FRfr_CA))也應完全區分,以提供更細緻的在地化。

追蹤語言環境:Locale 類別和 Localizations 小工具

Locale 類別識別使用者的語言。行動裝置支援設定所有應用程式的語言環境,通常使用系統設定功能表。國際化應用程式會回應顯示特定於語言環境的值。例如,如果使用者將裝置的語言環境從英文切換成法文,則原本顯示「Hello World」的 Text 小工具會重新建置為「Bonjour le monde」。

Localizations 小工具定義其子項目的語言環境和子項目依賴的在地化資源。WidgetsApp 小工具會建立 Localizations 小工具,並在系統的語言環境變更時重新建置該小工具。

你可以隨時使用 Localizations.localeOf() 查詢應用程式的目前語言環境

Locale myLocale = Localizations.localeOf(context);

指定應用程式的 supported­Locales 參數

儘管 flutter_localizations 函式庫目前支援 115 種語言和語言變體,但預設僅提供英文翻譯。開發人員必須決定要支援哪些語言。

MaterialApp supportedLocales 參數限制語言環境變更。當使用者變更其裝置上的語言環境設定時,應用程式的 Localizations 小工具僅在新的語言環境為此清單的成員時才遵循。如果找不到與裝置語言環境完全相符的語言環境,則會使用第一個支援的語言環境,且其 languageCode 相符。如果失敗,則會使用 supportedLocales 清單的第一個元素。

想要使用不同「語言環境解析」方法的應用程式可以提供 localeResolutionCallback。例如,讓你的應用程式無條件接受使用者選取的任何語言環境

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 ] 以在裝置支援時預設為美式英語。
標頭 要新增至產生的 Dart 本地化檔案開頭的標頭。此選項採用字串。

例如,傳入 "/// 所有已在地化的檔案。" 以在產生的 Dart 檔案開頭新增此字串。

或者,查看 header-file 選項,以傳入文字檔案作為較長的標頭。
標頭檔案 要新增至產生的 Dart 本地化檔案開頭的標頭。此選項的值是包含標頭文字的檔案名稱,該文字會插入在每個產生的 Dart 檔案的頂端。

或者,查看 header 選項,以傳入字串作為較簡單的標頭。

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

這可以透過減小 JavaScript 程式集的大小來減少網頁應用程式的初始啟動時間。當此旗標設定為 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 是否可為 null。

預設情況下,此值為 true,因此 Localizations.of(context) 會傳回可為 null 的值,以維持向後相容性。如果此值為 false,則會對 Localizations.of(context) 的傳回值執行 null 檢查,無需在使用者程式碼中執行 null 檢查。
[no-]format 如果指定,則在產生在地化檔案後,會執行 dart format 指令。
use-escaping 指定是否啟用使用單引號作為跳脫語法。
[no-]suppress-warnings 如果指定,則會抑制所有警告。

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() 擷取

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

這個特定的 Localizations.of() 表達式很常使用,所以 MaterialLocalizations 類別提供了一個方便的簡寫

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(),來查詢這些字串。

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 套件的類別會匯入產生的訊息目錄,提供 initializeMessages() 函式和 Intl.message() 的每種語言環境後端儲存。訊息目錄是由 intl 工具 產生的,此工具會分析包含 Intl.message() 呼叫的類別的原始碼。在這種情況下,只會是 DemoLocalizations 類別。

新增對新語言的支援

需要支援未包含在 GlobalMaterialLocalizations 中的語言的應用程式必須執行一些額外的工作:它必須提供約 70 個字詞或片語的翻譯(「在地化」)以及該地區設定的日期模式和符號。

請參閱以下內容,瞭解如何新增對挪威新挪威語的支援。

新的 GlobalMaterialLocalizations 子類別定義 Material 函式庫所依賴的在地化。還必須定義新的 LocalizationsDelegate 子類別,作為 GlobalMaterialLocalizations 子類別的工廠。

以下是完整的 add_language 範例的原始碼,不含實際的新挪威語翻譯。

特定於地區設定的 GlobalMaterialLocalizations 子類別稱為 NnMaterialLocalizations,而 LocalizationsDelegate 子類別為 _NnMaterialLocalizationsDelegateNnMaterialLocalizations.delegate 的值是委派項目的執行個體,而且是使用這些在地化項目的應用程式所需要的全部。

委派項目類別包含基本的日期和數字格式在地化。所有其他在地化都由 NnMaterialLocalizations 中的 String 值的屬性 getter 定義,如下所示

@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',因為有時字串包含具有 $ 前綴的變數。變數會由參數化在地化方法擴充

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

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

區域設定的日期樣式和符號也需要指定,在原始碼中定義如下

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

這些值需要修改為區域設定,才能使用正確的日期格式。很遺憾,由於 intl 函式庫不具備數字格式化的相同彈性,因此現有區域設定的格式必須用作 _NnMaterialLocalizationsDelegate 中的替代方案

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 自述檔

一旦您實作了 GlobalMaterialLocalizationsLocalizationsDelegate 的特定語言子類別,您需要將語言和委派執行個體新增至您的應用程式。以下程式碼將應用程式的語言設定為新挪威語,並將 NnMaterialLocalizations 委派執行個體新增至應用程式的 localizationsDelegates 清單

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 類別直接在每個語言的對應中包含其所有翻譯

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,因為不需要進行非同步載入。

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 格式的對應,其中每一個 Intl.message() 函式在 main.dart 中定義一個條目。此檔案用作英文和西班牙文翻譯的範本,intl_en.arbintl_es.arb。這些翻譯是由您,開發人員建立。

  2. 將應用程式的根目錄設為目前目錄,為每個 intl_<locale>.arb 檔案產生 intl_messages_<locale>.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 工具