diff --git a/pkgs/intl4x/CHANGELOG.md b/pkgs/intl4x/CHANGELOG.md index 9e5e4f89..e41644ad 100644 --- a/pkgs/intl4x/CHANGELOG.md +++ b/pkgs/intl4x/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.1 + +- Export plural rules. + ## 0.7.0 - Add conformance testing workflow. diff --git a/pkgs/intl4x/lib/intl4x.dart b/pkgs/intl4x/lib/intl4x.dart index 3496255c..aba21c3c 100644 --- a/pkgs/intl4x/lib/intl4x.dart +++ b/pkgs/intl4x/lib/intl4x.dart @@ -24,6 +24,7 @@ import 'src/plural_rules/plural_rules_impl.dart'; import 'src/plural_rules/plural_rules_options.dart'; export 'src/locale/locale.dart'; +export 'src/plural_rules/plural_rules.dart'; typedef Icu4xKey = String; diff --git a/pkgs/intl4x/pubspec.yaml b/pkgs/intl4x/pubspec.yaml index 5a80045a..2ceab4a2 100644 --- a/pkgs/intl4x/pubspec.yaml +++ b/pkgs/intl4x/pubspec.yaml @@ -1,7 +1,7 @@ name: intl4x description: >- A lightweight modular library for internationalization (i18n) functionality. -version: 0.7.0 +version: 0.7.1 repository: https://github.com/dart-lang/i18n/tree/main/pkgs/intl4x platforms: ## TODO: Add native platforms once ICU4X is integrated. web: diff --git a/pkgs/messages/CHANGELOG.md b/pkgs/messages/CHANGELOG.md index 822f95b0..69036625 100644 --- a/pkgs/messages/CHANGELOG.md +++ b/pkgs/messages/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.0 + +- Remove `IntlObject` interface. +- Introduce `PluralRules` to retrieve the correct message for plurals. + ## 0.1.1 - Update README. diff --git a/pkgs/messages/example_json/bin/example.dart b/pkgs/messages/example_json/bin/example.dart index 57218930..f53bce4c 100644 --- a/pkgs/messages/example_json/bin/example.dart +++ b/pkgs/messages/example_json/bin/example.dart @@ -7,13 +7,10 @@ import 'dart:io'; import 'package:example_json/testarbctx2.g.dart'; -import 'package:messages/package_intl_object.dart'; Future main(List arguments) async { - final messages = AboutPageMessages( - (String id) async => File(id).readAsString(), - const OldIntlObject(), - ); + final messages = + AboutPageMessages((String id) async => File(id).readAsString()); // final index = AboutPageMessagesEnum.aboutMessage; await messages.loadLocale('en'); diff --git a/pkgs/messages/example_json/lib/testarb.g.dart b/pkgs/messages/example_json/lib/testarb.g.dart index 03b75163..f35050f6 100644 --- a/pkgs/messages/example_json/lib/testarb.g.dart +++ b/pkgs/messages/example_json/lib/testarb.g.dart @@ -1,12 +1,10 @@ -// Generated by package:messages_builder +// Generated by package:messages_builder. +import 'package:intl4x/intl4x.dart'; import 'package:messages/messages_json.dart'; class HomePageMessages { - HomePageMessages( - this._fileLoader, - this.intlObject, - ); + HomePageMessages(this._fileLoader); final Future Function(String id) _fileLoader; @@ -14,28 +12,26 @@ class HomePageMessages { final Map _messages = {}; - static const carbs = { + static const _dataFiles = { 'de': ('lib/testarb_de.json', 'hbDN1MhX'), 'en': ('lib/testarb.json', 'dr9Md951') }; - IntlObject intlObject; - String get currentLocale => _currentLocale; MessageList get _currentMessages => _messages[currentLocale]!; - static Iterable get knownLocales => carbs.keys; + static Iterable get knownLocales => _dataFiles.keys; Future loadLocale(String locale) async { if (!_messages.containsKey(locale)) { - final info = carbs[locale]; + final info = _dataFiles[locale]; final carb = info?.$1; if (carb == null) { throw ArgumentError('Locale $locale is not in $knownLocales'); } final data = await _fileLoader(carb); - final messageList = MessageListJson.fromString(data, intlObject); + final messageList = MessageListJson.fromString(data, pluralSelector); if (messageList.preamble.hash != info?.$2) { throw ArgumentError(''' Messages file for locale $locale has different hash "${messageList.preamble.hash}" than generated code "${info?.$2}".'''); @@ -46,11 +42,31 @@ class HomePageMessages { } void loadAllLocales() { - for (var locale in knownLocales) { + for (final locale in knownLocales) { loadLocale(locale); } } + Message pluralSelector( + num howMany, { + required Message other, + Message? few, + Message? many, + Map? numberCases, + Map? wordCases, + }) { + Message getCase(int i) => numberCases?[i] ?? wordCases?[i] ?? other; + return switch ( + Intl(locale: Locale.parse(currentLocale)).plural().select(howMany)) { + PluralCategory.zero => getCase(0), + PluralCategory.one => getCase(1), + PluralCategory.two => getCase(2), + PluralCategory.few => few ?? other, + PluralCategory.many => many ?? other, + PluralCategory.other => other, + }; + } + String helloAndWelcome( String firstName, String lastName, diff --git a/pkgs/messages/example_json/lib/testarb.json b/pkgs/messages/example_json/lib/testarb.json index eb1082fd..d46d15af 100644 --- a/pkgs/messages/example_json/lib/testarb.json +++ b/pkgs/messages/example_json/lib/testarb.json @@ -1 +1 @@ -[0,"en","dr9Md951",0,null,["Welcome von !",[8,0],[13,1]],["Welcome von !",[8,0],[13,1]],[6,"test ",[3,0,["test new messages",[5,0]],[2,"No new messages",4,"One new message",5,"Two new Messages"]]],[6,"test ",[4,0,"Two new Messages",{"male":"No new messages","female":"One new message"}]]] \ No newline at end of file +[0,"en","dr9Md951",0,null,["Welcome von !",[8,0],[13,1]],["Welcome von !",[8,0],[13,1]],[6,"test ",[3,0,["test new messages",[5,0]],[0,"No new messages",1,"One new message","w2","Two new Messages"]]],[6,"test ",[4,0,"Two new Messages",{"male":"No new messages","female":"One new message"}]]] \ No newline at end of file diff --git a/pkgs/messages/example_json/lib/testarb_de.json b/pkgs/messages/example_json/lib/testarb_de.json index f77a4e5b..8480d165 100644 --- a/pkgs/messages/example_json/lib/testarb_de.json +++ b/pkgs/messages/example_json/lib/testarb_de.json @@ -1 +1 @@ -[0,"de","hbDN1MhX",0,null,["Willkommen von ",[11,0],[16,1]],["Willkommen von 2",[11,0],[16,1]],[6,"testde ",[3,0,["test new messages",[5,0]],[2,"No new messages",4,"One new message",5,"Two new Messages"]]],"testdse is just a simple message"] \ No newline at end of file +[0,"de","hbDN1MhX",0,null,["Willkommen von ",[11,0],[16,1]],["Willkommen von 2",[11,0],[16,1]],[6,"testde ",[3,0,["test new messages",[5,0]],[0,"No new messages",1,"One new message","w2","Two new Messages"]]],"testdse is just a simple message"] \ No newline at end of file diff --git a/pkgs/messages/example_json/lib/testarbctx2.g.dart b/pkgs/messages/example_json/lib/testarbctx2.g.dart index 0a7c6d80..55aa20f5 100644 --- a/pkgs/messages/example_json/lib/testarbctx2.g.dart +++ b/pkgs/messages/example_json/lib/testarbctx2.g.dart @@ -1,12 +1,10 @@ -// Generated by package:messages_builder +// Generated by package:messages_builder. +import 'package:intl4x/intl4x.dart'; import 'package:messages/messages_json.dart'; class AboutPageMessages { - AboutPageMessages( - this._fileLoader, - this.intlObject, - ); + AboutPageMessages(this._fileLoader); final Future Function(String id) _fileLoader; @@ -14,28 +12,26 @@ class AboutPageMessages { final Map _messages = {}; - static const carbs = { + static const _dataFiles = { 'fr': ('lib/testarbctx2_fr.json', 'EyPjEJJU'), 'en': ('lib/testarbctx2.json', 'QrwRSsOy') }; - IntlObject intlObject; - String get currentLocale => _currentLocale; MessageList get _currentMessages => _messages[currentLocale]!; - static Iterable get knownLocales => carbs.keys; + static Iterable get knownLocales => _dataFiles.keys; Future loadLocale(String locale) async { if (!_messages.containsKey(locale)) { - final info = carbs[locale]; + final info = _dataFiles[locale]; final carb = info?.$1; if (carb == null) { throw ArgumentError('Locale $locale is not in $knownLocales'); } final data = await _fileLoader(carb); - final messageList = MessageListJson.fromString(data, intlObject); + final messageList = MessageListJson.fromString(data, pluralSelector); if (messageList.preamble.hash != info?.$2) { throw ArgumentError(''' Messages file for locale $locale has different hash "${messageList.preamble.hash}" than generated code "${info?.$2}".'''); @@ -46,11 +42,31 @@ class AboutPageMessages { } void loadAllLocales() { - for (var locale in knownLocales) { + for (final locale in knownLocales) { loadLocale(locale); } } + Message pluralSelector( + num howMany, { + required Message other, + Message? few, + Message? many, + Map? numberCases, + Map? wordCases, + }) { + Message getCase(int i) => numberCases?[i] ?? wordCases?[i] ?? other; + return switch ( + Intl(locale: Locale.parse(currentLocale)).plural().select(howMany)) { + PluralCategory.zero => getCase(0), + PluralCategory.one => getCase(1), + PluralCategory.two => getCase(2), + PluralCategory.few => few ?? other, + PluralCategory.many => many ?? other, + PluralCategory.other => other, + }; + } + String aboutMessage(String websitename) => _currentMessages.generateStringAtIndex(0, [websitename]); diff --git a/pkgs/messages/example_json/lib/testarbctx2.json b/pkgs/messages/example_json/lib/testarbctx2.json index f59abb6d..a56c79c5 100644 --- a/pkgs/messages/example_json/lib/testarbctx2.json +++ b/pkgs/messages/example_json/lib/testarbctx2.json @@ -1 +1 @@ -[0,"en","QrwRSsOy",0,null,["About ",[6,0]],["Welcome von <",[8,0],[13,1]],[6,"test ",[3,0,["test new messages",[5,0]],[2,"No new messages",4,"One new message",5,"Two new Messages"]]],[6,"test ",[4,0,"Two new Messages",{"male":"No new messages","female":"One new message"}]],"other"] \ No newline at end of file +[0,"en","QrwRSsOy",0,null,["About ",[6,0]],["Welcome von <",[8,0],[13,1]],[6,"test ",[3,0,["test new messages",[5,0]],[0,"No new messages",1,"One new message","w2","Two new Messages"]]],[6,"test ",[4,0,"Two new Messages",{"male":"No new messages","female":"One new message"}]],"other"] \ No newline at end of file diff --git a/pkgs/messages/example_json/lib/testarbctx2_fr.json b/pkgs/messages/example_json/lib/testarbctx2_fr.json index a1c89f3d..61b6c08f 100644 --- a/pkgs/messages/example_json/lib/testarbctx2_fr.json +++ b/pkgs/messages/example_json/lib/testarbctx2_fr.json @@ -1 +1 @@ -[0,"fr","EyPjEJJU",0,null,["Sur ",[4,0]],["Welcome von <",[8,0],[13,1]],[6,"test ",[3,0,["test new messages",[5,0]],[2,"No new messages",4,"One new message",5,"Two new Messages"]]],[6,"test ",[4,0,"Two new Messages",{"male":"No new messages","female":"One new message"}]],"other"] \ No newline at end of file +[0,"fr","EyPjEJJU",0,null,["Sur ",[4,0]],["Welcome von <",[8,0],[13,1]],[6,"test ",[3,0,["test new messages",[5,0]],[0,"No new messages",1,"One new message","w2","Two new Messages"]]],[6,"test ",[4,0,"Two new Messages",{"male":"No new messages","female":"One new message"}]],"other"] \ No newline at end of file diff --git a/pkgs/messages/example_json/pubspec.yaml b/pkgs/messages/example_json/pubspec.yaml index 348628fd..6807cce4 100644 --- a/pkgs/messages/example_json/pubspec.yaml +++ b/pkgs/messages/example_json/pubspec.yaml @@ -6,6 +6,7 @@ environment: sdk: ^3.0.0 dependencies: + intl4x: ^0.7.0 messages: path: ../ @@ -25,3 +26,4 @@ package_options: generateMethods: true generateFindById: false generateFindBy: integer + pluralSelector: intl4x diff --git a/pkgs/messages/example_json/pubspec_overrides.yaml b/pkgs/messages/example_json/pubspec_overrides.yaml index 4477e4dd..54623dd3 100644 --- a/pkgs/messages/example_json/pubspec_overrides.yaml +++ b/pkgs/messages/example_json/pubspec_overrides.yaml @@ -3,3 +3,5 @@ dependency_overrides: path: ../ messages_serializer: path: ../../messages_serializer + intl4x: + path: ../../intl4x diff --git a/pkgs/messages/lib/messages.dart b/pkgs/messages/lib/messages.dart index 9926f34e..6544241e 100644 --- a/pkgs/messages/lib/messages.dart +++ b/pkgs/messages/lib/messages.dart @@ -3,6 +3,4 @@ // BSD-style license that can be found in the LICENSE file. export 'src/deserializer/deserializer.dart'; -export 'src/intl_object.dart' show IntlObject; -export 'src/intl_style_lookup.dart' show Intl; export 'src/message_format.dart'; diff --git a/pkgs/messages/lib/src/deserializer/deserializer.dart b/pkgs/messages/lib/src/deserializer/deserializer.dart index 7888ddc0..1bc61209 100644 --- a/pkgs/messages/lib/src/deserializer/deserializer.dart +++ b/pkgs/messages/lib/src/deserializer/deserializer.dart @@ -2,9 +2,9 @@ // 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 '../intl_object.dart'; import '../message_format.dart'; +import '../plural_selector.dart'; abstract class Deserializer { - T deserialize(IntlObject intl); + T deserialize(PluralSelector selector); } diff --git a/pkgs/messages/lib/src/deserializer/deserializer_json.dart b/pkgs/messages/lib/src/deserializer/deserializer_json.dart index a6aa5093..98a56edf 100644 --- a/pkgs/messages/lib/src/deserializer/deserializer_json.dart +++ b/pkgs/messages/lib/src/deserializer/deserializer_json.dart @@ -4,9 +4,9 @@ import 'dart:convert'; -import '../intl_object.dart'; import '../message_format.dart'; import '../message_list_json.dart'; +import '../plural_selector.dart'; import 'deserializer.dart'; class JsonDeserializer extends Deserializer { @@ -20,7 +20,7 @@ class JsonDeserializer extends Deserializer { } @override - MessageListJson deserialize(IntlObject intl) { + MessageListJson deserialize(PluralSelector selector) { if (preamble.version != serializationVersion) { throw ArgumentError( '''This message has version ${preamble.version}, while the deserializer has version $serializationVersion'''); @@ -32,7 +32,7 @@ class JsonDeserializer extends Deserializer { return MessageListJson( preamble, _messages, - intl, + selector, mapping?.map((key, value) => MapEntry( int.parse(key, radix: serializationRadix), int.parse(value as String, radix: serializationRadix), @@ -55,8 +55,6 @@ class JsonDeserializer extends Deserializer { return _forPlural(message, start, id); } else if (typeOrId == SelectMessage.type) { return _forSelect(message, start, id); - } else if (typeOrId == GenderMessage.type) { - return _forGender(message, start, id); } else if (typeOrId == CombinedMessage.type) { return _forCombined(message, start, id); } else if (typeOrId is String) { @@ -83,51 +81,29 @@ class JsonDeserializer extends Deserializer { PluralMessage _forPlural(List message, int start, String? id) { final argIndex = message[start] as int; final otherMessage = getMessage(message[start + 1]); - Message? zeroWordMessage; - Message? zeroNumberMessage; - Message? oneWordMessage; - Message? oneNumberMessage; - Message? twoWordMessage; - Message? twoNumberMessage; Message? fewMessage; Message? manyMessage; + final numberCases = {}; + final wordCases = {}; final submessages = message[start + 2] as List; for (var i = 0; i < submessages.length - 1; i += 2) { final msg = getMessage(submessages[i + 1]); - switch (submessages[i]) { - case Plural.zeroWord: - zeroWordMessage = msg; - break; - case Plural.zeroNumber: - zeroNumberMessage = msg; - break; - case Plural.oneWord: - oneWordMessage = msg; - break; - case Plural.oneNumber: - oneNumberMessage = msg; - break; - case Plural.twoWord: - twoWordMessage = msg; - break; - case Plural.twoNumber: - twoNumberMessage = msg; - break; - case Plural.few: - fewMessage = msg; - break; - case Plural.many: - manyMessage = msg; - break; + final messageMarker = submessages[i]; + if (messageMarker case PluralMarker.few) { + fewMessage = msg; + } else if (messageMarker case PluralMarker.many) { + manyMessage = msg; + } else if (messageMarker case final int digit) { + numberCases[digit] = msg; + } else if (messageMarker is String && + messageMarker.startsWith(PluralMarker.wordCase)) { + final digit = int.parse(messageMarker.substring(1)); + wordCases[digit] = msg; } } return PluralMessage( - zeroNumber: zeroNumberMessage, - zeroWord: zeroWordMessage, - oneNumber: oneNumberMessage, - oneWord: oneWordMessage, - twoNumber: twoNumberMessage, - twoWord: twoWordMessage, + numberCases: numberCases, + wordCases: wordCases, few: fewMessage, many: manyMessage, argIndex: argIndex, @@ -153,30 +129,4 @@ class JsonDeserializer extends Deserializer { message.skip(start).map(getMessage).toList(), ); } - - GenderMessage _forGender(List message, int start, String? id) { - final argIndex = message[start] as int; - final otherMessage = getMessage(message[start + 1]); - final submessages = message[start + 2] as List; - Message? femaleMessage; - Message? maleMessage; - for (var i = 0; i < submessages.length - 1; i += 2) { - final msg = getMessage(submessages[i + 1]); - switch (submessages[i]) { - case Gender.female: - femaleMessage = msg; - break; - case Gender.male: - maleMessage = msg; - break; - } - } - return GenderMessage( - female: femaleMessage, - male: maleMessage, - other: otherMessage, - argIndex: argIndex, - id: id, - ); - } } diff --git a/pkgs/messages/lib/src/intl_object.dart b/pkgs/messages/lib/src/intl_object.dart deleted file mode 100644 index 0638121b..00000000 --- a/pkgs/messages/lib/src/intl_object.dart +++ /dev/null @@ -1,29 +0,0 @@ -// 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 'message.dart'; - -abstract class IntlObject { - const IntlObject(); - - Message gender( - GenderEnum gender, - Message? female, - Message? male, - Message other, - ); - - Message plural( - num howMany, { - Message? zero, - Message? one, - Message? two, - Message? few, - Message? many, - required Message other, - String? locale, - }); - - Message select(Object arg, Map cases); -} diff --git a/pkgs/messages/lib/src/intl_style_lookup.dart b/pkgs/messages/lib/src/intl_style_lookup.dart deleted file mode 100644 index 5a5d4a66..00000000 --- a/pkgs/messages/lib/src/intl_style_lookup.dart +++ /dev/null @@ -1,23 +0,0 @@ -// 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. - -class Intl { - static MessageLookup? l; - - static String message( - String s, { - required List args, - required String id, - }) { - if (l != null) return l!.getById(id, args); - for (var i = 0; i < args.length; i++) { - s = s.replaceAll('#$i', args[i]); - } - return s; - } -} - -abstract class MessageLookup { - String getById(String id, List args); -} diff --git a/pkgs/messages/lib/src/message.dart b/pkgs/messages/lib/src/message.dart index 63da1fe2..54adec19 100644 --- a/pkgs/messages/lib/src/message.dart +++ b/pkgs/messages/lib/src/message.dart @@ -2,7 +2,7 @@ // 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 'intl_object.dart'; +import 'plural_selector.dart'; sealed class Message { final String? id; @@ -11,7 +11,7 @@ sealed class Message { String generateString( List allArgs, { - required IntlObject intl, + required PluralSelector pluralSelector, String? locale, String Function(String p1)? cleaner, }); @@ -25,14 +25,14 @@ final class CombinedMessage extends Message { @override String generateString( List allArgs, { - required IntlObject intl, + required PluralSelector pluralSelector, String Function(String p1)? cleaner, String? locale, }) => messages .map((e) => e.generateString( allArgs, - intl: intl, + pluralSelector: pluralSelector, cleaner: cleaner, locale: locale, )) @@ -57,7 +57,7 @@ final class StringMessage extends Message { @override String generateString( List allArgs, { - required IntlObject intl, + required PluralSelector pluralSelector, String Function(String p1)? cleaner, String? locale, }) { @@ -81,73 +81,20 @@ final class StringMessage extends Message { } } -final class GenderMessage extends Message { - final Message? male; - final Message? female; - final Message other; - final int argIndex; - - GenderMessage({ - this.male, - this.female, - required this.other, - required this.argIndex, - String? id, - }) : super(id); - - static const int type = 5; - - @override - String generateString( - List allArgs, { - required IntlObject intl, - String Function(String p1)? cleaner, - String? locale, - }) { - return intl - .gender( - allArgs[argIndex] as GenderEnum, - female, - male, - other, - ) - .generateString( - allArgs, - intl: intl, - cleaner: cleaner, - locale: locale, - ); - } -} - -enum GenderEnum { - female, - male, - other; -} - final class PluralMessage extends Message { - final Message? zeroWord; - final Message? zeroNumber; - final Message? oneWord; - final Message? oneNumber; - final Message? twoWord; - final Message? twoNumber; + final Map numberCases; + final Map wordCases; final Message? few; final Message? many; final Message other; final int argIndex; PluralMessage({ + this.numberCases = const {}, + this.wordCases = const {}, this.few, this.many, required this.other, - this.zeroWord, - this.zeroNumber, - this.oneWord, - this.oneNumber, - this.twoWord, - this.twoNumber, required this.argIndex, String? id, }) : super(id); @@ -157,22 +104,19 @@ final class PluralMessage extends Message { @override String generateString( List allArgs, { - required IntlObject intl, + required PluralSelector pluralSelector, String Function(String p1)? cleaner, String? locale, }) { - return intl - .plural( - allArgs[argIndex] as num, - few: few, - many: many, - zero: zeroNumber ?? zeroWord, - one: oneNumber ?? oneWord, - two: twoNumber ?? twoWord, - other: other, - locale: locale, - ) - .generateString(allArgs, intl: intl, cleaner: cleaner, locale: locale); + return pluralSelector( + allArgs[argIndex] as num, + numberCases: numberCases, + wordCases: wordCases, + few: few, + many: many, + other: other, + ).generateString(allArgs, + pluralSelector: pluralSelector, cleaner: cleaner, locale: locale); } } @@ -192,14 +136,13 @@ final class SelectMessage extends Message { @override String generateString( List allArgs, { - required IntlObject intl, + required PluralSelector pluralSelector, String Function(String p1)? cleaner, String? locale, }) { - final selected = - intl.select(allArgs[argIndex] as Object, {...cases, 'other': other}); + final selected = cases[allArgs[argIndex]!] ?? other; return selected.generateString( - intl: intl, + pluralSelector: pluralSelector, allArgs, cleaner: cleaner, locale: locale, diff --git a/pkgs/messages/lib/src/message_format.dart b/pkgs/messages/lib/src/message_format.dart index c8e881ea..909d7a42 100644 --- a/pkgs/messages/lib/src/message_format.dart +++ b/pkgs/messages/lib/src/message_format.dart @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. // ignore_for_file: non_constant_identifier_names -import 'intl_object.dart'; +import 'plural_selector.dart'; export 'message.dart'; @@ -30,36 +30,15 @@ abstract class Preamble { abstract class MessageList { Preamble get preamble; - IntlObject get intl; + PluralSelector get pluralSelector; String generateStringAtIndex(int index, List args); String generateStringAtId(String id, List args); } -enum PluralEnum { - zeroWord, - zeroNumber, - oneWord, - oneNumber, - twoWord, - twoNumber, - few, - many, -} - -class Plural { - static const int zeroWord = 1; - static const int zeroNumber = 2; - static const int oneWord = 3; - static const int oneNumber = 4; - static const int twoWord = 5; - static const int twoNumber = 6; - static const int few = 7; - static const int many = 8; -} - -class Gender { - static const int female = 1; - static const int male = 2; +sealed class PluralMarker { + static const String wordCase = 'w'; + static const String few = 'f'; + static const String many = 'm'; } diff --git a/pkgs/messages/lib/src/message_list_json.dart b/pkgs/messages/lib/src/message_list_json.dart index 773e4a3a..bb0eb1c4 100644 --- a/pkgs/messages/lib/src/message_list_json.dart +++ b/pkgs/messages/lib/src/message_list_json.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'deserializer/deserializer_json.dart'; -import 'intl_object.dart'; import 'message_format.dart'; +import 'plural_selector.dart'; class JsonPreamble extends Preamble { final List _data; @@ -40,12 +40,12 @@ class JsonPreamble extends Preamble { class MessageListJson extends MessageList { final List messages; - final IntlObject _intl; + final PluralSelector _selector; final JsonPreamble _preamble; final Map? messageIndices; @override - IntlObject get intl => _intl; + PluralSelector get pluralSelector => _selector; @override Preamble get preamble => _preamble; @@ -53,22 +53,22 @@ class MessageListJson extends MessageList { MessageListJson( this._preamble, this.messages, - this._intl, + this._selector, this.messageIndices, ); - factory MessageListJson.fromString(String string, IntlObject intl) => + factory MessageListJson.fromString(String string, PluralSelector intl) => JsonDeserializer(string).deserialize(intl); @override String generateStringAtId(String id, List args) => messages .where((element) => element.id == id) .first - .generateString(args, intl: _intl); + .generateString(args, pluralSelector: _selector); @override String generateStringAtIndex(int index, List args) => - messages[getIndex(index)].generateString(args, intl: _intl); + messages[getIndex(index)].generateString(args, pluralSelector: _selector); int getIndex(int index) => messageIndices?[index] ?? index; } diff --git a/pkgs/messages/lib/src/old_intl_object.dart b/pkgs/messages/lib/src/old_intl_object.dart deleted file mode 100644 index a2173768..00000000 --- a/pkgs/messages/lib/src/old_intl_object.dart +++ /dev/null @@ -1,56 +0,0 @@ -// 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:intl/intl.dart' as old_intl; - -import 'intl_object.dart'; -import 'message.dart'; - -class OldIntlObject extends IntlObject { - const OldIntlObject(); - @override - Message gender( - GenderEnum gender, - Message? female, - Message? male, - Message other, - ) => - old_intl.Intl.genderLogic( - gender.name, - female: female, - male: male, - other: other, - ); - - @override - Message plural( - num howMany, { - Message? zero, - Message? one, - Message? two, - Message? few, - Message? many, - required Message other, - String? locale, - }) { - return old_intl.Intl.pluralLogic( - howMany, - few: few, - many: many, - zero: zero, - one: one, - two: two, - other: other, - locale: locale, - ); - } - - @override - Message select(Object arg, Map cases) { - return old_intl.Intl.selectLogic( - arg, - cases, - ); - } -} diff --git a/pkgs/messages/lib/package_intl_object.dart b/pkgs/messages/lib/src/plural_selector.dart similarity index 50% rename from pkgs/messages/lib/package_intl_object.dart rename to pkgs/messages/lib/src/plural_selector.dart index 42ae38ee..5d6899bc 100644 --- a/pkgs/messages/lib/package_intl_object.dart +++ b/pkgs/messages/lib/src/plural_selector.dart @@ -2,5 +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. -//TODO: Move OldIntlObject to package:intl -export 'src/old_intl_object.dart' show OldIntlObject; +import 'message.dart'; + +typedef PluralSelector = Message Function( + num howMany, { + Map? numberCases, + Map? wordCases, + Message? few, + Message? many, + required Message other, +}); diff --git a/pkgs/messages/pubspec.yaml b/pkgs/messages/pubspec.yaml index 7ccaf862..82e3b90e 100644 --- a/pkgs/messages/pubspec.yaml +++ b/pkgs/messages/pubspec.yaml @@ -1,6 +1,6 @@ name: messages description: A lightweight modular library for localization (l10n) functionality. -version: 0.1.1 +version: 0.2.0 repository: https://github.com/dart-lang/i18n/tree/main/pkgs/messages environment: diff --git a/pkgs/messages/test/messagelist_json_test.dart b/pkgs/messages/test/messagelist_json_test.dart index 2c905875..ab291453 100644 --- a/pkgs/messages/test/messagelist_json_test.dart +++ b/pkgs/messages/test/messagelist_json_test.dart @@ -2,10 +2,31 @@ // 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:intl/intl.dart' as old_intl; import 'package:messages/messages_json.dart'; -import 'package:messages/package_intl_object.dart'; import 'package:test/test.dart'; +Message intlPluralSelector( + num howMany, { + Map? numberCases, + Map? wordCases, + Message? few, + Message? many, + required Message other, + String? locale, +}) { + return old_intl.Intl.pluralLogic( + howMany, + few: few, + many: many, + zero: numberCases?[0] ?? wordCases?[0], + one: numberCases?[1] ?? wordCases?[1], + two: numberCases?[2] ?? wordCases?[2], + other: other, + locale: locale, + ); +} + void main() { test('JSON MessageList', () { final MessageList messageList = MessageListJson( @@ -27,17 +48,19 @@ void main() { 'case2': StringMessage('Case 2'), 'case3': PluralMessage( other: StringMessage('other nested'), - twoNumber: StringMessage(': ', argPositions: [ - (stringIndex: 0, argIndex: 0), - (stringIndex: 2, argIndex: 1), - ]), + numberCases: { + 2: StringMessage(': ', argPositions: [ + (stringIndex: 0, argIndex: 0), + (stringIndex: 2, argIndex: 1), + ]) + }, argIndex: 1, ), }, 0, ) ], - const OldIntlObject(), + intlPluralSelector, null, ); diff --git a/pkgs/messages_builder/CHANGELOG.md b/pkgs/messages_builder/CHANGELOG.md index 9ee6d8df..e17f7548 100644 --- a/pkgs/messages_builder/CHANGELOG.md +++ b/pkgs/messages_builder/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.0 + +- Remove `IntlObject` interface. +- Introduce `PluralRules` to retrieve the correct message for plurals. + ## 0.1.1 - Add `header` to `GenerationOptions`. diff --git a/pkgs/messages_builder/lib/code_generation/constructor_generation.dart b/pkgs/messages_builder/lib/code_generation/constructor_generation.dart index 468c2d22..936ef667 100644 --- a/pkgs/messages_builder/lib/code_generation/constructor_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/constructor_generation.dart @@ -21,9 +21,10 @@ class ConstructorGeneration extends Generation { ..name = '_fileLoader' ..toThis = true, ), - Parameter((pb) => pb - ..name = 'intlObject' - ..toThis = true), + if (options.pluralSelector == PluralSelectorType.custom) + Parameter((pb) => pb + ..name = 'pluralSelector' + ..toThis = true), ])); return [nativeConstructor]; } diff --git a/pkgs/messages_builder/lib/code_generation/field_generation.dart b/pkgs/messages_builder/lib/code_generation/field_generation.dart index 7c667fb1..a6d28f67 100644 --- a/pkgs/messages_builder/lib/code_generation/field_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/field_generation.dart @@ -42,29 +42,30 @@ class FieldGeneration extends Generation { ..name = '_messages' ..assignment = const Code('{}'), ); - final carbs = Field( + final dataFiles = Field( (fb) { final paths = localeToResourceInfo.entries .map((e) => "'${e.key}' : ('${e.value.path}', '${e.value.hasch}')") .join(','); fb - ..name = 'carbs' + ..name = '_dataFiles' ..modifier = FieldModifier.constant ..static = true ..assignment = Code('{$paths}'); }, ); - final intlObject = Field( + final pluralSelector = Field( (fb) => fb - ..name = 'intlObject' - ..type = const Reference('IntlObject'), + ..name = 'pluralSelector' + ..type = const Reference( + '''Message Function(num howMany, {Map? numberCases, Map? wordCases, Message? few, Message? many, Message other, String? locale})'''), ); final fields = [ loadingStrategy, currentLocale, messages, - carbs, - intlObject, + dataFiles, + if (options.pluralSelector == PluralSelectorType.custom) pluralSelector, ]; return fields; } diff --git a/pkgs/messages_builder/lib/code_generation/import_generation.dart b/pkgs/messages_builder/lib/code_generation/import_generation.dart index 66a4b7fb..79ee4e52 100644 --- a/pkgs/messages_builder/lib/code_generation/import_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/import_generation.dart @@ -19,6 +19,13 @@ class ImportGeneration extends Generation { Directive.import('package:messages/messages_json.dart') ], }; - return serializationImports; + final pluralImports = switch (options.pluralSelector) { + PluralSelectorType.intl => [Directive.import('package:intl/intl.dart')], + PluralSelectorType.intl4x => [ + Directive.import('package:intl4x/intl4x.dart') + ], + PluralSelectorType.custom => [], + }; + return [...serializationImports, ...pluralImports]; } } diff --git a/pkgs/messages_builder/lib/code_generation/method_generation.dart b/pkgs/messages_builder/lib/code_generation/method_generation.dart index 39f684db..c2438f71 100644 --- a/pkgs/messages_builder/lib/code_generation/method_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/method_generation.dart @@ -63,7 +63,7 @@ class MethodGeneration extends Generation { final loading = switch (options.deserialization) { DeserializationType.web => ''' final data = await _fileLoader(carb); - final messageList = MessageListJson.fromString(data, intlObject);''', + final messageList = MessageListJson.fromString(data, pluralSelector);''', }; mb ..name = 'loadLocale' @@ -75,7 +75,7 @@ class MethodGeneration extends Generation { ..modifier = MethodModifier.async ..body = Code(''' if (!_messages.containsKey(locale)) { - final info = carbs[locale]; + final info = _dataFiles[locale]; final carb = info?.\$1; if (carb == null) { throw ArgumentError('Locale \$locale is not in \$knownLocales'); @@ -98,7 +98,7 @@ class MethodGeneration extends Generation { ..name = 'loadAllLocales' ..returns = const Reference('void') ..body = const Code(''' - for (var locale in knownLocales) { + for (final locale in knownLocales) { loadLocale(locale); } '''); @@ -110,7 +110,7 @@ class MethodGeneration extends Generation { ..type = MethodType.getter ..lambda = true ..static = true - ..body = const Code('carbs.keys') + ..body = const Code('_dataFiles.keys') ..returns = const Reference('Iterable'), ); final getCurrentMessages = Method( @@ -166,6 +166,60 @@ class MethodGeneration extends Generation { const Code('_currentMessages.generateStringAtIndex(val.index, args)') ..lambda = true ..returns = const Reference('String')); + // Message Function(num, + // {Message? few, + // String? locale, + // Message? many, + // Map? numberCases, + // required Message other, + // Map? wordCases}) intl; + Method pluralSelector() => Method( + (mb) => mb + ..name = 'pluralSelector' + ..returns = const Reference('Message') + ..requiredParameters.addAll([ + Parameter( + (pb) => pb + ..name = 'howMany' + ..type = const Reference('num') + ..named = false, + ), + ]) + ..optionalParameters.addAll([ + Parameter( + (pb) => pb + ..name = 'other' + ..type = const Reference('Message') + ..required = true + ..named = true, + ), + Parameter( + (pb) => pb + ..name = 'few' + ..type = const Reference('Message?') + ..named = true, + ), + Parameter( + (pb) => pb + ..name = 'many' + ..type = const Reference('Message?') + ..named = true, + ), + Parameter( + (pb) => pb + ..name = 'numberCases' + ..type = const Reference('Map?') + ..named = true, + ), + Parameter( + (pb) => pb + ..name = 'wordCases' + ..type = const Reference('Map?') + ..named = true, + ), + ]) + ..body = pluralSelectorBody(), + ); return [ getCurrentLocale, @@ -175,7 +229,37 @@ class MethodGeneration extends Generation { getKnownLocales, loadLocale, loadAllLocales, + if (options.pluralSelector != PluralSelectorType.custom) pluralSelector(), ...messageCalls, ]; } + + Code pluralSelectorBody() { + return switch (options.pluralSelector) { + PluralSelectorType.intl => const Code(''' +return Intl.pluralLogic( + howMany, + few: few, + many: many, + zero: numberCases?[0] ?? wordCases?[0], + one: numberCases?[1] ?? wordCases?[1], + two: numberCases?[2] ?? wordCases?[2], + other: other, + locale: currentLocale, + ); + '''), + PluralSelectorType.intl4x => const Code(''' +Message getCase(int i) => numberCases?[i] ?? wordCases?[i] ?? other; + return switch (Intl(locale: Locale.parse(currentLocale)).plural().select(howMany)) { + PluralCategory.zero => getCase(0), + PluralCategory.one => getCase(1), + PluralCategory.two => getCase(2), + PluralCategory.few => few ?? other, + PluralCategory.many => many ?? other, + PluralCategory.other => other, + }; + '''), + PluralSelectorType.custom => throw ArgumentError(), + }; + } } diff --git a/pkgs/messages_builder/lib/generation_options.dart b/pkgs/messages_builder/lib/generation_options.dart index e9e2616b..6573b3fe 100644 --- a/pkgs/messages_builder/lib/generation_options.dart +++ b/pkgs/messages_builder/lib/generation_options.dart @@ -36,8 +36,12 @@ class GenerationOptions { /// dart native code. final DeserializationType deserialization; + /// The header to add to all generated files, for example for licensing. final String header; + /// The origin of the algorithm for determining which plural case to use. + final PluralSelectorType pluralSelector; + GenerationOptions({ required this.serialization, required this.deserialization, @@ -45,6 +49,7 @@ class GenerationOptions { required this.findById, required this.indexType, required this.header, + required this.pluralSelector, }); static Future fromPubspec(BuildStep buildStep) async { @@ -58,16 +63,31 @@ class GenerationOptions { deserialization: DeserializationType.web, messageCalls: (messagesOptions?['generateMethods'] as bool?) ?? true, findById: (messagesOptions?['generateFindById'] as bool?) ?? false, - indexType: IndexType.values - .where((type) => - type.name == messagesOptions?['generateFindBy'] as String?) - .firstOrNull ?? - IndexType.integer, + indexType: _indexType(messagesOptions), header: messagesOptions?['header'] as String? ?? 'Generated by package:messages_builder.', + pluralSelector: _pluralSelector(messagesOptions), ); return generationOptions; } + + static IndexType _indexType(YamlMap? messagesOptions) { + final generateFindString = messagesOptions?['generateFindBy'] as String?; + return generateFindString != null + ? IndexType.values + .where((type) => type.name == generateFindString) + .first + : IndexType.integer; + } + + static PluralSelectorType _pluralSelector(YamlMap? messagesOptions) { + final pluralSelectorString = messagesOptions?['pluralSelector'] as String?; + return pluralSelectorString != null + ? PluralSelectorType.values + .where((type) => type.name == pluralSelectorString) + .first + : PluralSelectorType.intl; + } } enum SerializationType { @@ -78,8 +98,27 @@ enum DeserializationType { web; } +/// How the indexing of the messages should be implemented. enum IndexType { + /// No indexing. none, + + /// Indexing via a collection of `static int`s. integer, + + /// Indexing via an enum. enumerate; } + +/// The origin of the algorithm for determining which plural case to use. +enum PluralSelectorType { + /// From `package:intl`. + intl, + + /// From `package:intl4x`. + + intl4x, + + /// A user-specified algorithm. + custom; +} diff --git a/pkgs/messages_builder/lib/message_parser/plural_parser.dart b/pkgs/messages_builder/lib/message_parser/plural_parser.dart index 92fd3407..77ec6543 100644 --- a/pkgs/messages_builder/lib/message_parser/plural_parser.dart +++ b/pkgs/messages_builder/lib/message_parser/plural_parser.dart @@ -56,12 +56,8 @@ class PluralParser { final numberCases = getNumberCases(parts, arguments); final wordCases = getWordCases(parts, arguments); return PluralMessage( - zeroNumber: numberCases[0], - zeroWord: wordCases[0], - oneNumber: numberCases[1], - oneWord: wordCases[1], - twoNumber: numberCases[2], - twoWord: wordCases[2], + numberCases: numberCases, + wordCases: wordCases, few: getNamed(parts, 'few', arguments), many: getNamed(parts, 'many', arguments), other: getOther(parts, arguments)!, diff --git a/pkgs/messages_builder/pubspec.yaml b/pkgs/messages_builder/pubspec.yaml index 962f923e..1cf7124b 100644 --- a/pkgs/messages_builder/pubspec.yaml +++ b/pkgs/messages_builder/pubspec.yaml @@ -1,6 +1,6 @@ name: messages_builder description: Build the messages for consumption by package:messages -version: 0.1.1 +version: 0.2.0 repository: https://github.com/dart-lang/i18n/pkgs/messages_builder environment: diff --git a/pkgs/messages_builder/test/web_deserializer_native_test.dart b/pkgs/messages_builder/test/web_deserializer_native_test.dart index 07616839..e93fa824 100644 --- a/pkgs/messages_builder/test/web_deserializer_native_test.dart +++ b/pkgs/messages_builder/test/web_deserializer_native_test.dart @@ -5,8 +5,8 @@ import 'dart:convert'; import 'package:build/src/asset/id.dart'; +import 'package:intl/intl.dart' as old_intl; import 'package:messages/messages_json.dart'; -import 'package:messages/package_intl_object.dart'; import 'package:messages_builder/arb_parser.dart'; import 'package:messages_builder/message_with_metadata.dart'; import 'package:messages_serializer/messages_serializer.dart'; @@ -14,6 +14,27 @@ import 'package:test/test.dart'; import 'testarb.arb.dart'; +Message intlPluralSelector( + num howMany, { + Map? numberCases, + Map? wordCases, + Message? few, + Message? many, + required Message other, + String? locale, +}) { + return old_intl.Intl.pluralLogic( + howMany, + few: few, + many: many, + zero: numberCases?[0] ?? wordCases?[0], + one: numberCases?[1] ?? wordCases?[1], + two: numberCases?[2] ?? wordCases?[2], + other: other, + locale: locale, + ); +} + void main() { final uniqueKey = AssetId('package', 'path'); test('generateMessageFile from Object json', () { @@ -24,7 +45,7 @@ void main() { .serialize('', '', messageList.map((e) => e.message).toList()) .data; final messages = - JsonDeserializer(buffer).deserialize(const OldIntlObject()).messages; + JsonDeserializer(buffer).deserialize(intlPluralSelector).messages; expect((messages[0] as StringMessage).value, message.value); }); @@ -38,7 +59,7 @@ void main() { .serialize('', '', parsed.messages.map((e) => e.message).toList()) .data; final messages = - JsonDeserializer(buffer).deserialize(const OldIntlObject()).messages; + JsonDeserializer(buffer).deserialize(intlPluralSelector).messages; expect((messages[0] as StringMessage).value, 'Hello World'); }); test('generateMessageFile from simple arb JSON with placeholder', () { @@ -51,7 +72,7 @@ void main() { .serialize('', '', parsed.messages.map((e) => e.message).toList()) .data; final messages = - JsonDeserializer(buffer).deserialize(const OldIntlObject()).messages; + JsonDeserializer(buffer).deserialize(intlPluralSelector).messages; expect((messages[0] as StringMessage).value, 'Hello '); expect( (messages[0] as StringMessage).argPositions, @@ -68,7 +89,7 @@ void main() { .serialize('', '', parsed.messages.map((e) => e.message).toList()) .data; final messages = - JsonDeserializer(buffer).deserialize(const OldIntlObject()).messages; + JsonDeserializer(buffer).deserialize(intlPluralSelector).messages; expect((messages[0] as StringMessage).value, ''); expect( (messages[0] as StringMessage).argPositions, @@ -87,11 +108,11 @@ void main() { .serialize('', '', parsed.messages.map((e) => e.message).toList()) .data; final messages = - JsonDeserializer(buffer).deserialize(const OldIntlObject()).messages; + JsonDeserializer(buffer).deserialize(intlPluralSelector).messages; expect( messages[2].generateString( ['female', 'b'], - intl: const OldIntlObject(), + pluralSelector: intlPluralSelector, ), 'test One new message'); }); diff --git a/pkgs/messages_serializer/CHANGELOG.md b/pkgs/messages_serializer/CHANGELOG.md index a0712a79..47f0d5db 100644 --- a/pkgs/messages_serializer/CHANGELOG.md +++ b/pkgs/messages_serializer/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +- Switch plural encoding to map. + ## 0.1.0 - Initial version. diff --git a/pkgs/messages_serializer/lib/src/serializer_json.dart b/pkgs/messages_serializer/lib/src/serializer_json.dart index 2b468af5..a1bb1a5a 100644 --- a/pkgs/messages_serializer/lib/src/serializer_json.dart +++ b/pkgs/messages_serializer/lib/src/serializer_json.dart @@ -59,8 +59,6 @@ class JsonSerializer extends Serializer { messageIndex = encodePlural(message, isVisible); } else if (message is CombinedMessage) { messageIndex = encodeCombined(message, isVisible); - } else if (message is GenderMessage) { - messageIndex = encodeGender(message, isVisible); } else { throw ArgumentError('Unknown message type'); } @@ -136,36 +134,21 @@ class JsonSerializer extends Serializer { m.add(encodeMessage(message.other)); final caseIndices = []; if (message.few != null) { - caseIndices.add(Plural.few); + caseIndices.add(PluralMarker.few); caseIndices.add(encodeMessage(message.few!)); } if (message.many != null) { - caseIndices.add(Plural.many); + caseIndices.add(PluralMarker.many); caseIndices.add(encodeMessage(message.many!)); } - if (message.zeroNumber != null) { - caseIndices.add(Plural.zeroNumber); - caseIndices.add(encodeMessage(message.zeroNumber!)); + for (final MapEntry(key: caseIndex, value: messageIndex) + in message.numberCases.entries) { + caseIndices.add(caseIndex); + caseIndices.add(encodeMessage(messageIndex)); } - if (message.zeroWord != null) { - caseIndices.add(Plural.zeroWord); - caseIndices.add(encodeMessage(message.zeroWord!)); - } - if (message.oneNumber != null) { - caseIndices.add(Plural.oneNumber); - caseIndices.add(encodeMessage(message.oneNumber!)); - } - if (message.oneWord != null) { - caseIndices.add(Plural.oneWord); - caseIndices.add(encodeMessage(message.oneWord!)); - } - if (message.twoNumber != null) { - caseIndices.add(Plural.twoNumber); - caseIndices.add(encodeMessage(message.twoNumber!)); - } - if (message.twoWord != null) { - caseIndices.add(Plural.twoWord); - caseIndices.add(encodeMessage(message.twoWord!)); + for (final entry in message.wordCases.entries) { + caseIndices.add(PluralMarker.wordCase + entry.key.toString()); + caseIndices.add(encodeMessage(entry.value)); } m.add(caseIndices); return m; @@ -187,34 +170,6 @@ class JsonSerializer extends Serializer { return m; } - /// Encodes a gender message as follows: - /// - /// * int | the GenderMessage type - /// * if we write IDs: String | the message id - /// * int | the argument index on which the gender switches - /// * int | the index of the other case message, which must be present - /// * List\ | the cases, which are added in pairs of two: - /// * int | the case index as encoded by the constants in `Gender` - /// * int | the message index of the case - List encodeGender(GenderMessage message, bool isVisible) { - final m = []; - m.add(GenderMessage.type); - addId(message, m, isVisible); - m.add(message.argIndex); - m.add(encodeMessage(message.other)); - final caseIndices = []; - if (message.female != null) { - caseIndices.add(Gender.female); - caseIndices.add(encodeMessage(message.female!)); - } - if (message.male != null) { - caseIndices.add(Gender.male); - caseIndices.add(encodeMessage(message.male!)); - } - m.add(caseIndices); - return m; - } - /// Add a non-null ID iff `writeIds` is enabled void addId(Message message, List m, bool isVisible) { if (writeIds && message.id != null && isVisible) m.add(message.id!); diff --git a/pkgs/messages_serializer/pubspec.yaml b/pkgs/messages_serializer/pubspec.yaml index 0fbb33d4..0653fd15 100644 --- a/pkgs/messages_serializer/pubspec.yaml +++ b/pkgs/messages_serializer/pubspec.yaml @@ -1,6 +1,6 @@ name: messages_serializer description: Serialization of messages for package:messages. -version: 0.1.0 +version: 0.2.0 repository: https://github.com/dart-lang/i18n/tree/main/pkgs/messages_serializer environment: @@ -12,4 +12,5 @@ dependencies: dev_dependencies: dart_flutter_team_lints: ^2.1.1 + intl: ^0.18.1 test: ^1.21.0 diff --git a/pkgs/messages_serializer/test/messages_serializer_test.dart b/pkgs/messages_serializer/test/messages_serializer_test.dart index a53e43bd..b9ebee90 100644 --- a/pkgs/messages_serializer/test/messages_serializer_test.dart +++ b/pkgs/messages_serializer/test/messages_serializer_test.dart @@ -4,11 +4,32 @@ import 'dart:math'; +import 'package:intl/intl.dart' as old_intl; import 'package:messages/messages_json.dart'; -import 'package:messages/package_intl_object.dart'; import 'package:messages_serializer/messages_serializer.dart'; import 'package:test/test.dart'; +Message intlPluralSelector( + num howMany, { + Map? numberCases, + Map? wordCases, + Message? few, + Message? many, + required Message other, + String? locale, +}) { + return old_intl.Intl.pluralLogic( + howMany, + few: few, + many: many, + zero: numberCases?[0] ?? wordCases?[0], + one: numberCases?[1] ?? wordCases?[1], + two: numberCases?[2] ?? wordCases?[2], + other: other, + locale: locale, + ); +} + StringMessage stringMessage = StringMessage('Hello World', id: 'hello_world'); CombinedMessage combinedMessage = CombinedMessage('combined', [ @@ -20,8 +41,8 @@ PluralMessage pluralMessage = PluralMessage( id: 'pluralMes', few: StringMessage('few case'), many: StringMessage('many case'), - oneNumber: StringMessage('oneNumber case'), - twoWord: StringMessage('twoWord case'), + numberCases: {1: StringMessage('oneNumber case')}, + wordCases: {2: StringMessage('twoWord case')}, other: StringMessage('Other case'), argIndex: 0, ); @@ -36,14 +57,6 @@ SelectMessage selectMessage = SelectMessage( 'selectMes', ); -GenderMessage genderMessage = GenderMessage( - female: StringMessage('Female'), - male: StringMessage('Male'), - argIndex: 0, - other: StringMessage('other'), - id: 'genderMes', -); - void main() { test('Serialize with IDs', () { final messages = [ @@ -51,12 +64,11 @@ void main() { combinedMessage, pluralMessage, selectMessage, - genderMessage ]; final serialized = JsonSerializer(true).serialize('hash', 'locale', messages); final deserialize = - JsonDeserializer(serialized.data).deserialize(const OldIntlObject()); + JsonDeserializer(serialized.data).deserialize(intlPluralSelector); expect( deserialize.messages.map((e) => e.id), orderedEquals(messages.map((e) => e.id)), @@ -69,15 +81,14 @@ void main() { combinedMessage, pluralMessage, selectMessage, - genderMessage ]; final serialized = - JsonSerializer(true).serialize('hash', 'locale', messages, [1, 4]); + JsonSerializer(true).serialize('hash', 'locale', messages, [1, 3]); final deserialize = - JsonDeserializer(serialized.data).deserialize(const OldIntlObject()); + JsonDeserializer(serialized.data).deserialize(intlPluralSelector); expect( deserialize.messages.map((e) => e.id), - orderedEquals([messages[1], messages[4]].map((e) => e.id)), + orderedEquals([messages[1], messages[3]].map((e) => e.id)), ); }); @@ -89,7 +100,6 @@ void main() { combinedMessage, pluralMessage, selectMessage, - genderMessage ] ]; final params = [ @@ -116,7 +126,7 @@ void serializeThenDeserialize( final serialized = serializer.serialize(hash, locale, messages); final deserializer = deserializerBuilder(serialized.data); - final deserialized = deserializer.deserialize(const OldIntlObject()); + final deserialized = deserializer.deserialize(intlPluralSelector); expect(deserialized.preamble.hash, hash); expect(deserialized.preamble.locale, locale); @@ -139,12 +149,19 @@ void compareMessage(Message? original, Message? deserialized) { if (original is StringMessage) { expect((deserialized as StringMessage).value, original.value); } else if (original is PluralMessage) { - compareMessage((deserialized as PluralMessage).zeroWord, original.zeroWord); - compareMessage(deserialized.zeroNumber, original.zeroNumber); - compareMessage(deserialized.oneWord, original.oneWord); - compareMessage(deserialized.oneNumber, original.oneNumber); - compareMessage(deserialized.twoWord, original.twoWord); - compareMessage(deserialized.twoNumber, original.twoNumber); + final deserialized2 = deserialized as PluralMessage; + for (final key in original.wordCases.keys) { + compareMessage( + deserialized2.wordCases[key], + original.wordCases[key], + ); + } + for (final key in original.numberCases.keys) { + compareMessage( + deserialized.numberCases[key], + original.numberCases[key], + ); + } compareMessage(deserialized.few, original.few); compareMessage(deserialized.many, original.many); compareMessage(deserialized.other, original.other); diff --git a/pkgs/messages_shrinker/CHANGELOG.md b/pkgs/messages_shrinker/CHANGELOG.md index a0712a79..19e1dfec 100644 --- a/pkgs/messages_shrinker/CHANGELOG.md +++ b/pkgs/messages_shrinker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +- Adapt to `PluralRules` interface to retrieve the correct message for plurals. + ## 0.1.0 - Initial version. diff --git a/pkgs/messages_shrinker/lib/messages_shrinker.dart b/pkgs/messages_shrinker/lib/messages_shrinker.dart index 5703fd7d..2099b3e6 100644 --- a/pkgs/messages_shrinker/lib/messages_shrinker.dart +++ b/pkgs/messages_shrinker/lib/messages_shrinker.dart @@ -6,7 +6,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:messages/messages_json.dart'; -import 'package:messages/package_intl_object.dart'; import 'package:messages_serializer/messages_serializer.dart'; class MessageShrinker { @@ -33,7 +32,12 @@ class MessageShrinker { /// message indices in [messagesToKeep]. String shrinkJson(String buffer, List messagesToKeep) { final sizeBefore = buffer.length; - final json = JsonDeserializer(buffer).deserialize(const OldIntlObject()); + final json = JsonDeserializer(buffer).deserialize( + (howMany, {few, locale, many, numberCases, required other, wordCases}) { + throw StateError('As the deserialized MessageList is not used, but ' + 'just immediately reserialized, this selector will not be called.'); + }, + ); final data = JsonSerializer(json.preamble.hasIds) .serialize( json.preamble.hash, diff --git a/pkgs/messages_shrinker/pubspec.yaml b/pkgs/messages_shrinker/pubspec.yaml index cfb73ada..088558e5 100644 --- a/pkgs/messages_shrinker/pubspec.yaml +++ b/pkgs/messages_shrinker/pubspec.yaml @@ -1,6 +1,6 @@ name: messages_shrinker description: A starting point for Dart libraries or applications. -version: 0.1.0 +version: 0.2.0 repository: https://github.com/dart-lang/i18n/pkgs/messages_shrinker environment: @@ -17,4 +17,5 @@ dependencies: dev_dependencies: dart_flutter_team_lints: ^2.0.0 + intl: ^0.18.1 test: ^1.21.0 diff --git a/pkgs/messages_shrinker/test/message_shrinker_test.dart b/pkgs/messages_shrinker/test/message_shrinker_test.dart index 276ddb55..5c8f76f9 100644 --- a/pkgs/messages_shrinker/test/message_shrinker_test.dart +++ b/pkgs/messages_shrinker/test/message_shrinker_test.dart @@ -9,15 +9,36 @@ import 'dart:convert'; import 'dart:io'; import 'package:build/build.dart'; +import 'package:intl/intl.dart' as old_intl; import 'package:messages/messages_json.dart'; -import 'package:messages/package_intl_object.dart'; import 'package:messages_builder/arb_parser.dart'; import 'package:messages_serializer/messages_serializer.dart'; import 'package:messages_shrinker/messages_shrinker.dart'; import 'package:test/test.dart'; +Message intlPluralSelector( + num howMany, { + Map? numberCases, + Map? wordCases, + Message? few, + Message? many, + required Message other, + String? locale, +}) { + return old_intl.Intl.pluralLogic( + howMany, + few: few, + many: many, + zero: numberCases?[0] ?? wordCases?[0], + one: numberCases?[1] ?? wordCases?[1], + two: numberCases?[2] ?? wordCases?[2], + other: other, + locale: locale, + ); +} + void main() { - final intl = const OldIntlObject(); + final intl = intlPluralSelector; late String dataFileContents; late String dataFile;