Flutter本地化数据支持插件common_locale_data的使用

发布于 1周前 作者 eggper 来自 Flutter

Flutter本地化数据支持插件common_locale_data的使用

common_locale_data 插件提供了类型安全且可树摇的方法来访问翻译后的通用数据。这些翻译是从通用区域数据存储库(CLDR)提取出来的。

该插件还包括用于使用这些数据格式化单位、时区名称和区域名称的帮助函数。

可用数据

翻译包括:

  • 测量单位的全称和缩写形式,包括单数/复数变化
  • 语言名称
  • 脚本、变体和Unicode扩展名称
  • 国家和地区名称
  • 国家子分区名称
  • 货币名称,包括单数/复数变化
  • 星期几、月份、纪元、一天中的时间段,全称和缩写形式
  • 日历字段
  • 相对时间字段
  • 时区和时区示例城市(或类似物)

功能

翻译可以通过静态成员函数或动态映射访问。

格式化函数可用于:单位、货币、相对时间字段、时区和区域标识符。

区域标识符支持解析、规范化、添加和删除可能的子标签,并以多种格式进行格式化。

树摇优化

所有数据和翻译都作为代码中的字面字符串存储。API 设计为可树摇(机制移除未使用的数据)。最终应用程序的二进制文件不会包含未使用的语言的翻译,也不会包含未使用的数据类型。

如果您单独使用区域(例如不存储在集合中),树摇效果最佳:仅包括实际使用的每个区域的数据类型。

如果通过静态访问成员,编译器也可以减少到仅使用的字段(例如国家、货币、语言)。如果使用动态访问(通过 [] 操作符),则会包括所有字段。

如果您将区域存储在集合中并动态选择集合成员(或遍历集合),编译器无法确定实际使用的区域,因此它会包含集合中的所有区域及其所有使用的数据类型。

编译

为了提高编译速度,当使用 [common_locale_data] 时,需要单独导入区域。

例如,对于英语:

import 'package:common_locale_data/en.dart';

或者对于法语:

import 'package:common_locale_data/fr.dart';

您可以导入所有区域,通过 [CommonLocaleDataAll]。但是这将显著增加编译时间(比包括几个区域要多10倍)。

只要您不使用 [CommonLocaleDataAll.locales] 成员动态获取区域,编译后的文件大小不会受到影响。如果您确实使用了 [CommonLocaleDataAll.locales] 成员,所有区域都会被编译进来,文件大小会达到几十兆字节(取决于您使用的数据类型)。

数据源

主要的 CLDR 数据从以下仓库提取:https://github.com/unicode-org/cldr-json

附加时区相关数据从以下仓库提取:https://github.com/unicode-org/cldrhttps://github.com/unicode-org/icu

  • CLDR 版本 46.0.0,变体:现代
  • Unicode 版本 16.0.0
  • TZDB 版本 2024b

示例

// 导入特定区域而不是 common_locale_data_all.dart 以节省编译时间。

import 'package:common_locale_data/de.dart';
import 'package:common_locale_data/en.dart';
import 'package:common_locale_data/en_gb.dart';
import 'package:common_locale_data/es.dart';
import 'package:common_locale_data/fr.dart';
import 'package:common_locale_data/zh_hans.dart';
import 'package:intl/intl.dart';

