diff --git a/.github/workflows/intl4x.yml b/.github/workflows/intl4x.yml index 591859fb..403cd06e 100644 --- a/.github/workflows/intl4x.yml +++ b/.github/workflows/intl4x.yml @@ -20,7 +20,8 @@ jobs: runs-on: ubuntu-latest env: - ICU4X_BUILD_MODE: fetch + ICU4X_BUILD_MODE: checkout + LOCAL_ICU4X_CHECKOUT: ${{ github.workspace }}/submodules/icu4x defaults: run: diff --git a/pkgs/intl4x/CHANGELOG.md b/pkgs/intl4x/CHANGELOG.md index 9ae7f725..7ce621ce 100644 --- a/pkgs/intl4x/CHANGELOG.md +++ b/pkgs/intl4x/CHANGELOG.md @@ -3,6 +3,7 @@ - Add ICU4X support for number formatting. - Add resource identifier annotations. - Add ICU4X support for plural rules. +- Add ICU4X support for display names. ## 0.8.1 diff --git a/pkgs/intl4x/README.md b/pkgs/intl4x/README.md index d5646854..4289516e 100644 --- a/pkgs/intl4x/README.md +++ b/pkgs/intl4x/README.md @@ -18,7 +18,7 @@ via our [issue tracker](https://github.com/dart-lang/i18n/issues)). | | Number format | List format | Date format | Collation | Display names | Plural Rules | |---|:---:|:---:|:---:|:---:|:---:|:---:| | **ECMA402 (web)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| **ICU4X (web/native)** | :heavy_check_mark: | | | :heavy_check_mark: | | | +| **ICU4X (web/native)** | :heavy_check_mark: | | | :heavy_check_mark: | :heavy_check_mark: | | ## Implementation and Goals diff --git a/pkgs/intl4x/build.dart b/pkgs/intl4x/build.dart index e20f0114..8bfec0d5 100644 --- a/pkgs/intl4x/build.dart +++ b/pkgs/intl4x/build.dart @@ -47,7 +47,10 @@ Unknown build mode for icu4x. Set the `ICU4X_BUILD_MODE` environment variable wi path: AssetAbsolutePath(Uri.file(libPath)), ) ], - dependencies: Dependencies([Uri.file('build.dart')]), + dependencies: Dependencies([ + ...buildMode.dependencies, + Uri.file('build.dart'), + ]), ).writeToFile(outDir: config.outDir); } @@ -64,6 +67,8 @@ void unzipFirstFile({required File input, required File output}) { } sealed class BuildMode { + List get dependencies; + Future build(); } @@ -98,6 +103,9 @@ final class FetchMode implements BuildMode { return 'ubuntu'; } } + + @override + List get dependencies => []; } final class LocalMode implements BuildMode { @@ -111,6 +119,9 @@ final class LocalMode implements BuildMode { Future build() async { await File(_localBinaryPath).copy(libPath); } + + @override + List get dependencies => [Uri.file(_localBinaryPath)]; } final class CheckoutMode implements BuildMode { @@ -118,19 +129,27 @@ final class CheckoutMode implements BuildMode { final String libPath; CheckoutMode(this.config, this.libPath); + String? get workingDirectory => Platform.environment['LOCAL_ICU4X_CHECKOUT']; + @override Future build() async { - final workingDirectory = Platform.environment['LOCAL_ICU4X_CHECKOUT']; if (workingDirectory == null) { throw ArgumentError('Specify the ICU4X checkout folder' 'with the LOCAL_ICU4X_CHECKOUT variable'); } final lib = await buildLib( config, - workingDirectory, + workingDirectory!, ); await File(lib).copy(libPath); } + + @override + List get dependencies => Directory(workingDirectory!) + .listSync() + .whereType() + .map((e) => Uri.file(e.path)) + .toList(); } Future buildLib( @@ -165,13 +184,15 @@ Future buildLib( 'buffer_provider', 'logging', 'simple_logger', + 'experimental_components', ]; final noStdFeatures = [ 'default_components', 'compiled_data', 'buffer_provider', 'libc-alloc', - 'panic-handler' + 'panic-handler', + 'experimental_components', ]; final tempDir = Directory.systemTemp.createTempSync(); final linkModeType = @@ -181,7 +202,7 @@ Future buildLib( final arguments = [ if (isNoStd) '+nightly', 'rustc', - '-p={crateName}', + '-p=$crateName', '--crate-type=$linkModeType', '--release', '--config=profile.release.panic="abort"', diff --git a/pkgs/intl4x/dart_test.yaml b/pkgs/intl4x/dart_test.yaml new file mode 100644 index 00000000..1dd7d8d2 --- /dev/null +++ b/pkgs/intl4x/dart_test.yaml @@ -0,0 +1,6 @@ +tags: + icu4xUnimplemented: + on_platform: + vm: + skip: "Not implemented in ICU4X yet. Use -P force to force tests." + presets: { force: { skip: false } } diff --git a/pkgs/intl4x/lib/src/display_names/display_names_4x.dart b/pkgs/intl4x/lib/src/display_names/display_names_4x.dart index 988ec30b..523c9af0 100644 --- a/pkgs/intl4x/lib/src/display_names/display_names_4x.dart +++ b/pkgs/intl4x/lib/src/display_names/display_names_4x.dart @@ -2,11 +2,13 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import '../../display_names.dart'; +import '../bindings/lib.g.dart' as icu; import '../data.dart'; +import '../data_4x.dart'; import '../locale/locale.dart'; -import '../options.dart'; +import '../locale/locale_4x.dart'; import 'display_names_impl.dart'; -import 'display_names_options.dart'; DisplayNamesImpl getDisplayNames4X( Locale locale, @@ -16,35 +18,69 @@ DisplayNamesImpl getDisplayNames4X( DisplayNames4X(locale, data, options); class DisplayNames4X extends DisplayNamesImpl { - DisplayNames4X(super.locale, Data data, super.options); + final icu.LocaleDisplayNamesFormatter _formatter; + final icu.RegionDisplayNames _regionFormatter; + + DisplayNames4X(super.locale, Data data, super.options) + : _formatter = icu.LocaleDisplayNamesFormatter( + data.to4X(), + locale.to4X(), + options.to4X(), + ), + _regionFormatter = icu.RegionDisplayNames( + data.to4X(), + locale.to4X(), + ); @override String ofCalendar(Calendar calendar) { - throw UnimplementedError('Insert diplomat bindings here'); + throw UnsupportedError('Not supported by ICU4X yet.'); } @override String ofCurrency(String currencyCode) { - throw UnimplementedError('Insert diplomat bindings here'); + throw UnsupportedError('Not supported by ICU4X yet.'); } @override String ofDateTime(DateTimeField field) { - throw UnimplementedError('Insert diplomat bindings here'); + throw UnsupportedError('Not supported by ICU4X yet.'); } @override - String ofLanguage(Locale locale) { - throw UnimplementedError('Insert diplomat bindings here'); - } + String ofLanguage(Locale locale) => _formatter.of(locale.to4X()); @override - String ofRegion(String regionCode) { - throw UnimplementedError('Insert diplomat bindings here'); - } + String ofRegion(String regionCode) => _regionFormatter.of(regionCode); @override String ofScript(String scriptCode) { - throw UnimplementedError('Insert diplomat bindings here'); + throw UnsupportedError('Not supported by ICU4X yet.'); + } +} + +extension on DisplayNamesOptions { + icu.DisplayNamesOptions to4X() { + final icuStyle = switch (style) { + Style.narrow => icu.DisplayNamesStyle.narrow, + Style.short => icu.DisplayNamesStyle.short, + Style.long => icu.DisplayNamesStyle.long, + }; + + final icuFallback = switch (fallback) { + Fallback.code => icu.DisplayNamesFallback.code, + Fallback.none => icu.DisplayNamesFallback.none, + }; + + final icuLanguageDisplay = switch (languageDisplay) { + LanguageDisplay.dialect => icu.LanguageDisplay.dialect, + LanguageDisplay.standard => icu.LanguageDisplay.standard, + }; + + return icu.DisplayNamesOptions( + style: icuStyle, + fallback: icuFallback, + languageDisplay: icuLanguageDisplay, + ); } } diff --git a/pkgs/intl4x/lib/src/display_names/display_names_impl.dart b/pkgs/intl4x/lib/src/display_names/display_names_impl.dart index e3eb16ed..01a0be6a 100644 --- a/pkgs/intl4x/lib/src/display_names/display_names_impl.dart +++ b/pkgs/intl4x/lib/src/display_names/display_names_impl.dart @@ -9,7 +9,8 @@ import '../ecma/ecma_policy.dart'; import '../locale/locale.dart'; import '../options.dart'; import '../utils.dart'; -import 'display_names_4x.dart'; +import 'display_names_4x.dart' + if (dart.library.js) 'display_names_stub_4x.dart'; import 'display_names_options.dart'; import 'display_names_stub.dart' if (dart.library.js) 'display_names_ecma.dart'; diff --git a/pkgs/intl4x/lib/src/display_names/display_names_stub_4x.dart b/pkgs/intl4x/lib/src/display_names/display_names_stub_4x.dart new file mode 100644 index 00000000..48f3d2fa --- /dev/null +++ b/pkgs/intl4x/lib/src/display_names/display_names_stub_4x.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../../display_names.dart'; +import '../data.dart'; +import '../locale/locale.dart'; +import 'display_names_impl.dart'; + +DisplayNamesImpl getDisplayNames4X( + Locale locale, + Data data, + DisplayNamesOptions options, +) => + throw UnimplementedError('Cannot use ICU4X in web environments.'); diff --git a/pkgs/intl4x/test/display_names_test.dart b/pkgs/intl4x/test/display_names_test.dart new file mode 100644 index 00000000..f5462383 --- /dev/null +++ b/pkgs/intl4x/test/display_names_test.dart @@ -0,0 +1,141 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:intl4x/display_names.dart'; +import 'package:intl4x/intl4x.dart'; +import 'package:test/test.dart'; + +import 'utils.dart'; + +void main() { + testWithFormatting('basic', () { + expect( + Intl(locale: const Locale(language: 'en', region: 'US')) + .displayNames() + .ofLanguage(const Locale(language: 'de', region: 'DE')), + 'German (Germany)'); + }); + + testWithFormatting('language', () { + String languageOf(Locale locale, Locale language) => Intl(locale: locale) + .displayNames(const DisplayNamesOptions(style: Style.long)) + .ofLanguage(language); + + const en = Locale(language: 'en'); + const fr = Locale(language: 'fr'); + const de = Locale(language: 'de'); + const zh = Locale(language: 'zh', script: 'Hant'); + expect(languageOf(en, fr), 'French'); + expect(languageOf(en, de), 'German'); + expect(languageOf(en, const Locale(language: 'fr', region: 'CA')), + 'Canadian French'); + //TODO(mosuem): Skip as ECMA seems to have a bug here. + // expect(languageOf(en, zh), 'Traditional Chinese'); + expect(languageOf(en, const Locale(language: 'en', region: 'US')), + 'American English'); + //TODO(mosuem): Skip as ECMA seems to have a bug here. + // expect(languageOf(en, const Locale(language: 'zh', region: 'TW')), + // 'Chinese (Taiwan)'); + + expect(languageOf(zh, fr), '法文'); + expect(languageOf(zh, const Locale(language: 'zh')), '中文'); + expect(languageOf(zh, de), '德文'); + }); + + testWithFormatting( + 'language with languageDisplay', + () { + String languageWith(LanguageDisplay display) => + Intl(locale: const Locale(language: 'en')) + .displayNames(DisplayNamesOptions(languageDisplay: display)) + .ofLanguage(const Locale(language: 'en', region: 'GB')); + + expect(languageWith(LanguageDisplay.dialect), 'British English'); + expect( + languageWith(LanguageDisplay.standard), 'English (United Kingdom)'); + }, + tags: ['icu4xUnimplemented'], + ); + + testWithFormatting( + 'calendar', + () { + final displayNames = + Intl(locale: const Locale(language: 'en')).displayNames(); + + expect(displayNames.ofCalendar(Calendar.roc), 'Minguo Calendar'); + expect(displayNames.ofCalendar(Calendar.gregory), 'Gregorian Calendar'); + expect(displayNames.ofCalendar(Calendar.chinese), 'Chinese Calendar'); + }, + tags: ['icu4xUnimplemented'], + ); + + testWithFormatting( + 'dateTimeField', + () { + final displayNames = + Intl(locale: const Locale(language: 'pt')).displayNames(); + expect(displayNames.ofDateTime(DateTimeField.era), 'era'); + expect(displayNames.ofDateTime(DateTimeField.year), 'ano'); + expect(displayNames.ofDateTime(DateTimeField.month), 'mês'); + expect(displayNames.ofDateTime(DateTimeField.quarter), 'trimestre'); + expect(displayNames.ofDateTime(DateTimeField.weekOfYear), 'semana'); + expect(displayNames.ofDateTime(DateTimeField.weekday), 'dia da semana'); + expect(displayNames.ofDateTime(DateTimeField.dayPeriod), 'AM/PM'); + expect(displayNames.ofDateTime(DateTimeField.day), 'dia'); + expect(displayNames.ofDateTime(DateTimeField.hour), 'hora'); + expect(displayNames.ofDateTime(DateTimeField.minute), 'minuto'); + expect(displayNames.ofDateTime(DateTimeField.second), 'segundo'); + }, + tags: ['icu4xUnimplemented'], + ); + + testWithFormatting( + 'currency', + () { + expect( + Intl(locale: const Locale(language: 'pt')) + .displayNames() + .ofCurrency('USD'), + 'Dólar americano', + ); + }, + tags: ['icu4xUnimplemented'], + ); + + testWithFormatting( + 'script', + () { + expect( + Intl(locale: const Locale(language: 'fr')) + .displayNames() + .ofScript('Egyp'), + 'hiéroglyphes égyptiens', + ); + }, + tags: ['icu4xUnimplemented'], + ); + + testWithFormatting('region', () { + String regionNames(Locale locale, String region) => + Intl(locale: locale).displayNames().ofRegion(region); + + const en = Locale(language: 'en'); + expect(regionNames(en, '419'), 'Latin America'); + expect(regionNames(en, 'BZ'), 'Belize'); + expect(regionNames(en, 'US'), 'United States'); + expect(regionNames(en, 'BA'), 'Bosnia & Herzegovina'); + expect(regionNames(en, 'MM'), 'Myanmar (Burma)'); + + const zh = Locale(language: 'zh', script: 'Hant'); + expect(regionNames(zh, '419'), '拉丁美洲'); + expect(regionNames(zh, 'BZ'), '貝里斯'); + expect(regionNames(zh, 'US'), '美國'); + expect(regionNames(zh, 'BA'), '波士尼亞與赫塞哥維納'); + expect(regionNames(zh, 'MM'), '緬甸'); + + const es = Locale(language: 'es', region: '419'); + expect(regionNames(es, 'DE'), 'Alemania'); + }); +} diff --git a/submodules/icu4x b/submodules/icu4x index af4bbdba..8387180c 160000 --- a/submodules/icu4x +++ b/submodules/icu4x @@ -1 +1 @@ -Subproject commit af4bbdba5f9715726caa75528f8b95440db19948 +Subproject commit 8387180cc2d2051a6cbafcfb967d45d6ff3ba495