From 63d1b5558aa0ae00c0703bc7aac9f3cbd90f6363 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 5 Nov 2024 15:57:24 +0100 Subject: [PATCH] Refactorings! --- pkgs/messages/example/bin/example.dart | 2 +- .../example/lib/AboutPage_en_empty.g.dart | 1 + .../example/lib/AboutPage_fr_empty.g.dart | 1 + ...sages.g.dart => AboutPage_messages.g.dart} | 118 +++--------- .../example/lib/HomePage_de_empty.g.dart | 1 + .../example/lib/HomePage_en_empty.g.dart | 1 + .../example/lib/HomePage_messages.g.dart | 109 +++++++++++ pkgs/messages/example/pubspec.yaml | 2 +- .../my_application/assets/l10n/messages.arb | 4 +- .../my_application/lib/src/empty_de.dart | 1 - .../my_application/lib/src/empty_en.dart | 1 - .../my_application/lib/src/messages.g.dart | 58 +++--- .../lib/src/my_app_de_DE_empty.dart | 1 + .../lib/src/my_app_de_DE_empty.g.dart | 1 + .../lib/src/my_app_en_US_empty.dart | 1 + .../lib/src/my_app_en_US_empty.g.dart | 1 + .../my_application/test/widget_test.dart | 30 --- .../my_shopping_cart/pubspec.yaml | 2 +- pkgs/messages_builder/lib/arb_parser.dart | 8 +- pkgs/messages_builder/lib/builder.dart | 182 ++++++++---------- .../lib/code_generation/class_generation.dart | 2 +- .../{code.dart => code_generation.dart} | 43 ++--- .../lib/code_generation/field_generation.dart | 16 +- .../code_generation/import_generation.dart | 11 +- .../code_generation/library_generation.dart | 31 +-- .../message_file_metadata.dart | 11 ++ .../code_generation/method_generation.dart | 22 ++- .../lib/generation_options.dart | 13 +- .../lib/message_data_builder.dart | 2 +- .../lib/message_parser/message_parser.dart | 4 +- .../lib/message_with_metadata.dart | 20 +- .../test/web_deserializer_native_test.dart | 4 +- 32 files changed, 356 insertions(+), 348 deletions(-) create mode 100644 pkgs/messages/example/lib/AboutPage_en_empty.g.dart create mode 100644 pkgs/messages/example/lib/AboutPage_fr_empty.g.dart rename pkgs/messages/example/lib/{messages.g.dart => AboutPage_messages.g.dart} (56%) create mode 100644 pkgs/messages/example/lib/HomePage_de_empty.g.dart create mode 100644 pkgs/messages/example/lib/HomePage_en_empty.g.dart create mode 100644 pkgs/messages/example/lib/HomePage_messages.g.dart delete mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/empty_de.dart delete mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/empty_en.dart create mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.dart create mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.g.dart create mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.dart create mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart delete mode 100644 pkgs/messages/examples_flutter/my_application/test/widget_test.dart rename pkgs/messages_builder/lib/code_generation/{code.dart => code_generation.dart} (79%) create mode 100644 pkgs/messages_builder/lib/code_generation/message_file_metadata.dart diff --git a/pkgs/messages/example/bin/example.dart b/pkgs/messages/example/bin/example.dart index 793f5d9a..0a1b0940 100644 --- a/pkgs/messages/example/bin/example.dart +++ b/pkgs/messages/example/bin/example.dart @@ -6,7 +6,7 @@ import 'dart:io'; -import 'package:example/messages.g.dart'; +import 'package:example/AboutPage_messages.g.dart'; Future main(List arguments) async { final messages = AboutPageMessages( diff --git a/pkgs/messages/example/lib/AboutPage_en_empty.g.dart b/pkgs/messages/example/lib/AboutPage_en_empty.g.dart new file mode 100644 index 00000000..665ce8f8 --- /dev/null +++ b/pkgs/messages/example/lib/AboutPage_en_empty.g.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale en, generated by `dart run messages`. diff --git a/pkgs/messages/example/lib/AboutPage_fr_empty.g.dart b/pkgs/messages/example/lib/AboutPage_fr_empty.g.dart new file mode 100644 index 00000000..2fae6552 --- /dev/null +++ b/pkgs/messages/example/lib/AboutPage_fr_empty.g.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale fr, generated by `dart run messages`. diff --git a/pkgs/messages/example/lib/messages.g.dart b/pkgs/messages/example/lib/AboutPage_messages.g.dart similarity index 56% rename from pkgs/messages/example/lib/messages.g.dart rename to pkgs/messages/example/lib/AboutPage_messages.g.dart index fc227262..5b4ecb05 100644 --- a/pkgs/messages/example/lib/messages.g.dart +++ b/pkgs/messages/example/lib/AboutPage_messages.g.dart @@ -5,26 +5,8 @@ import 'package:intl/intl.dart'; import 'package:messages/messages_json.dart'; -Message _pluralSelector( - num howMany, - String locale, { - required Message other, - Message? few, - Message? many, - Map? numberCases, - Map? wordCases, -}) { - 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: locale, - ); -} +import 'AboutPage_en_empty.g.dart' deferred as AboutPage_en_empty; +import 'AboutPage_fr_empty.g.dart' deferred as AboutPage_fr_empty; class AboutPageMessages { AboutPageMessages(this._assetLoader); @@ -60,6 +42,12 @@ class AboutPageMessages { if (dataFile == null) { throw ArgumentError('Locale $locale is not in $knownLocales'); } + if (locale == 'en') { + await AboutPage_en_empty.loadLibrary(); + } else if (locale == 'fr') { + await AboutPage_fr_empty.loadLibrary(); + } + final data = await _assetLoader(dataFile); final messageList = MessageListJson.fromString(data, _pluralSelector); if (messageList.preamble.hash != info?.$2) { @@ -98,75 +86,23 @@ class AboutPageMessages { String get otherMsg => _currentMessages.generateStringAtIndex(4, []); } -class HomePageMessages { - HomePageMessages(this._assetLoader); - - final Future Function(String id) _assetLoader; - - String _currentLocale = 'en'; - - final Map _messages = {}; - - static const _dataFiles = { - 'de': ('packages/example/assets/testarb_de.arb.json', 'hbDN1MhX'), - 'en': ('packages/example/assets/testarb.arb.json', 'dr9Md951') - }; - - String get currentLocale => _currentLocale; - - MessageList get _currentMessages => _messages[currentLocale]!; - - String getById( - String id, [ - List args = const [], - ]) { - return _currentMessages.generateStringAtId(id, args); - } - - static Iterable get knownLocales => _dataFiles.keys; - - Future loadLocale(String locale) async { - if (!_messages.containsKey(locale)) { - final info = _dataFiles[locale]; - final dataFile = info?.$1; - if (dataFile == null) { - throw ArgumentError('Locale $locale is not in $knownLocales'); - } - final data = await _assetLoader(dataFile); - 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}".'''); - } - _messages[locale] = messageList; - } - _currentLocale = locale; - } - - Future loadAllLocales() async { - for (final locale in knownLocales) { - await loadLocale(locale); - } - } - - String helloAndWelcome( - String firstName, - String lastName, - ) => - _currentMessages.generateStringAtIndex(0, [firstName, lastName]); - - String helloAndWelcome2( - String firstName, - String lastName, - ) => - _currentMessages.generateStringAtIndex(1, [firstName, lastName]); - - String newMessages(int newMessages) => - _currentMessages.generateStringAtIndex(2, [newMessages]); - - String newMessages2( - String gender, - int newVar, - ) => - _currentMessages.generateStringAtIndex(3, [gender, newVar]); +Message _pluralSelector( + num howMany, + String locale, { + required Message other, + Message? few, + Message? many, + Map? numberCases, + Map? wordCases, +}) { + 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: locale, + ); } diff --git a/pkgs/messages/example/lib/HomePage_de_empty.g.dart b/pkgs/messages/example/lib/HomePage_de_empty.g.dart new file mode 100644 index 00000000..c794efb1 --- /dev/null +++ b/pkgs/messages/example/lib/HomePage_de_empty.g.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale de, generated by `dart run messages`. diff --git a/pkgs/messages/example/lib/HomePage_en_empty.g.dart b/pkgs/messages/example/lib/HomePage_en_empty.g.dart new file mode 100644 index 00000000..665ce8f8 --- /dev/null +++ b/pkgs/messages/example/lib/HomePage_en_empty.g.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale en, generated by `dart run messages`. diff --git a/pkgs/messages/example/lib/HomePage_messages.g.dart b/pkgs/messages/example/lib/HomePage_messages.g.dart new file mode 100644 index 00000000..c358be33 --- /dev/null +++ b/pkgs/messages/example/lib/HomePage_messages.g.dart @@ -0,0 +1,109 @@ +// Generated by package:messages_builder. + +// ignore_for_file: non_constant_identifier_names + +import 'package:intl/intl.dart'; +import 'package:messages/messages_json.dart'; + +import 'HomePage_de_empty.g.dart' deferred as HomePage_de_empty; +import 'HomePage_en_empty.g.dart' deferred as HomePage_en_empty; + +class HomePageMessages { + HomePageMessages(this._assetLoader); + + final Future Function(String id) _assetLoader; + + String _currentLocale = 'en'; + + final Map _messages = {}; + + static const _dataFiles = { + 'de': ('packages/example/assets/testarb_de.arb.json', 'hbDN1MhX'), + 'en': ('packages/example/assets/testarb.arb.json', 'dr9Md951') + }; + + String get currentLocale => _currentLocale; + + MessageList get _currentMessages => _messages[currentLocale]!; + + String getById( + String id, [ + List args = const [], + ]) { + return _currentMessages.generateStringAtId(id, args); + } + + static Iterable get knownLocales => _dataFiles.keys; + + Future loadLocale(String locale) async { + if (!_messages.containsKey(locale)) { + final info = _dataFiles[locale]; + final dataFile = info?.$1; + if (dataFile == null) { + throw ArgumentError('Locale $locale is not in $knownLocales'); + } + if (locale == 'de') { + await HomePage_de_empty.loadLibrary(); + } else if (locale == 'en') { + await HomePage_en_empty.loadLibrary(); + } + + final data = await _assetLoader(dataFile); + 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}".'''); + } + _messages[locale] = messageList; + } + _currentLocale = locale; + } + + Future loadAllLocales() async { + for (final locale in knownLocales) { + await loadLocale(locale); + } + } + + String helloAndWelcome( + String firstName, + String lastName, + ) => + _currentMessages.generateStringAtIndex(0, [firstName, lastName]); + + String helloAndWelcome2( + String firstName, + String lastName, + ) => + _currentMessages.generateStringAtIndex(1, [firstName, lastName]); + + String newMessages(int newMessages) => + _currentMessages.generateStringAtIndex(2, [newMessages]); + + String newMessages2( + String gender, + int newVar, + ) => + _currentMessages.generateStringAtIndex(3, [gender, newVar]); +} + +Message _pluralSelector( + num howMany, + String locale, { + required Message other, + Message? few, + Message? many, + Map? numberCases, + Map? wordCases, +}) { + 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: locale, + ); +} diff --git a/pkgs/messages/example/pubspec.yaml b/pkgs/messages/example/pubspec.yaml index 6887fd91..064111dd 100644 --- a/pkgs/messages/example/pubspec.yaml +++ b/pkgs/messages/example/pubspec.yaml @@ -26,4 +26,4 @@ package_options: plural_selector: intl arb_input_folder: assets/l10n/ message_output_folder: assets/ - generated_code_file: lib/messages.g.dart + generated_code_files: lib/ diff --git a/pkgs/messages/examples_flutter/my_application/assets/l10n/messages.arb b/pkgs/messages/examples_flutter/my_application/assets/l10n/messages.arb index 3ad19b4c..a008ad19 100644 --- a/pkgs/messages/examples_flutter/my_application/assets/l10n/messages.arb +++ b/pkgs/messages/examples_flutter/my_application/assets/l10n/messages.arb @@ -7,8 +7,8 @@ "placeholders": { "arg": { "type":"String", - "example":"1" + "example":"winter" } } } -} \ No newline at end of file +} diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/empty_de.dart b/pkgs/messages/examples_flutter/my_application/lib/src/empty_de.dart deleted file mode 100644 index 8b137891..00000000 --- a/pkgs/messages/examples_flutter/my_application/lib/src/empty_de.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/empty_en.dart b/pkgs/messages/examples_flutter/my_application/lib/src/empty_en.dart deleted file mode 100644 index 8b137891..00000000 --- a/pkgs/messages/examples_flutter/my_application/lib/src/empty_en.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/messages.g.dart b/pkgs/messages/examples_flutter/my_application/lib/src/messages.g.dart index dfbbd3b8..b9c89cc5 100644 --- a/pkgs/messages/examples_flutter/my_application/lib/src/messages.g.dart +++ b/pkgs/messages/examples_flutter/my_application/lib/src/messages.g.dart @@ -5,29 +5,8 @@ import 'package:intl/intl.dart'; import 'package:messages/messages_json.dart'; -import 'empty_en.dart' deferred as empty_en; -import 'empty_de.dart' deferred as empty_de; - -Message _pluralSelector( - num howMany, - String locale, { - required Message other, - Message? few, - Message? many, - Map? numberCases, - Map? wordCases, -}) { - 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: locale, - ); -} +import 'my_app_de_DE_empty.g.dart' deferred as my_app_de_DE_empty; +import 'my_app_en_US_empty.g.dart' deferred as my_app_en_US_empty; class MyAppMessages { MyAppMessages(this._assetLoader); @@ -39,6 +18,10 @@ class MyAppMessages { final Map _messages = {}; static const _dataFiles = { + 'de_DE': ( + 'packages/my_application/assets/messages_de.arb.json', + 'xXViKglj' + ), 'en_US': ('packages/my_application/assets/messages.arb.json', 'IT21w/eV') }; @@ -55,10 +38,10 @@ class MyAppMessages { if (dataFile == null) { throw ArgumentError('Locale $locale is not in $knownLocales'); } - if (locale.startsWith('en')) { - empty_en.loadLibrary(); - } else if (locale.startsWith('de')) { - empty_de.loadLibrary(); + if (locale == 'de_DE') { + await my_app_de_DE_empty.loadLibrary(); + } else if (locale == 'en_US') { + await my_app_en_US_empty.loadLibrary(); } final data = await _assetLoader(dataFile); @@ -81,3 +64,24 @@ class MyAppMessages { String current_sale_name(String arg) => _currentMessages.generateStringAtIndex(0, [arg]); } + +Message _pluralSelector( + num howMany, + String locale, { + required Message other, + Message? few, + Message? many, + Map? numberCases, + Map? wordCases, +}) { + 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: locale, + ); +} diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.dart b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.dart new file mode 100644 index 00000000..63eba547 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale de_DE, generated by `dart run messages`. diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.g.dart b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.g.dart new file mode 100644 index 00000000..63eba547 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.g.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale de_DE, generated by `dart run messages`. diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.dart b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.dart new file mode 100644 index 00000000..a06585ad --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale en_US, generated by `dart run messages`. diff --git a/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart new file mode 100644 index 00000000..a06585ad --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart @@ -0,0 +1 @@ +/// This is a helper file for deferred loading of the messages for locale en_US, generated by `dart run messages`. diff --git a/pkgs/messages/examples_flutter/my_application/test/widget_test.dart b/pkgs/messages/examples_flutter/my_application/test/widget_test.dart deleted file mode 100644 index 8a58b5d6..00000000 --- a/pkgs/messages/examples_flutter/my_application/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:my_application/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml b/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml index 09a0a53f..2f771d04 100644 --- a/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml +++ b/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml @@ -26,7 +26,7 @@ package_options: messages_builder: arb_input_folder: assets/l10n/ message_output_folder: assets/ - generated_code_file: lib/src/messages.g.dart + generated_code_files: lib/src/ flutter: assets: diff --git a/pkgs/messages_builder/lib/arb_parser.dart b/pkgs/messages_builder/lib/arb_parser.dart index 0d8aba1e..87303105 100644 --- a/pkgs/messages_builder/lib/arb_parser.dart +++ b/pkgs/messages_builder/lib/arb_parser.dart @@ -13,21 +13,19 @@ class ArbParser { final bool addName; ArbParser([this.addName = false]); - MessagesWithMetadata parseMessageFile(Map arb) { + MessageFile parseMessageFile(Map arb) { final locale = arb['@@locale'] as String?; final context = arb['@@context'] as String?; - final referencePath = arb['@@x-reference'] as String?; final messagesWithKeys = arb.keys .where((key) => !key.startsWith('@')) .map((key) => (key, parseMessage(arb, key, '${context}_$locale'))) .toList(); messagesWithKeys.sort((a, b) => a.$1.compareTo(b.$1)); final messages = messagesWithKeys.map((e) => e.$2).toList(); - return MessagesWithMetadata( + return MessageFile( messages, locale, context, - referencePath, getHash(arb), arb.keys.any((key) => key.startsWith('@') && !key.startsWith('@@')), ); @@ -38,7 +36,7 @@ class ArbParser { return base64Encode(digest.bytes).substring(0, 8); } - MessageWithMetadata parseMessage( + ParameterizedMessage parseMessage( Map arb, String messageKey, String debugString, diff --git a/pkgs/messages_builder/lib/builder.dart b/pkgs/messages_builder/lib/builder.dart index d6c26374..270425ec 100644 --- a/pkgs/messages_builder/lib/builder.dart +++ b/pkgs/messages_builder/lib/builder.dart @@ -6,13 +6,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:code_builder/code_builder.dart'; import 'package:collection/collection.dart'; import 'package:path/path.dart' as path; import 'arb_parser.dart'; -import 'code_generation/code.dart'; +import 'code_generation/code_generation.dart'; import 'code_generation/library_generation.dart'; +import 'code_generation/message_file_metadata.dart'; import 'generation_options.dart'; import 'message_with_metadata.dart'; @@ -26,80 +26,73 @@ class MessageCallingCodeGenerator { }); Future build() async { - final allMessageFiles = await getParsedMessageFiles(); - final libraries = []; - for (final input in allMessageFiles) { - final parentFile = getParentFile(allMessageFiles, input); - final scrubbedMessageFile = scrub( - input.message, - parentFile.message.messages.map((e) => e.name).toList(), - ); - if (parentFile.path == input.path) { - final library = await writeDartLibrary( - allMessageFiles, - scrubbedMessageFile, - ); - libraries.add(library); - } - } - if (libraries.isNotEmpty) { + final messageFiles = await parseMessageFiles(); + + final families = messageFiles.groupListsBy( + (messageFile) => getParentFile(messageFiles, messageFile)); + + var counter = 0; + + for (final MapEntry(key: parent, value: children) in families.entries) { + final context = parent.file.context; + + final childrensMetadata = collectMetadata(children); + + printIncludeFilesNotification( + context, childrensMetadata.map((e) => e.path)); + + final dummyFilePaths = Map.fromEntries(childrensMetadata + .map((e) => e.locale) + .map((e) => MapEntry(e, [context, e, 'empty'].join('_')))); + + final library = LibraryGeneration( + options: options, + context: context, + initialLocale: parent.file.locale!, + messages: parent.file.messages, + messageFilesMetadata: childrensMetadata, + emptyFiles: dummyFilePaths) + .generate(); + final code = CodeGenerator( options: options, - libraries: libraries, + library: library, + emptyFilePaths: dummyFilePaths.values, ).generate(); - await options.generatedCodeFile.create(recursive: true); - await options.generatedCodeFile.writeAsString(code); - } - } + final parentPath = Directory(options.generatedCodeFiles.path); - /// Only keep the messages which are in the parent file, as only those will - /// get a generated method to embed them in code. - MessagesWithMetadata scrub( - MessagesWithMetadata inputMessageFile, - List messageNames, - ) { - final messages = inputMessageFile.messages - .where((message) => messageNames.contains(message.name)) - .toList() - ..sort((a, b) => a.name.compareTo(b.name)); + final mainFile = File(path.join( + parentPath.path, '${context ?? 'm${counter++}'}_messages.g.dart')); + await mainFile.create(recursive: true); + await mainFile.writeAsString(code); - return inputMessageFile.copyWith(messages: messages); + for (final MapEntry(key: locale, value: emptyFilePath) + in dummyFilePaths.entries) { + final file = File(path.join(parentPath.path, '$emptyFilePath.g.dart')); + await file.create(); + await file.writeAsString(''' +/// This is a helper file for deferred loading of the messages for locale $locale, generated by `dart run messages`. +'''); + } + } } - /// Generates the Dart library which extracts the messages from their file - /// format and makes the available to the user in a way specified through the - /// `GenerationOptions`. - Future writeDartLibrary( - List assetList, - MessagesWithMetadata messageList, - ) async { - final resourcesInContext = assetList - .where((resource) => resource.message.context == messageList.context); - - final localeToResource = resourcesInContext - .map((resource) => ( - locale: resource.message.locale ?? 'en_US', - id: 'packages/${options.packageName}/${resource.path}', - hasch: resource.message.hash, - )) - .sortedBy((resource) => resource.locale); - printIncludeFilesNotification(messageList.context, localeToResource); - return LibraryGeneration( - options, - messageList.context, - messageList.locale!, - messageList.messages, - localeToResource, - ).generate(); - } + List collectMetadata( + List messageFiles) => + messageFiles + .map((messageFile) => MessageFileMetadata( + locale: messageFile.file.locale ?? 'en_US', + path: 'packages/${options.packageName}/${messageFile.path}', + hash: messageFile.file.hash, + )) + .sortedBy((resource) => resource.locale); - Future> getParsedMessageFiles() async => + Future> parseMessageFiles() async => Future.wait(mapping.entries - .map((p) async => ParsedMessageFile( + .map((p) async => LocatedMessageFile( path: path.relative(p.value, from: Directory.current.path), - message: - await parseMessageFile(await getArbfile(p.key), options), + file: await parseMessageFile(await getArbfile(p.key), options), )) .toList()); @@ -107,57 +100,38 @@ class MessageCallingCodeGenerator { await File(path).readAsString(); /// Either get the referenced parent file, or try to infer which it might be. - static ParsedMessageFile getParentFile( - List arbFiles, - ParsedMessageFile currentFile, + static LocatedMessageFile getParentFile( + List messageFiles, + LocatedMessageFile currentFile, ) { - /// If the reference file is explicitly named, return that. - if (currentFile.message.referencePath != null) { - final reference = arbFiles - .where((element) => element.path == currentFile.message.referencePath) - .firstOrNull; - if (reference != null) { - return reference; - } - } - - /// If the current file is a reference for others, return the current file. - final references = arbFiles.where( - (resource) => resource.message.referencePath == currentFile.path); - if (references.contains(currentFile)) { - return currentFile; - } - /// Try to infer by looking at which files contain metadata, which is a sign /// they might be the references for others in the same context. - final contextLeads = - arbFiles.groupListsBy((resource) => resource.message.context); - final contextWithMetadata = contextLeads[currentFile.message.context]! - .firstWhereOrNull((element) => element.message.hasMetadata); - if (contextWithMetadata != null) { - return contextWithMetadata; + final filesInContext = messageFiles.where( + (messageFile) => messageFile.file.context == currentFile.file.context); + final potentialParent = + filesInContext.firstWhereOrNull((element) => element.file.hasMetadata); + if (potentialParent == null && filesInContext.length > 1) { + throw ArgumentError(''' +The files $filesInContext have no metadata, so it is not clear which one is the main source of truth.'''); } - - return currentFile; + return potentialParent ?? currentFile; } /// Display a notification to the user to include the newly generated files /// in their assets. void printIncludeFilesNotification( String? context, - Iterable<({String hasch, String id, String locale})> localeToResource, + Iterable fileList, ) { - var contextMessage = 'The'; - if (context != null) { - contextMessage = 'For the messages in $context, the'; - } - final fileList = localeToResource.map((e) => '\t${e.id}').join('\n'); + final contextMessage = + context != null ? 'For the messages in $context, the' : 'The'; + final fileListJoined = fileList.map((e) => '\t$e').join('\n'); print( - '''$contextMessage following files need to be declared in your assets:\n$fileList'''); + '''$contextMessage following files need to be declared in your assets:\n$fileListJoined'''); } } -Future parseMessageFile( +Future parseMessageFile( String arbFile, GenerationOptions options, ) async { @@ -166,9 +140,9 @@ Future parseMessageFile( return ArbParser(options.findById).parseMessageFile(arb); } -class ParsedMessageFile { +class LocatedMessageFile { final String path; - final MessagesWithMetadata message; + final MessageFile file; - ParsedMessageFile({required this.path, required this.message}); + LocatedMessageFile({required this.path, required this.file}); } diff --git a/pkgs/messages_builder/lib/code_generation/class_generation.dart b/pkgs/messages_builder/lib/code_generation/class_generation.dart index 5468e9e6..2fbcd0fa 100644 --- a/pkgs/messages_builder/lib/code_generation/class_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/class_generation.dart @@ -10,7 +10,7 @@ import 'generation.dart'; class ClassGeneration { final GenerationOptions options; - final List messages; + final List messages; final String? context; final List constructors; diff --git a/pkgs/messages_builder/lib/code_generation/code.dart b/pkgs/messages_builder/lib/code_generation/code_generation.dart similarity index 79% rename from pkgs/messages_builder/lib/code_generation/code.dart rename to pkgs/messages_builder/lib/code_generation/code_generation.dart index f8d80025..a3705069 100644 --- a/pkgs/messages_builder/lib/code_generation/code.dart +++ b/pkgs/messages_builder/lib/code_generation/code_generation.dart @@ -10,33 +10,28 @@ import 'import_generation.dart'; class CodeGenerator { final GenerationOptions options; - final List libraries; + final Library library; + final Iterable emptyFilePaths; - CodeGenerator({required this.options, required this.libraries}); + CodeGenerator({ + required this.options, + required this.library, + required this.emptyFilePaths, + }); String generate() { - final imports = ImportGeneration(options).generate(); - final lib = libraries.fold( - Library( - (p0) => p0 - ..comments.add(options.header) - ..directives.addAll(imports) - ..body.addAll([ - if (options.pluralSelector != PluralSelectorType.custom) - pluralSelector(), - ]), - ), (value, element) { - return Library( - (p0) => p0 - ..ignoreForFile.add('non_constant_identifier_names') - ..comments.addAll({...value.comments, ...element.comments}) - ..directives.addAll({...value.directives, ...element.directives}) - ..body.addAll([ - ...value.body, - ...element.body, - ]), - ); - }); + final imports = ImportGeneration(options, emptyFilePaths).generate(); + final lib = Library( + (p0) => p0 + ..ignoreForFile.add('non_constant_identifier_names') + ..comments.add(options.header) + ..directives.addAll(imports) + ..body.addAll([ + ...library.body, + if (options.pluralSelector != PluralSelectorType.custom) + pluralSelector(), + ]), + ); final emitter = DartEmitter(orderDirectives: true); final source = '${lib.accept(emitter)}'; final code = DartFormatter().format(source); diff --git a/pkgs/messages_builder/lib/code_generation/field_generation.dart b/pkgs/messages_builder/lib/code_generation/field_generation.dart index edc96468..9ee10b43 100644 --- a/pkgs/messages_builder/lib/code_generation/field_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/field_generation.dart @@ -5,17 +5,17 @@ import 'package:code_builder/code_builder.dart'; import '../generation_options.dart'; +import 'message_file_metadata.dart'; class FieldGeneration { final GenerationOptions options; - final Iterable<({String hasch, String id, String locale})> - localeToResourceInfo; - final String locale; + final Iterable messageFilesMetadata; + final String initialLocale; FieldGeneration( this.options, - this.localeToResourceInfo, - this.locale, + this.messageFilesMetadata, + this.initialLocale, ); List generate() { @@ -32,7 +32,7 @@ class FieldGeneration { (fb) => fb ..type = const Reference('String') ..name = '_currentLocale' - ..assignment = Code("'$locale'"), + ..assignment = Code("'$initialLocale'"), ); final messages = Field( (fb) => fb @@ -43,8 +43,8 @@ class FieldGeneration { ); final dataFiles = Field( (fb) { - final paths = localeToResourceInfo - .map((e) => "'${e.locale}' : ('${e.id}', '${e.hasch}')") + final paths = messageFilesMetadata + .map((e) => "'${e.locale}' : ('${e.path}', '${e.hash}')") .join(','); fb ..name = '_dataFiles' diff --git a/pkgs/messages_builder/lib/code_generation/import_generation.dart b/pkgs/messages_builder/lib/code_generation/import_generation.dart index d927f6dc..56f2bf9a 100644 --- a/pkgs/messages_builder/lib/code_generation/import_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/import_generation.dart @@ -8,10 +8,9 @@ import '../generation_options.dart'; class ImportGeneration { final GenerationOptions options; - final List locales; - final String name; + final Iterable emptyFiles; - ImportGeneration(this.options, this.locales, this.name); + ImportGeneration(this.options, this.emptyFiles); List generate() { final serializationImports = switch (options.deserialization) { @@ -27,8 +26,10 @@ class ImportGeneration { PluralSelectorType.custom => [], }; - final deferredImports = locales.map((e) => Directive.importDeferredAs( - '${name}_${e}_empty.dart', '${name}_${e}_empty')); + final deferredImports = emptyFiles.map((emptyFilePath) { + return Directive.importDeferredAs('$emptyFilePath.g.dart', emptyFilePath); + }); + return [ ...serializationImports, ...pluralImports, diff --git a/pkgs/messages_builder/lib/code_generation/library_generation.dart b/pkgs/messages_builder/lib/code_generation/library_generation.dart index 10725044..de0231ab 100644 --- a/pkgs/messages_builder/lib/code_generation/library_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/library_generation.dart @@ -9,37 +9,40 @@ import '../message_with_metadata.dart'; import 'class_generation.dart'; import 'constructor_generation.dart'; import 'field_generation.dart'; +import 'message_file_metadata.dart'; import 'method_generation.dart'; class LibraryGeneration { final GenerationOptions options; final String? context; - final String locale; - final List messages; - final Iterable<({String hasch, String id, String locale})> - localeToResourceInfo; - - LibraryGeneration( - this.options, - this.context, - this.locale, - this.messages, - this.localeToResourceInfo, - ); + final String initialLocale; + final List messages; + final Iterable messageFilesMetadata; + final Map emptyFiles; + + LibraryGeneration({ + required this.options, + required this.context, + required this.initialLocale, + required this.messages, + required this.messageFilesMetadata, + required this.emptyFiles, + }); Library generate() { final constructors = ConstructorGeneration(options).generate(); final fields = FieldGeneration( options, - localeToResourceInfo, - locale, + messageFilesMetadata, + initialLocale, ).generate(); final methods = MethodGeneration( options, context, messages, + emptyFiles, ).generate(); final classes = ClassGeneration( diff --git a/pkgs/messages_builder/lib/code_generation/message_file_metadata.dart b/pkgs/messages_builder/lib/code_generation/message_file_metadata.dart new file mode 100644 index 00000000..5b00b9c2 --- /dev/null +++ b/pkgs/messages_builder/lib/code_generation/message_file_metadata.dart @@ -0,0 +1,11 @@ +class MessageFileMetadata { + final String hash; + final String path; + final String locale; + + MessageFileMetadata({ + required this.hash, + required this.path, + required this.locale, + }); +} diff --git a/pkgs/messages_builder/lib/code_generation/method_generation.dart b/pkgs/messages_builder/lib/code_generation/method_generation.dart index e00b1f00..8623c390 100644 --- a/pkgs/messages_builder/lib/code_generation/method_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/method_generation.dart @@ -11,11 +11,12 @@ import 'generation.dart'; class MethodGeneration { final GenerationOptions options; final String? context; - final List messages; + final List messages; + final Map emptyFiles; - MethodGeneration(this.options, this.context, this.messages); + MethodGeneration(this.options, this.context, this.messages, this.emptyFiles); - Method? generateMessageCall(int index, MessageWithMetadata message) { + Method? generateMessageCall(int index, ParameterizedMessage message) { if (!message.nameIsDartConform) { return null; } @@ -64,6 +65,15 @@ class MethodGeneration { final data = await _assetLoader(dataFile); final messageList = MessageListJson.fromString(data, _pluralSelector);''', }; + final loadLibraries = emptyFiles.entries + .map( + (e) => ''' +if (locale == '${e.key}') { + await ${e.value}.loadLibrary(); +} +''', + ) + .join(' else '); mb ..name = 'loadLocale' ..requiredParameters.add(Parameter( @@ -79,11 +89,7 @@ class MethodGeneration { if (dataFile == null) { throw ArgumentError('Locale \$locale is not in \$knownLocales'); } - if (locale.startsWith('en')) { - await empty_en.loadLibrary(); - } else if (locale.startsWith('de')) { - await empty_de.loadLibrary(); - } + $loadLibraries $loading if (messageList.preamble.hash != info?.\$2) { throw ArgumentError(\'\'\' diff --git a/pkgs/messages_builder/lib/generation_options.dart b/pkgs/messages_builder/lib/generation_options.dart index 54b484c4..989ab478 100644 --- a/pkgs/messages_builder/lib/generation_options.dart +++ b/pkgs/messages_builder/lib/generation_options.dart @@ -49,7 +49,7 @@ class GenerationOptions { /// Where to write the message data files to. final Directory messageFolder; - final File generatedCodeFile; + final Directory generatedCodeFiles; final String packageName; @@ -60,7 +60,7 @@ class GenerationOptions { static const _pluralSelectorKey = 'plural_selector'; static const _arbInputFolderKey = 'arb_input_folder'; static const _messageOutputFolderKey = 'message_output_folder'; - static const _generatedCodeFileKey = 'generated_code_file'; + static const _generatedCodeFilesKey = 'generated_code_files'; static List get validKeys => [ _generateMethodsKey, @@ -70,7 +70,7 @@ class GenerationOptions { _headerKey, _arbInputFolderKey, _messageOutputFolderKey, - _generatedCodeFileKey, + _generatedCodeFilesKey, ]; GenerationOptions({ @@ -84,7 +84,7 @@ class GenerationOptions { required this.packageName, required this.arbFolder, required this.messageFolder, - required this.generatedCodeFile, + required this.generatedCodeFiles, }); static Future fromPubspec(String pubspecData) async { @@ -111,9 +111,8 @@ class GenerationOptions { messagesOptions?[_arbInputFolderKey] as String? ?? 'assets/l10n/'), messageFolder: Directory( messagesOptions?[_messageOutputFolderKey] as String? ?? 'assets/'), - generatedCodeFile: File( - messagesOptions?[_generatedCodeFileKey] as String? ?? - 'lib/src/messages.g.dart')); + generatedCodeFiles: Directory( + messagesOptions?[_generatedCodeFilesKey] as String? ?? 'lib/src/')); return generationOptions; } diff --git a/pkgs/messages_builder/lib/message_data_builder.dart b/pkgs/messages_builder/lib/message_data_builder.dart index 9ad21ff4..ca753e72 100644 --- a/pkgs/messages_builder/lib/message_data_builder.dart +++ b/pkgs/messages_builder/lib/message_data_builder.dart @@ -65,7 +65,7 @@ class MessageDataFileBuilder { } String _arbToData( - MessagesWithMetadata messageBundle, + MessageFile messageBundle, String arbFilePath, Serializer serializer, ) => diff --git a/pkgs/messages_builder/lib/message_parser/message_parser.dart b/pkgs/messages_builder/lib/message_parser/message_parser.dart index bf9b7fb6..4a78e648 100644 --- a/pkgs/messages_builder/lib/message_parser/message_parser.dart +++ b/pkgs/messages_builder/lib/message_parser/message_parser.dart @@ -10,7 +10,7 @@ import 'plural_parser.dart'; import 'select_parser.dart'; class MessageParser { - static MessageWithMetadata parse( + static ParameterizedMessage parse( String debugString, String fileContents, String name, { @@ -20,7 +20,7 @@ class MessageParser { final arguments = []; final message = parseNode(node, arguments, name, addId) ?? StringMessage(''); - return MessageWithMetadata(message, arguments, name); + return ParameterizedMessage(message, arguments, name); } static Message? parseNode( diff --git a/pkgs/messages_builder/lib/message_with_metadata.dart b/pkgs/messages_builder/lib/message_with_metadata.dart index fe4a0acb..44501cf3 100644 --- a/pkgs/messages_builder/lib/message_with_metadata.dart +++ b/pkgs/messages_builder/lib/message_with_metadata.dart @@ -4,7 +4,7 @@ import 'package:messages/messages.dart'; -class MessageWithMetadata { +class ParameterizedMessage { final Message message; final String name; List placeholders; @@ -12,40 +12,36 @@ class MessageWithMetadata { static final RegExp _dartName = RegExp(r'^[a-zA-Z][a-zA-Z_0-9]*$'); bool get nameIsDartConform => _dartName.hasMatch(name); - MessageWithMetadata(this.message, List arguments, this.name) + ParameterizedMessage(this.message, List arguments, this.name) : placeholders = arguments.map(Placeholder.new).toList(); } -class MessagesWithMetadata { - final List messages; +class MessageFile { + final List messages; final String? locale; final String? context; - final String? referencePath; final String hash; final bool hasMetadata; - MessagesWithMetadata( + MessageFile( this.messages, this.locale, this.context, - this.referencePath, this.hash, this.hasMetadata, ); - MessagesWithMetadata copyWith({ - List? messages, + MessageFile copyWith({ + List? messages, String? locale, String? context, - String? referencePath, String? hash, bool? hasMetadata, }) { - return MessagesWithMetadata( + return MessageFile( messages ?? this.messages, locale ?? this.locale, context ?? this.context, - referencePath ?? this.referencePath, hash ?? this.hash, hasMetadata ?? this.hasMetadata, ); diff --git a/pkgs/messages_builder/test/web_deserializer_native_test.dart b/pkgs/messages_builder/test/web_deserializer_native_test.dart index 6f5b9898..ee8eb8ed 100644 --- a/pkgs/messages_builder/test/web_deserializer_native_test.dart +++ b/pkgs/messages_builder/test/web_deserializer_native_test.dart @@ -37,8 +37,8 @@ Message intlPluralSelector( void main() { test('generateMessageFile from Object json', () { final message = StringMessage('Hello World'); - final message1 = MessageWithMetadata(message, [], 'helloWorld'); - final messageList = [message1]; + final message1 = ParameterizedMessage(message, [], 'helloWorld'); + final messageList = [message1]; final buffer = JsonSerializer() .serialize('', '', messageList.map((e) => e.message).toList()) .data;