void main() {
  // 打印此包的基本版本信息。
  print(
      'CLDR version: ${CommonLocaleData.cldrVersion} - ${CommonLocaleData.cldrVariant} ');
  print('Unicode version: ${CommonLocaleData.unicodeVersion}');
  print('ICU version: ${CommonLocaleData.icuVersion}');
  print('Timezone DB version: ${CommonLocaleData.tzdbVersion}');

  // 为了获得最佳的树摇效果,你应该显式选择你想要使用的语言,例如 `var cld = CommonLocaleDataEnGB()`。

  // 然而在实践中,你通常希望根据支持的语言动态选择用户的首选语言。
  // 你可以使用 e.g. Platform.localeName 或者 Intl.defaultLocale。

  // 为了说明目的,我们在这里固定一组所需的语言:
  var desiredLocales = ['nl', 'en-IR'];

  // 随意选择的一组支持的语言。
  var supportedLocales = [
    CommonLocaleDataDe(),
    CommonLocaleDataEn(),
    CommonLocaleDataEnGB(),
    CommonLocaleDataEs(),
    CommonLocaleDataFr(),
    CommonLocaleDataZhHans(),
  ];

  // 获取最佳匹配的语言。
  var cld =
      LocaleMatcher.getBestCommonLocaleData(desiredLocales, supportedLocales)!;

  // 打印当前语言标识。
  print('');
  print(
      'Current localeId: ${cld.locale}: ${cld.localeDisplayName.formatWithExtensions(LocaleId.parse(cld.locale))}');

  // 单位
  print('');
  print(cld.units.lengthMeter); // meters
  print(cld.units.lengthMeter.long(3.5)); // 3 meters
  print(cld.units.lengthMeter.long(3)); // 3 meters
  print(cld.units.lengthMeter.long(1)); // 1 meter

  print(cld.units.areaSquareMeter.long(3.5)); // 3 square meters
  print(cld.units.areaSquareMeter.short(3.5)); // 3 m²
  print(cld.units.areaSquareMeter.narrow(3.5)); // 3m²

  // 具有不同格式的单位
  print('');
  print(cld.units.areaSquareMeter.short(3541.53));
  print(cld.units.areaSquareMeter.short(3541.53,
      numberFormat: NumberFormat.decimalPatternDigits(decimalDigits: 2)));

  print(cld.units.areaSquareMeter
      .short(3541.53, numberFormat: NumberFormat.compact()));
  print(cld.units.areaSquareMeter
      .short(3541.53, numberFormat: NumberFormat.compactLong()));

  // 复合单位
  print('');
  print(cld.units.per.long(cld.units.electricAmpere.long.displayName,
      cld.units.durationSecond.long.displayName));
  print(cld.units.per.short(cld.units.electricAmpere.short.displayName,
      cld.units.durationSecond.short.displayName));
  print(cld.units.per.narrow(cld.units.electricAmpere.narrow.displayName,
      cld.units.durationSecond.narrow.displayName));
  print(cld.units.times.long(cld.units.electricOhm.long.displayName,
      cld.units.lengthMeter.long.displayName));
  print(cld.units.times.short(cld.units.electricOhm.short.displayName,
      cld.units.lengthMeter.short.displayName));
  print(cld.units.times.narrow(cld.units.electricOhm.narrow.displayName,
      cld.units.lengthMeter.narrow.displayName));

  // 日期字段
  print('');
  print(cld.date.year.future.long(2)); // in 2 years
  print(cld.date.year.past.long(2)); // 2 years ago
  print(cld.date.year.next.long); // next year
  print(cld.date.year.previous.long); // last year

  // 领土
  print('');
  print(cld.territories.world); // world
  print(cld.territories.africa); // Africa
  print(cld.territories.$019); // Americas
  print(cld.territories.us); // South Africa

  // 使用动态访问的领土:这将导致所有领土数据被包含在二进制文件中
  print(cld.territories.territories['019']); // Americas
  print(cld.territories.territories['US']); // United States

  // 语言名称
  print('');
  print(cld.languages.en.name); // English
  print(cld.languages.zhHans.name); // Simplified Chinese
  print(cld.languages.und.name); // Undefined language

  // 使用动态访问的语言:这将导致所有领土数据被包含在二进制文件中
  print(cld.languages['en']!.name); // English

  // 货币
  print('');
  var currency = cld.currencies.usd;
  print(currency.displayName);
  print(currency.symbol);
  print(currency.symbolNarrow);
  print(currency.symbolVariant);

  print(currency(1.51));
  print(currency.short(1.51));
  print(currency.narrow(1.51));
  print(currency.variant(1.51));
  print(currency.iso(1.51));
  print(currency.hidden(1.51));

  print(currency.full(0));
  print(currency.full(0.51));
  print(currency.full(1));
  print(currency.full(1, decimalDigits: 1));
  print(currency.full(1.51));
  print(currency.full(2));
  print(currency.full(2.51));
  print(currency.full(3));
  print(currency.full(10));

  print(currency.short(1513));
  print(currency.short(1513, compact: true));
  print(currency.full(1513));
  print(currency.full(1513, compact: true));

  // 使用动态访问的货币:这将导致所有领土数据被包含在二进制文件中
  print(cld.currencies['eur']!.displayName);

  // 时区
  print('');
  var zone = cld.timeZones.get('europe/athens', dateTime: DateTime(2017))!;

  print('code: ${zone.code}');
  print('canonicalCode: ${zone.canonicalCode}');
  print('ianaCode: ${zone.iana}');
  print('shortCode: ${zone.short}');
  print('exemplarCity: ${zone.exemplarCity}');
  print('country: ${zone.country}');
  print('metaZone code: ${zone.metaZone?.code}');
  print('isPrimaryOrSingle: ${zone.isPrimaryOrSingle}');
  print('dateRange: ${zone.dateRange}');

  // 时区的不同格式
  print('');
  var otherCountry = 'US';
  for (var style in TimeZoneStyle.values) {
    // 偏移量手动获取,使用时区包来获取给定日期/时间的偏移量。
    var zoneNameOwn = zone.format(style, Duration(hours: 2, minutes: 0));
    // 也依赖于当前国家
    var zoneNameOther = zone.format(style, Duration(hours: 2, minutes: 0),
        country: otherCountry);
    print(
        '${style.name.padLeft(28)}: $zoneNameOwn${zoneNameOwn != zoneNameOther ? " (in $otherCountry: $zoneNameOther)".padLeft(50) : ""}');
  }

  // 展示不同时间的时区名称
  print('');
  print(cld.timeZones['America/Buenos_Aires']); // 使用当前 DateTime
  print(cld.timeZones.get('America/Buenos_Aires', dateTime: DateTime(2008)));
  print(cld.timeZones
      .get('America/Buenos_Aires', dateTime: DateTime(2004, 6, 2)));

  // 区域标识符操作
  print('');
  var localeId = LanguageId(lang: 'en', region: 'us');
  print('           toString: $localeId');
  print('            toBCP47: ${localeId.toBCP47()}');
  print('     toUnicodeBCP47: ${localeId.toUnicodeBCP47()}');
  print('      toUnicodeCLDR: ${localeId.toUnicodeCLDR()}');
  print('       canonicalize: ${localeId.canonicalize()}');
  print('   addLikelySubTags: ${localeId.addLikelySubTags()}');
  print('removeLikelySubTags: ${localeId.removeLikelySubTags()}');
  print(
      '  localeDisplayName: ${cld.localeDisplayName.formatWithExtensions(localeId)}');

  print('');
  localeId = LocaleId.parse(
      'en_US_POSIX_fonipa_u_co_phonebk_kb_yes_x_private@calendar=japanese;timezone=europe/paris###');
  print('           toString: $localeId');
  print('            toBCP47: ${localeId.toBCP47()}');
  print('     toUnicodeBCP47: ${localeId.toUnicodeBCP47()}');
  print('      toUnicodeCLDR: ${localeId.toUnicodeCLDR()}');
  print('       canonicalize: ${localeId.canonicalize()}');
  print('   addLikelySubTags: ${localeId.addLikelySubTags()}');
  print('removeLikelySubTags: ${localeId.removeLikelySubTags()}');
  print('  localeDisplayName: ${cld.localeDisplayName.format(localeId)}');
  print(
      '    with Extensions: ${cld.localeDisplayName.formatWithExtensions(localeId)}');

  // 区域显示名称
  print('');
  print(cld.localeDisplayName.format(LanguageId.parse('en-GB')));
  print(cld.localeDisplayName
      .format(LanguageId.parse('en-GB'), preferComposition: true));
  print(cld.localeDisplayName.format(LanguageId.parse('hi-Latn-GB')));
  print(cld.localeDisplayName.format(LanguageId.parse('hi-Latn-GB'),
      preferType: LocaleDisplayNameStyle.variant));
  print(cld.localeDisplayName.format(LanguageId.parse('hi-Latn-GB'),
      preferType: LocaleDisplayNameStyle.short));
  print(cld.localeDisplayName.format(LanguageId.parse('hi-Latn-GB'),
      preferType: LocaleDisplayNameStyle.menu));

  // 不带扩展格式化
  print(cld.localeDisplayName.format(LocaleId.parse(
      'en-GB-scoUse-fonipa-abl1943-u-cu-eur-ms-uksystem-rg-gband-sd-gblnd')));

  // 带扩展格式化(因此会拉取分区、货币和时区数据)
  print(cld.localeDisplayName.formatWithExtensions(LocaleId.parse(
      'en-GB-scoUse-fonipa-abl1943-u-cu-eur-ms-ussystem-rg-gband-sd-gblnd')));
}

