diff --git a/executors/dart/bin/executor.dart b/executors/dart/bin/executor.dart index bef1677a..5973107e 100644 --- a/executors/dart/bin/executor.dart +++ b/executors/dart/bin/executor.dart @@ -47,32 +47,36 @@ void main() { } catch (e) { throw 'ERRORSTART $line ERROREND'; } - - final testType = TestTypes.values.firstWhereOrNull( - (type) => type.name == decoded['test_type'], - ); - final outputLine = switch (testType) { - TestTypes.collation => testCollation(line), - TestTypes.decimal_fmt => testDecimalFormatWrapped(line), - TestTypes.number_fmt => testDecimalFormatWrapped(line), - TestTypes.datetime_fmt => testDateTimeFmt(line), - TestTypes.display_names => throw UnimplementedError( - 'display_names is not supported yet', - ), - TestTypes.lang_names => testLangNames(line), - // TestTypes.likely_subtags => testLikelySubtags(line), - TestTypes.likely_subtags => throw UnimplementedError( - 'likely_subtags is not supported yet, as the Locale object is not yet migrated to ICU4X', - ), - TestTypes.list_fmt => testListFmt(line), - TestTypes.plural_rules => testPluralRules(line), - null => throw ArgumentError.value( - decoded['test_type'], - 'Unknown test type', - ), - }; - - print(outputLine); + try { + final testType = TestTypes.values.firstWhereOrNull( + (type) => type.name == decoded['test_type'], + ); + final outputLine = switch (testType) { + TestTypes.collation => testCollation(line), + TestTypes.decimal_fmt => testDecimalFormatWrapped(line), + TestTypes.number_fmt => testDecimalFormatWrapped(line), + TestTypes.datetime_fmt => testDateTimeFmt(line), + TestTypes.display_names => throw UnimplementedError( + 'display_names is not supported yet', + ), + TestTypes.lang_names => testLangNames(line), + // TestTypes.likely_subtags => testLikelySubtags(line), + TestTypes.likely_subtags => throw UnimplementedError( + 'likely_subtags is not supported yet, as the Locale object is not yet migrated to ICU4X', + ), + TestTypes.list_fmt => testListFmt(line), + TestTypes.plural_rules => testPluralRules(line), + null => throw ArgumentError.value( + decoded['test_type'], + 'Unknown test type', + ), + }; + print(outputLine); + } catch (e, s) { + throw ArgumentError( + 'Error while executing on $line. Error was:\n $e \n $s', + ); + } } } } diff --git a/executors/dart/lib/collator.dart b/executors/dart/lib/collator.dart index 84e87a57..e9a07f5e 100644 --- a/executors/dart/lib/collator.dart +++ b/executors/dart/lib/collator.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:intl4x/collation.dart'; -import 'package:intl4x/intl4x.dart'; String testCollation(String jsonEncoded) { final json = jsonDecode(jsonEncoded) as Map; @@ -52,16 +51,13 @@ String testCollation(String jsonEncoded) { }); } else { try { - final coll = Intl(locale: Locale.parse(localeString)); - - final collationOptions = CollationOptions( + final compared = Collation( + locale: Locale.parse(localeString), ignorePunctuation: ignorePunctuation, sensitivity: sensitivity, numeric: numeric, caseFirst: caseFirst, - ); - - final compared = coll.collation(collationOptions).compare(s1, s2); + ).compare(s1, s2); bool result; if (compareType == '=') { @@ -91,3 +87,14 @@ String testCollation(String jsonEncoded) { } return jsonEncode(outputLine); } + +// Copied from intl4x/lib/src/collation/collation_ecma.dart +extension on CaseFirst { + /// The JavaScript-compatible string representation of the case first option. + String get jsName => switch (this) { + // Map the custom name 'localeDependent' to 'false'. + CaseFirst.localeDependent => 'false', + // All other cases implicitly use the enum's name (e.g., 'upper', 'lower'). + _ => name, + }; +} diff --git a/executors/dart/lib/datetime_format.dart b/executors/dart/lib/datetime_format.dart index 21c270f8..9eb57b7d 100644 --- a/executors/dart/lib/datetime_format.dart +++ b/executors/dart/lib/datetime_format.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:intl4x/datetime_format.dart'; -import 'package:intl4x/intl4x.dart'; /// Tests date/time formatting using intl4x. /// @@ -40,10 +39,9 @@ String testDateTimeFmt(String jsonEncoded) { } // Initialize DateTimeFormatOptions - var dateTimeFormatOptions = DateTimeFormatOptions(); - // Handle calendar and timezone from test_optionsJson String? calendarString; + Calendar? calendar; if (testOptionsJson.containsKey('calendar')) { calendarString = testOptionsJson['calendar'] as String; // Note: intl4x's DateTimeFormatOptions directly supports calendar as a string. @@ -51,10 +49,8 @@ String testDateTimeFmt(String jsonEncoded) { // is not directly available in `intl4x`'s current Locale API for all calendars. // If a calendar is unsupported, the `DateTimeFormat` constructor might throw, // or it might fall back. We'll rely on intl4x's internal handling. - dateTimeFormatOptions = dateTimeFormatOptions.copyWith( - calendar: Calendar.values.firstWhereOrNull( - (calendar) => calendar.jsName == calendarString, - ), + calendar = Calendar.values.firstWhereOrNull( + (calendar) => calendar.jsName == calendarString, ); } @@ -67,13 +63,25 @@ String testDateTimeFmt(String jsonEncoded) { offsetSeconds = (json['tz_offset_secs'] as num).toInt(); } + final skeleton = testOptionsJson['skeleton'] as String?; final semanticSkeleton = testOptionsJson['semanticSkeleton'] as String?; final semanticSkeletonLength = testOptionsJson['semanticSkeletonLength'] as String?; final dateStyle = testOptionsJson['dateStyle'] as String?; final timeStyle = testOptionsJson['timeStyle'] as String?; - final yearStyle = testOptionsJson['yearStyle'] as String?; + + final timePrecision = switch (timeStyle) { + 'full' => TimePrecision.second, + 'long' => TimePrecision.second, + 'medium' => TimePrecision.second, + 'short' => TimePrecision.minute, + _ => null, + }; + final yearStyle = switch (testOptionsJson['yearStyle'] as String?) { + 'with_era' => YearStyle.withEra, + _ => null, + }; DateTime? testDate; if (json['input_string'] != null) { @@ -93,31 +101,33 @@ String testDateTimeFmt(String jsonEncoded) { returnJson['input_string'] = isoDateString; return jsonEncode(returnJson); } - } else { - testDate = DateTime.now(); } - final dtFormatter = Intl( - locale: locale, - ).dateTimeFormat(dateTimeFormatOptions); - try { final formatter = semanticSkeleton != null ? getFormatterForSkeleton( semanticSkeleton, semanticSkeletonLength, - dtFormatter, + locale, + yearStyle, + timePrecision, ) - : getFormatterForStyle(dateStyle, timeStyle, yearStyle, dtFormatter); + : getFormatterForStyle( + dateStyle, + timeStyle, + yearStyle, + locale, + timePrecision, + ); String formattedDt; - if (timeStyle == 'full' && timeZoneName != null) { + if (formatter is DateTimeFormatter && + (semanticSkeleton ?? '').contains('Z')) { final offset = Duration(seconds: offsetSeconds!); - final timeZone = TimeZone(name: timeZoneName, offset: offset); - final timeZoneStyle = 'long'; - final zonedFormatter = getZonedFormatter(timeZoneStyle, formatter); - formattedDt = zonedFormatter.format(testDate.add(offset), timeZone); + final zoneStyle = testOptionsJson['zoneStyle'] as String?; + final zonedFormatter = getZonedFormatter(zoneStyle, formatter, skeleton); + formattedDt = zonedFormatter.format(testDate!.add(offset), timeZoneName!); } else { - formattedDt = formatter.format(testDate); + formattedDt = formatter.format(testDate!); } returnJson['result'] = formattedDt; } on Exception catch (e) { @@ -125,97 +135,180 @@ String testDateTimeFmt(String jsonEncoded) { returnJson['unsupported'] = ': ${e.toString()}'; return jsonEncode(returnJson); } - returnJson['actual_options'] = dateTimeFormatOptions.humanReadable; + returnJson['actual_options'] = { + 'locale': locale.toString(), + if (dateStyle != null) 'dateStyle': dateStyle, + if (timeStyle != null) 'timeStyle': timeStyle, + if (yearStyle != null) 'yearStyle': yearStyle.name, + if (calendar != null) 'calendar': calendar.jsName, + }; returnJson['options'] = testOptionsJson; return jsonEncode(returnJson); } -DateTimeFormatter getFormatterForSkeleton( +DateTimeFormatterStandalone getFormatterForSkeleton( String semanticSkeleton, String? semanticSkeletonLength, - DateTimeFormatBuilder dtFormatter, + Locale locale, + YearStyle? yearStyle, + TimePrecision? timePrecision, ) { // The provided Rust code implies a more complex logic, but here we'll map the known skeletons. // The Rust code's `None => None` and `None => Ok(...)` branches aren't directly translatable // to a Dart function that must return a Formatter. We'll handle the valid cases and throw for others. final semanticDateStyle = switch (semanticSkeletonLength) { - 'short' => DateFormatStyle.short, - 'medium' => DateFormatStyle.medium, - 'long' => DateFormatStyle.long, + 'short' => DateTimeLength.short, + 'medium' => DateTimeLength.medium, + 'long' => DateTimeLength.long, _ => throw Exception(), }; + return switch (semanticSkeleton) { - 'D' || 'DT' || 'DTZ' => dtFormatter.d(), - 'MD' => dtFormatter.md(), - 'MDT' || 'MDTZ' => dtFormatter.mdt(dateStyle: semanticDateStyle), - 'YMD' || 'YMDT' || 'YMDTZ' => dtFormatter.ymd(dateStyle: semanticDateStyle), - 'YMDE' => dtFormatter.ymde(dateStyle: semanticDateStyle), - 'YMDET' || 'YMDETZ' => dtFormatter.ymdet(dateStyle: semanticDateStyle), - 'M' => dtFormatter.m(), - 'Y' => dtFormatter.y(), - 'T' || 'Z' || 'TZ' => dtFormatter.t(), + 'D' || + 'DT' || + 'DTZ' => DateTimeFormat.day(locale: locale, length: semanticDateStyle), + 'MD' => DateTimeFormat.monthDay(locale: locale, length: semanticDateStyle), + 'MDT' || 'MDTZ' => DateTimeFormat.monthDayTime( + locale: locale, + length: semanticDateStyle, + timePrecision: timePrecision, + ), + 'YMD' || 'YMDT' || 'YMDTZ' => DateTimeFormat.yearMonthDay( + locale: locale, + length: semanticDateStyle, + ), + 'YMDE' => DateTimeFormat.yearMonthDayWeekday( + locale: locale, + length: semanticDateStyle, + yearStyle: yearStyle, + ), + 'YMDET' || 'YMDETZ' => DateTimeFormat.yearMonthDayWeekdayTime( + locale: locale, + length: semanticDateStyle, + timePrecision: timePrecision, + ), + 'M' => DateTimeFormat.month(locale: locale, length: semanticDateStyle), + 'Y' => DateTimeFormat.year(locale: locale, length: semanticDateStyle), + 'T' || 'TZ' => DateTimeFormat.time( + locale: locale, + length: semanticDateStyle, + timePrecision: timePrecision, + ), _ => throw Exception('Unknown skeleton: $semanticSkeleton'), }; } ZonedDateTimeFormatter getZonedFormatter( - String timeZoneStyle, + String? timeZoneStyle, DateTimeFormatter formatter, -) => switch (timeZoneStyle) { - 'short' => formatter.withTimeZoneShort(), - 'long' => formatter.withTimeZoneLong(), - 'full' => formatter.withTimeZoneLongGeneric(), - String() => throw Exception('Unknown time zone style `$timeZoneStyle`'), -}; + String? skeleton, +) { + if (skeleton != null) { + // Long Generic + if (skeleton.contains('vvvv') || skeleton.contains('VVVV')) { + return formatter.withTimeZoneLongGeneric(); + } + // Short Generic + else if (skeleton.contains('v') || skeleton.contains('V')) { + return formatter.withTimeZoneShortGeneric(); + } + // Long Offset + else if (skeleton.contains('OOOO')) { + return formatter.withTimeZoneLongOffset(); + } + // Short Offset + else if (skeleton.contains('O')) { + return formatter.withTimeZoneShortOffset(); + } + // Long Specific (Name) + else if (skeleton.contains('zzzz')) { + return formatter.withTimeZoneLong(); + } + // Short Specific (Name) + else if (skeleton.contains('z')) { + return formatter.withTimeZoneShort(); + } + } + return switch (timeZoneStyle) { + 'short' => formatter.withTimeZoneShort(), + 'specific' => formatter.withTimeZoneShort(), + 'full' => formatter.withTimeZoneLongGeneric(), + 'generic' => formatter.withTimeZoneLongGeneric(), + 'location' => formatter.withTimeZoneShort(), + 'offset' => formatter.withTimeZoneLongGeneric(), + null => formatter.withTimeZoneLongGeneric(), + String() => throw Exception('Unknown time zone style `$timeZoneStyle`'), + }; +} DateTimeFormatter getFormatterForStyle( String? dateStyle, String? timeStyle, - String? yearStyle, - DateTimeFormatBuilder dtFormatter, -) => switch ((dateStyle, timeStyle, yearStyle)) { - ('medium', null, _) => dtFormatter.ymd(dateStyle: DateFormatStyle.medium), - (null, 'short', _) => dtFormatter.t(style: TimeFormatStyle.short), - ('full', 'short', null) => dtFormatter.ymdet( - dateStyle: DateFormatStyle.full, - timeStyle: TimeFormatStyle.short, - ), - ('full', 'full', null) => dtFormatter.ymdet( - dateStyle: DateFormatStyle.full, - timeStyle: TimeFormatStyle.full, - ), - ('short', 'full', null) => dtFormatter.ymdt( - dateStyle: DateFormatStyle.short, - timeStyle: TimeFormatStyle.full, - ), - ('short', 'full', 'with_era') => dtFormatter.ymdet( - dateStyle: DateFormatStyle.short, - timeStyle: TimeFormatStyle.full, - ), - (_, _, 'with_era') => dtFormatter.ymde(), - (_, _, _) => throw Exception( - 'Unknown combination of date style `$dateStyle`, time style `$timeStyle`, and year style `$yearStyle`', - ), -}; - -extension on DateTimeFormatOptions { - String get humanReadable { - final fields = { - if (calendar != null) 'calendar': calendar, - if (dayPeriod != null) 'dayPeriod': dayPeriod, - if (numberingSystem != null) 'numberingSystem': numberingSystem, - if (clockstyle != null) 'clockstyle': clockstyle, - if (era != null) 'era': era, - if (timestyle != null) 'timestyle': timestyle, - if (fractionalSecondDigits != null) - 'fractionalSecondDigits': fractionalSecondDigits, - 'formatMatcher': formatMatcher, - }; - final entries = fields.entries - .map((e) => '${e.key}: ${e.value}') - .join(', '); - return 'DateTimeFormatOptions($entries)'; - } + YearStyle? yearStyle, + Locale locale, + TimePrecision? timePrecision, +) { + print((dateStyle, timeStyle, yearStyle)); + return switch ((dateStyle, timeStyle, yearStyle)) { + ('medium', null, null) => DateTimeFormat.yearMonthDay( + locale: locale, + length: DateTimeLength.medium, + ), + (null, 'short', _) => DateTimeFormat.time( + locale: locale, + length: DateTimeLength.short, + timePrecision: timePrecision, + ), + ('full', 'short', null) => DateTimeFormat.yearMonthDayWeekdayTime( + locale: locale, + length: DateTimeLength.long, + timePrecision: timePrecision, + ), + ('full', 'full', null) => DateTimeFormat.yearMonthDayWeekdayTime( + locale: locale, + length: DateTimeLength.long, + timePrecision: timePrecision, + ), + ('short', 'full', null) => DateTimeFormat.yearMonthDayTime( + locale: locale, + length: DateTimeLength.short, + ), + ('short', 'full', YearStyle.withEra) => + DateTimeFormat.yearMonthDayWeekdayTime( + locale: locale, + length: DateTimeLength.short, + yearStyle: yearStyle, + timePrecision: timePrecision, + ), + (_, _, YearStyle.withEra) => DateTimeFormat.yearMonthDayWeekday( + locale: locale, + yearStyle: yearStyle, + ), + (_, _, _) => throw Exception( + 'Unknown combination of date style `$dateStyle`, time style `$timeStyle`, and year style `$yearStyle`', + ), + }; +} + +// Copied from intl4x/lib/src/locale/locale.dart +extension CalendarJsName on Calendar { + /// Returns the JavaScript-compatible name for the calendar. + /// + /// This implementation uses a switch expression to map specific enum + /// values to their corresponding JS names, falling back to the enum's + /// `name` for others. + String get jsName => switch (this) { + Calendar.traditionalChinese => 'chinese', + Calendar.traditionalKorean => 'dangi', + Calendar.ethiopianAmeteAlem => 'ethioaa', + Calendar.ethiopian => 'ethiopic', + Calendar.gregorian => 'gregory', + Calendar.hijriUmalqura => 'islamic-umalqura', + Calendar.hijriTbla => 'islamic-tbla', + Calendar.hijriCivil => 'islamic-civil', + Calendar.minguo => 'roc', + _ => name, + }; } diff --git a/executors/dart/lib/lang_names.dart b/executors/dart/lib/lang_names.dart index f04c2e3b..4e729089 100644 --- a/executors/dart/lib/lang_names.dart +++ b/executors/dart/lib/lang_names.dart @@ -1,7 +1,7 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:intl4x/display_names.dart'; -import 'package:intl4x/intl4x.dart'; String testLangNames(String jsonEncoded) { final json = jsonDecode(jsonEncoded) as Map; @@ -10,8 +10,7 @@ String testLangNames(String jsonEncoded) { final Locale locale; try { - if (json['locale_label'] != null) { - final localeJson = json['locale_label'] as String; + if (json['locale_label'] case final String localeJson?) { locale = Locale.parse(localeJson); } else { locale = Locale.parse('en'); @@ -33,6 +32,9 @@ String testLangNames(String jsonEncoded) { }); return jsonEncode(outputLine); } + final languageDisplay = LanguageDisplay.values.firstWhereOrNull( + (element) => element.name == json['languageDisplay'], + ); Locale languageLabelLocale; try { @@ -47,28 +49,24 @@ String testLangNames(String jsonEncoded) { return jsonEncode(outputLine); } - final options = DisplayNamesOptions( - languageDisplay: LanguageDisplay.standard, - ); try { - final displayNames = Intl(locale: locale).displayNames(options); - final resultLangName = displayNames.ofLanguage(languageLabelLocale); + final displayNames = DisplayNames( + locale: locale, + languageDisplay: languageDisplay ?? LanguageDisplay.dialect, + ); + final resultLangName = displayNames.ofLocale(languageLabelLocale); outputLine['result'] = resultLangName; } catch (error) { outputLine.addAll({ 'error_detail': error.toString(), - 'actual_options': options.toJson(), + 'actual_options': { + 'languageDisplay': languageDisplay?.name, + 'locale_label': locale.toString(), + 'language_label': languageLabelLocale.toString(), + }, 'error_retry': false, // Do not repeat }); } return jsonEncode(outputLine); } - -extension on DisplayNamesOptions { - Map toJson() => { - 'style': style.name, - 'languageDisplay': languageDisplay.name, - 'fallback': fallback.name, - }; -} diff --git a/executors/dart/lib/list_format.dart b/executors/dart/lib/list_format.dart index 58e5614c..b90902f4 100644 --- a/executors/dart/lib/list_format.dart +++ b/executors/dart/lib/list_format.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:collection/collection.dart'; -import 'package:intl4x/intl4x.dart'; -import 'package:intl4x/list_format.dart'; // Import for ListFormat and ListFormatOptions +import 'package:intl4x/list_format.dart' + show ListStyle, Locale, ListType, ListFormat; /// Tests Intl Locale for list formatting. /// @@ -29,22 +29,21 @@ String testListFmt(String jsonEncoded) { final Map? optionsJson = json['options']; - ListFormatOptions? listFormatOptions; + ListStyle? style; + ListType? type; if (optionsJson != null) { try { - listFormatOptions = ListFormatOptions( - style: - ListStyle.values.firstWhereOrNull( - (style) => style.name == optionsJson['style'] as String?, - ) ?? - ListStyle.long, - type: switch (optionsJson['list_type'] as String?) { - 'conjunction' => Type.and, - 'disjunction' => Type.or, - 'unit' => Type.unit, - _ => Type.and, - }, - ); + style = + ListStyle.values.firstWhereOrNull( + (style) => style.name == optionsJson['style'] as String?, + ) ?? + ListStyle.long; + type = switch (optionsJson['list_type'] as String?) { + 'conjunction' => ListType.and, + 'disjunction' => ListType.or, + 'unit' => ListType.unit, + _ => ListType.and, + }; } catch (e) { returnJson.addAll({ 'error': 'CONSTRUCTOR: Invalid options format', @@ -62,9 +61,12 @@ String testListFmt(String jsonEncoded) { } final inputList = inputListDynamic.map((e) => e.toString()).toList(); - final listFormatter = Intl( - locale: locale, - ).listFormat(listFormatOptions ?? ListFormatOptions()); + final listFormatter = switch ((style, type)) { + (final s?, final t?) => ListFormat(locale: locale, style: s, type: t), + (_, final t?) => ListFormat(locale: locale, type: t), + (final s?, _) => ListFormat(locale: locale, style: s), + (_, _) => ListFormat(locale: locale), + }; try { final result = listFormatter.format(inputList); diff --git a/executors/dart/lib/numberformat.dart b/executors/dart/lib/numberformat.dart index e2a8caf3..b4e480d6 100644 --- a/executors/dart/lib/numberformat.dart +++ b/executors/dart/lib/numberformat.dart @@ -1,108 +1,20 @@ import 'dart:convert'; -import 'package:intl4x/intl4x.dart'; +import 'package:collection/collection.dart'; +import 'package:intl4x/datetime_format.dart'; import 'package:intl4x/number_format.dart'; +// ignore: implementation_imports +import 'package:intl4x/src/ecma/ecma_native.dart' + if (dart.library.js_interop) 'package:intl4x/src/ecma/ecma_web.dart'; +// ignore: implementation_imports +import 'package:intl4x/src/number_format/number_format_impl.dart'; +// ignore: implementation_imports +import 'package:intl4x/src/number_format/number_format_options.dart'; String testDecimalFormat(String encoded, bool loggingEnabled, String version) { - //just some call to not treeshake the function - final json = jsonDecode(encoded) as Map; - final jsonOptions = (json['options'] ?? {}) as Map; - final getUnsupportedOptionsForNode = _getUnsupportedOptionsForNode( - jsonOptions, - version, - loggingEnabled, - ); - return testDecimalFormatWrapped( - encoded, - loggingEnabled, - getUnsupportedOptionsForNode, - ); -} - -List _getUnsupportedOptionsForNode( - Map jsonOptions, - String nodeVersion, - bool doLogInput, -) { - List versionSupportedOptions; - if (nodeVersion.compareTo(_firstV3Version) >= 0) { - if (doLogInput) { - print('#V3 !!!! $nodeVersion'); - } - versionSupportedOptions = _supportedOptionsByVersion[NodeVersion.v3]!; - } else { - if (doLogInput) { - print('#pre_v3 !!!! $nodeVersion'); - } - versionSupportedOptions = _supportedOptionsByVersion[NodeVersion.preV3]!; - } - if (doLogInput) { - print('#NNNN $versionSupportedOptions'); - } - - final unsupportedOptions = []; - // Check for option items that are not supported - for (var key in jsonOptions.keys) { - if (!versionSupportedOptions.contains(key)) { - unsupportedOptions.add('$key:${jsonOptions[key]}'); - } - } - return unsupportedOptions; + return testDecimalFormatWrapped(encoded, loggingEnabled); } -// The nodejs version that first supported advance rounding options -const _firstV3Version = 'v20.1.0'; - -enum NodeVersion { v3, preV3 } - -// Use this -const _supportedOptionsByVersion = { - NodeVersion.v3: [ - 'compactDisplay', - 'currency', - 'currencyDisplay', - 'currencySign', - 'localeMatcher', - 'notation', - 'numberingSystem', - 'signDisplay', - 'style', - 'unit', - 'unitDisplay', - 'useGrouping', - 'roundingMode', - 'roundingPriority', - 'roundingIncrement', - 'trailingZeroDisplay', - 'minimumIntegerDigits', - 'minimumFractionDigits', - 'maximumFractionDigits', - 'minimumSignificantDigits', - 'maximumSignificantDigits', - ], - NodeVersion.preV3: [ - 'compactDisplay', - 'currency', - 'currencyDisplay', - 'currencySign', - 'localeMatcher', - 'notation', - 'numberingSystem', - 'signDisplay', - 'style', - 'unit', - 'unitDisplay', - 'useGrouping', - 'roundingMode', - 'minimumIntegerDigits', - 'minimumFractionDigits', - 'maximumFractionDigits', - 'minimumSignificantDigits', - 'maximumSignificantDigits', - ], - // TODO: Add older version support. -}; - final _patternsToOptions = { '0.0': NumberFormatOptions.custom( digits: Digits.withFractionDigits(minimum: 1), @@ -127,11 +39,7 @@ const _unsupportedSkeletonTerms = [ 'decimal-always', ]; -String testDecimalFormatWrapped( - String encoded, [ - bool loggingEnabled = false, - List unsupportedOptionsForNode = const [], -]) { +String testDecimalFormatWrapped(String encoded, [bool loggingEnabled = false]) { final json = jsonDecode(encoded) as Map; final label = json['label'] as String?; final skeleton = json['skeleton'] as String?; @@ -163,11 +71,23 @@ String testDecimalFormatWrapped( // Some error - to return this message return jsonEncode({ 'error': error.toString(), - 'error_type': '_decimalPatternToOptions', + 'error_type': 'unsupported', 'label': label, }); } } + + if (!useBrowser && + (options.style is UnitStyle || + options.style is PercentStyle || + options.style is CurrencyStyle)) { + return jsonEncode({ + 'error': 'Unit, percent, or currency style are not supported yet', + 'error_type': 'unsupported', + 'unsupported': 'unsupported_options', + 'label': label, + }); + } // Default maximumFractionDigits and rounding modes are set in test generation // Check each option for implementation. @@ -192,18 +112,10 @@ String testDecimalFormatWrapped( } } - // Supported options depends on the nodejs version - if (loggingEnabled) { - print('#NNNN $unsupportedOptionsForNode'); - } - final unsupportedSkeletonTerms = _getUnsupportedSkeletonterms( + final unsupportedOptions = _getUnsupportedSkeletonterms( skeletonTerms, loggingEnabled, ); - final unsupportedOptions = [ - ...unsupportedOptionsForNode, - ...unsupportedSkeletonTerms, - ]; if (unsupportedOptions.isNotEmpty) { return jsonEncode({ @@ -216,28 +128,29 @@ String testDecimalFormatWrapped( final testLocale = json['locale'] as String?; - Intl intl; Map outputLine; + Locale locale; try { if (testLocale != null) { - intl = Intl(locale: Locale.parse(testLocale)); + locale = Locale.parse(testLocale); } else { - intl = Intl(locale: Locale.parse('und')); + locale = Locale.parse('und'); } - final nf = intl.numberFormat(options); + final nf = NumberFormatImpl.build(locale, options); // TODO: Catch unsupported units, e.g., furlongs. outputLine = { 'label': json['label'], - 'result': nf.format(input), + 'result': nf.formatImpl(input), 'actual_options': options.toMapString(), }; - } catch (error) { + } catch (error, stacktrace) { // Handle type of the error outputLine = { 'label': json['label'], 'error': 'formatting error: $error', + 'stacktrace': stacktrace.toString(), 'actual_options': options.toMapString(), }; @@ -298,6 +211,7 @@ NumberFormatOptions _fromJson(Map options) { final unitDisplay = UnitDisplay.values .where((element) => element.name == options['unitDisplay']) .firstOrNull; + final currency = options['currency']; final currencyDisplay = CurrencyDisplay.values .where((element) => element.name == options['currencyDisplay']) @@ -308,6 +222,11 @@ NumberFormatOptions _fromJson(Map options) { CurrencyStyle( currency: currency, display: currencyDisplay ?? CurrencyDisplay.symbol, + sign: + CurrencySign.values.firstWhereOrNull( + (element) => element.name == options['currencySign'] as String?, + ) ?? + CurrencySign.standard, ), if (unit != null) UnitStyle(unit: unit, unitDisplay: unitDisplay ?? UnitDisplay.short), @@ -375,7 +294,9 @@ NumberFormatOptions _fromJson(Map options) { signDisplay: signDisplay, notation: notation, useGrouping: useGrouping, - numberingSystem: options['numberingSystem'], + numberingSystem: NumberingSystem.values.firstWhereOrNull( + (element) => element.jsName == options['numberingSystem'], + ), roundingMode: roundingMode, trailingZeroDisplay: trailingZeroDisplay, minimumIntegerDigits: options['minimumIntegerDigits'], @@ -391,7 +312,7 @@ extension on NumberFormatOptions { 'signDisplay': signDisplay.name, 'notation': notation.name, 'useGrouping': useGrouping.jsName, - 'numberingSystem': numberingSystem, + 'numberingSystem': numberingSystem?.jsName, 'roundingMode': roundingMode.name, 'trailingZeroDisplay': trailingZeroDisplay.name, 'minimumIntegerDigits': minimumIntegerDigits, @@ -403,4 +324,70 @@ extension on NumberFormatOptions { }, }; } + + NumberFormatOptions copyWith({ + FormatStyle? style, + String? currency, + SignDisplay? signDisplay, + Notation? notation, + Grouping? useGrouping, + NumberingSystem? numberingSystem, + RoundingMode? roundingMode, + TrailingZeroDisplay? trailingZeroDisplay, + int? minimumIntegerDigits, + Digits? digits, + }) => NumberFormatOptions.custom( + style: style ?? this.style, + currency: currency ?? this.currency, + signDisplay: signDisplay ?? this.signDisplay, + notation: notation ?? this.notation, + useGrouping: useGrouping ?? this.useGrouping, + numberingSystem: numberingSystem ?? this.numberingSystem, + roundingMode: roundingMode ?? this.roundingMode, + trailingZeroDisplay: trailingZeroDisplay ?? this.trailingZeroDisplay, + minimumIntegerDigits: minimumIntegerDigits ?? this.minimumIntegerDigits, + digits: digits ?? this.digits, + ); +} + +// Copied from intl4x/lib/src/number_format/number_format_ecma.dart +/// Extension to provide a JavaScript-compatible name for the Unit enum. +extension on Unit { + /// The JavaScript-compatible string representation of the unit. + String get jsName => switch (this) { + Unit.fluidOunce => 'fluid-ounce', + Unit.scandinavianMile => 'mile-scandinavian', + // Fallback to the enum's name for all other units (e.g., 'acre', 'bit', + // 'byte'). + _ => name, + }; +} + +// Copied from intl4x/lib/src/locale/locale.dart +extension NumberingSystemJsName on NumberingSystem { + /// Returns the BCP 47/CLDR short name for the numbering system. + String get jsName => switch (this) { + NumberingSystem.arabic => 'arab', + NumberingSystem.extendedarabicindic => 'arabext', + NumberingSystem.balinese => 'bali', + NumberingSystem.bangla => 'beng', + NumberingSystem.devanagari => 'deva', + NumberingSystem.fullwidth => 'fullwide', + NumberingSystem.gujarati => 'gujr', + NumberingSystem.gurmukhi => 'guru', + NumberingSystem.hanjadecimal => 'hant', + NumberingSystem.khmer => 'khmr', + NumberingSystem.kannada => 'knda', + NumberingSystem.lao => 'laoo', + NumberingSystem.malayalam => 'mlym', + NumberingSystem.mongolian => 'mong', + NumberingSystem.myanmar => 'mymr', + NumberingSystem.odia => 'orya', + NumberingSystem.tamildecimal => 'taml', + NumberingSystem.telugu => 'telu', + NumberingSystem.thai => 'thai', + NumberingSystem.tibetan => 'tibt', + NumberingSystem.latin => 'latn', + NumberingSystem.limbu => 'limb', + }; } diff --git a/executors/dart/lib/plural_rules.dart b/executors/dart/lib/plural_rules.dart index 0c8300f0..82da0ca1 100644 --- a/executors/dart/lib/plural_rules.dart +++ b/executors/dart/lib/plural_rules.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:collection/collection.dart'; -import 'package:intl4x/intl4x.dart'; import 'package:intl4x/plural_rules.dart'; /// Tests Intl Locale for plural rules. @@ -60,15 +59,13 @@ String testPluralRules(String jsonEncoded) { } // PluralRulesOptions setup - var testOptions = PluralRulesOptions(); final typeString = json['type'] as String?; + PluralType? pluralType; if (typeString != null) { - final pluralType = Type.values.firstWhereOrNull( + pluralType = PluralType.values.firstWhereOrNull( (type) => typeString == type.name, ); - if (pluralType != null) { - testOptions = testOptions.copyWith(type: pluralType); - } else { + if (pluralType == null) { returnJson.addAll({ 'error': 'unsupported', 'unsupported': 'unsupported_plural_type', @@ -78,8 +75,9 @@ String testPluralRules(String jsonEncoded) { } } - final intl = Intl(locale: locale); - final pluralRules = intl.plural(testOptions); + final pluralRules = pluralType != null + ? PluralRules(locale: locale, type: pluralType) + : PluralRules(locale: locale); try { final result = pluralRules.select(sample); diff --git a/executors/dart/lib/version.dart b/executors/dart/lib/version.dart index d54983bd..012e4e6f 100644 --- a/executors/dart/lib/version.dart +++ b/executors/dart/lib/version.dart @@ -1,2 +1,2 @@ /// This file is autogenerated by bin/set_version.dart, do not modify manually. -const intl4xVersion = '0.13.0'; +const intl4xVersion = '0.17.0'; diff --git a/executors/dart/out/version.js b/executors/dart/out/version.js index 92fc0e76..9d756391 100644 --- a/executors/dart/out/version.js +++ b/executors/dart/out/version.js @@ -1,2 +1,2 @@ -const dartVersion = "0.13.0"; +const dartVersion = "0.17.0"; module.exports = { dartVersion }; diff --git a/executors/dart/pubspec.lock b/executors/dart/pubspec.lock index 5c9d27b0..539925d1 100644 --- a/executors/dart/pubspec.lock +++ b/executors/dart/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a + sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" url: "https://pub.dev" source: hosted - version: "88.0.0" + version: "92.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" + sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "9.0.0" args: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: code_assets - sha256: "7f42899e7b22f7810ea8c2b281c979add25555fbe391dde539621069e20b90b4" + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" url: "https://pub.dev" source: hosted - version: "0.19.5" + version: "1.0.0" collection: dependency: "direct main" description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" ffi: dependency: transitive description: @@ -133,10 +133,18 @@ packages: dependency: transitive description: name: hooks - sha256: "2bd640e4625fdfe5788ef33d825a8639797d44bce05695ab13543cde0ff9a078" + sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7" url: "https://pub.dev" source: hosted - version: "0.20.0" + version: "1.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -157,18 +165,18 @@ packages: dependency: transitive description: name: icu4x - sha256: "9e89a363c410608de336e1a15c599ecc49245cc6f9d7d719b4b1ced57151c92c" + sha256: "8c2f3ed5f860c68be13076e43685f49330956a8a8c351c9af7920606be411368" url: "https://pub.dev" source: hosted - version: "2.0.0-dev.0" + version: "2.1.0-dev.1" intl4x: dependency: "direct main" description: name: intl4x - sha256: "17ca4511e95d036954897ca192d771598b3744c07bd43de72a35af795f244228" + sha256: "30170321747219904977b4826dbd5156e34f407876f87695d8b8167da5955d20" url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.17.0" io: dependency: transitive description: @@ -177,14 +185,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" json_annotation: dependency: transitive description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" meta: dependency: "direct dev" description: @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: "7e8358a4f6ec69a4f2d366bb971af298aca50d6c2e8a07be7c12d7f6d40460aa" + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" url: "https://pub.dev" source: hosted - version: "0.17.1" + version: "0.17.4" node_preamble: dependency: "direct main" description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" pub_semver: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: record_use - sha256: e2e0d735e843f81a8ab28572fcff9f30aeb0a5870b6450d4bc6778cbc919c8e8 + sha256: d3d59b18ca7aa1ce689bb3b3fd982bbebdfcd9e89f977576486a55e3b0f19a27 url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" shelf: dependency: transitive description: @@ -389,26 +389,34 @@ packages: dependency: "direct dev" description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.28.0" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.8" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4 + url: "https://pub.dev" + source: hosted + version: "0.6.14" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.10.1" typed_data: dependency: transitive description: @@ -429,10 +437,10 @@ packages: dependency: transitive description: name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" web: dependency: transitive description: diff --git a/executors/dart/pubspec.yaml b/executors/dart/pubspec.yaml index 0c1bd1ed..0993b715 100644 --- a/executors/dart/pubspec.yaml +++ b/executors/dart/pubspec.yaml @@ -6,14 +6,14 @@ environment: dependencies: collection: ^1.19.1 - intl4x: 0.13.0 + intl4x: 0.17.0 node_preamble: ^2.0.2 pubspec_lock_parse: ^2.2.0 dev_dependencies: lints: ^6.0.0 - meta: ^1.16.0 - test: ^1.25.2 + meta: ^1.17.0 + test: ^1.28.0 hook: diff --git a/executors/dart/test/misc_test.dart b/executors/dart/test/misc_test.dart index 976f4e17..24f1e4f2 100644 --- a/executors/dart/test/misc_test.dart +++ b/executors/dart/test/misc_test.dart @@ -6,7 +6,6 @@ import 'package:dart_executor/datetime_format.dart'; import 'package:dart_executor/lang_names.dart'; import 'package:dart_executor/numberformat.dart'; import 'package:intl4x/datetime_format.dart'; -import 'package:intl4x/intl4x.dart'; import 'package:meta/meta.dart'; import 'package:test/test.dart'; @@ -102,13 +101,9 @@ void main() { final outputLine = testDateTimeFmt(jsonEncode(input)); final output = jsonDecode(outputLine) as Map; print( - Intl(locale: Locale.parse(input['locale'] as String)) - .dateTimeFormat(DateTimeFormatOptions()) - .ymdt( - dateStyle: DateFormatStyle.short, - timeStyle: TimeFormatStyle.short, - ) - .format(DateTime.parse(input['input_string'] as String)), + DateTimeFormat.yearMonthDayTime( + locale: Locale.parse(input['locale'] as String), + ).format(DateTime.parse(input['input_string'] as String)), ); expect(output['result'], '1/1, 12:00:00 AM GMT'); }, skip: 'Failing for now'); diff --git a/run_config.json b/run_config.json index 840c2569..1877c1e4 100644 --- a/run_config.json +++ b/run_config.json @@ -252,6 +252,7 @@ "exec": "dart_native", "test_type": [ "collation", + "datetime_fmt", "number_fmt", "lang_names", "plural_rules", @@ -267,7 +268,7 @@ "command": "nvm install 24.0.0;nvm use 24.0.0 --silent" }, "run": { - "icu_version": "icu76", + "icu_version": "icu77", "exec": "dart_web", "test_type": [ "collation", @@ -311,6 +312,7 @@ "exec": "dart_web", "test_type": [ "collation", + "datetime_fmt", "number_fmt", "lang_names", "plural_rules", @@ -330,6 +332,7 @@ "exec": "dart_web", "test_type": [ "collation", + "datetime_fmt", "number_fmt", "lang_names", "plural_rules", @@ -349,6 +352,7 @@ "exec": "dart_web", "test_type": [ "collation", + "datetime_fmt", "number_fmt", "lang_names", "plural_rules",