跳至主要內容

將 Flutter 應用程式國際化

如果您的應用程式可能會部署給說其他語言的使用者,那麼您就需要將其國際化。這表示您需要以某種方式編寫應用程式,使其可以針對應用程式支援的每種語言或語言環境本地化文字和版面配置等值。Flutter 提供了有助於國際化的 Widget 和類別,而 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,請將該套件和 intl 套件作為相依性新增到您的 pubspec.yaml 檔案中

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

這會建立一個具有下列條目的 pubspec.yml 檔案

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 套件並新增先前的程式碼後,現在應該在 115 種支援的語言環境之一中正確本地化 MaterialCupertino 套件。Widget 應該會根據本地化的訊息進行調整,並具有正確的從左到右或從右到左的版面配置。

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

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

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

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

有關這些應用程式屬性、它們所依賴的類型以及國際化的 Flutter 應用程式通常如何建構的更多資訊,請參閱本頁。

覆寫地區設定

#

Localizations.overrideLocalizations widget 的工廠建構函式,允許在應用程式的某個部分需要本地化為與裝置設定的語言環境不同的語言環境(通常很少見)時使用。

若要觀察此行為,請新增對 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 widget 應該以西班牙文重新呈現。

新增您自己的本地化訊息

#

在新增 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. 將新的 yaml 檔案新增到 Flutter 專案的根目錄。將此檔案命名為 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,程式碼產生會自動進行。您應該會在 ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n 中找到產生的檔案。或者,您也可以執行 flutter gen-l10n 來產生相同的檔案,而無需執行應用程式。

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

    dart
    import 'package:flutter_gen/gen_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),
    ),

如果目標裝置的語言環境設定為英文,則此程式碼會產生一個顯示「Hello World!」的 Text widget,如果目標裝置的語言環境設定為西班牙文,則會顯示「¡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 參數定義 hello 訊息,請將下列項目新增至 lib/l10n/app_en.arb

json
"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 中的程式碼取代為下列程式碼

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

逸出語法

#

有時,您必須使用 Token,例如 {},作為一般字元。若要忽略要剖析的此類 Token,請藉由將下列項目新增至 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 類別,根據語言環境和所需的格式來格式化數字。

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

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

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. 在「專案導覽器」中,選取「專案」下方的 Runner 專案檔案。

  3. 在專案編輯器中,選取「Info」標籤。

  4. 在「本地化」區段中,按一下「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_FRfr_CA))也應完全區分,以進行更細微的本地化。

追蹤地區設定:Locale 類別和 Localizations Widget

#

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

Localizations 小工具會為其子項及其所依賴的本地化資源定義地區設定。WidgetsApp 小工具會建立 Localizations 小工具,並在系統的地區設定變更時重建它。

您隨時可以使用 Localizations.localeOf() 查詢應用程式目前的地區設定。

dart
Locale myLocale = Localizations.localeOf(context);

指定應用程式的 supportedLocales 參數

#

雖然 flutter_localizations 程式庫目前支援 115 種語言和語言變體,但預設只提供英文翻譯。開發人員可以自行決定要支援哪些語言。

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 網頁中延遲載入每個地區設定。

這可以透過減少 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指定時,會隱藏所有警告。
[no-]relax-syntax指定時,語法會放寬,以便如果特殊字元「{」後面沒有有效的預留位置,則會將其視為字串;如果「}」沒有關閉任何先前被視為特殊字元的「{」,則會將其視為字串。
[no-]use-named-parameters是否為產生的本地化方法使用具名參數。

Flutter 中的國際化運作方式

#

本節涵蓋 Flutter 中本地化運作方式的技術細節。如果您打算支援自己的一組本地化訊息,以下內容會很有幫助。否則,您可以略過本節。

載入和擷取本地化值

#

Localizations 小部件用於載入和查詢包含本地化數值集合的物件。應用程式使用 Localizations.of(context,type) 來參照這些物件。如果裝置的地區設定變更,Localizations 小部件會自動載入新地區設定的值,然後重建使用它的部件。之所以會這樣,是因為 Localizations 的運作方式就像一個 InheritedWidget。當一個建構函式參照一個繼承的小部件時,會建立一個對繼承小部件的隱式依賴關係。當一個繼承的小部件變更時(當 Localizations 小部件的地區設定變更時),其相依的 context 會被重建。

本地化數值由 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 個單詞或短語的翻譯(「本地化」),以及日期模式和符號。

請參閱以下範例,了解如何新增對挪威尼諾斯克語的支援。

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

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_<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 工具