更多关于Flutter本地化数据支持插件common_locale_data的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter本地化数据支持插件common_locale_data的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter本地化数据支持插件common_locale_data的代码案例。这个插件可以帮助你管理和使用应用程序中的本地化数据。

首先,确保你已经在pubspec.yaml文件中添加了common_locale_data依赖:

dependencies:
  flutter:
    sdk: flutter
  common_locale_data: ^x.y.z  # 请替换为最新版本号

然后,运行flutter pub get来获取依赖。

接下来,你可以按照以下步骤在你的Flutter应用程序中使用common_locale_data插件:

  1. 定义本地化文件

假设你有两个语言环境的本地化资源,英语(en)和中文(zh)。你可以创建两个JSON文件,比如en.jsonzh.json,内容如下:

en.json

{
  "greeting": "Hello",
  "farewell": "Goodbye"
}

zh.json

{
  "greeting": "你好",
  "farewell": "再见"
}
  1. 加载本地化数据

在你的Flutter应用中,你可以使用common_locale_data来加载这些JSON文件。以下是一个简单的示例,展示了如何加载和使用本地化数据:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:common_locale_data/common_locale_data.dart';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      localizationsDelegates: [
        // 添加common_locale_data的LocalizationsDelegate
        CommonLocaleData.delegate,
        // 添加Flutter自带的localizations delegates
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: CommonLocaleData.supportedLocales,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Locale _currentLocale;
  Map<String, String> _localizedStrings;

  @override
  void initState() {
    super.initState();
    _currentLocale = Localizations.localeOf(context);
    loadLocalizedStrings(_currentLocale.languageCode);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Localization Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_localizedStrings['greeting'] ?? 'Loading...'),
            ElevatedButton(
              onPressed: () {
                // 切换语言示例(这里只是简单地切换英语和中文)
                setState(() {
                  if (_currentLocale.languageCode == 'en') {
                    _currentLocale = Locale('zh');
                  } else {
                    _currentLocale = Locale('en');
                  }
                  loadLocalizedStrings(_currentLocale.languageCode);
                  // 更新应用的语言环境
                  Localizations.overrideLocaleOf(context, _currentLocale);
                });
              },
              child: Text('Switch Language'),
            ),
            Text(_localizedStrings['farewell'] ?? 'Loading...'),
          ],
        ),
      ),
    );
  }

  Future<void> loadLocalizedStrings(String languageCode) async {
    String jsonString = '';
    // 根据语言代码加载对应的JSON文件
    if (languageCode == 'zh') {
      final file = File('${(await getApplicationDocumentsDirectory()).path}/zh.json');
      jsonString = await file.readAsString();
    } else if (languageCode == 'en') {
      final file = File('${(await getApplicationDocumentsDirectory()).path}/en.json');
      jsonString = await file.readAsString();
    }

    // 解析JSON数据
    final Map<String, dynamic> jsonMap = jsonDecode(jsonString);
    setState(() {
      _localizedStrings = jsonMap.map<String, String>((key, value) => MapEntry(key, value.toString()));
    });
  }
}

注意:上面的代码示例中,假设你的JSON文件已经被复制到应用程序的文档目录中。在实际应用中,你可能需要在应用启动时将这些文件从assets复制到文档目录,或者直接使用rootBundle从assets加载它们。这里为了简化示例,直接假设文件已经存在。

使用rootBundle从assets加载JSON文件的示例:

Future<void> loadLocalizedStringsFromAssets(String languageCode) async {
    String jsonString;
    if (languageCode == 'zh') {
      jsonString = await rootBundle.loadString('assets/locales/zh.json');
    } else if (languageCode == 'en') {
      jsonString = await rootBundle.loadString('assets/locales/en.json');
    }

    final Map<String, dynamic> jsonMap = jsonDecode(jsonString);
    setState(() {
      _localizedStrings = jsonMap.map<String, String>((key, value) => MapEntry(key, value.toString()));
    });
}

pubspec.yaml中添加assets:

flutter:
  assets:
    - assets/locales/en.json
    - assets/locales/zh.json

然后调用loadLocalizedStringsFromAssets而不是loadLocalizedStrings即可。

这个示例展示了如何使用common_locale_data插件来管理和加载本地化数据,并通过按钮切换语言环境。当然,根据实际需求,你可能需要进一步优化和扩展这个示例。

回到顶部