From af512ccf092b9726c481909c42701633a4f2af8c Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 14 Nov 2024 12:35:58 +0100 Subject: [PATCH] Add files to pubspec --- pkgs/messages/CHANGELOG.md | 3 +- pkgs/messages/example/bin/example.dart | 2 +- .../example/lib/AboutPage_en_empty.g.dart | 2 + .../example/lib/AboutPage_fr_empty.g.dart | 2 + ...sages.g.dart => AboutPage_messages.g.dart} | 121 +++--------- .../example/lib/HomePage_de_empty.g.dart | 2 + .../example/lib/HomePage_en_empty.g.dart | 2 + .../example/lib/HomePage_messages.g.dart | 110 +++++++++++ pkgs/messages/example/pubspec.yaml | 2 +- .../my_application/android/.gitignore | 13 ++ .../my_application/android/app/build.gradle | 48 +++++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 38 ++++ .../example/my_application/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 ++ .../main/res/drawable/launch_background.xml | 12 ++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 ++ .../app/src/main/res/values/strings.xml | 5 + .../app/src/main/res/values/styles.xml | 18 ++ .../app/src/profile/AndroidManifest.xml | 7 + .../my_application/android/build.gradle | 18 ++ .../android/deMessages/build.gradle | 47 +++++ .../deMessages/src/main/AndroidManifest.xml | 12 ++ .../android/enMessages/build.gradle | 47 +++++ .../enMessages/src/main/AndroidManifest.xml | 12 ++ .../my_application/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../my_application/android/settings.gradle | 25 +++ .../my_application/assets/l10n/messages.arb | 4 +- .../assets/l10n/messages_de.arb | 6 + .../my_application/assets/messages.arb.json | 2 +- .../assets/messages_de.arb.json | 1 + .../deferred_components_loading_units.yaml | 31 +++ .../my_application/lib/main.dart | 2 +- .../my_application/lib/src/empty_de.dart | 1 - .../my_application/lib/src/empty_en.dart | 1 - .../lib/src/my_app_de_DE_empty.g.dart | 2 + .../lib/src/my_app_en_US_empty.g.dart | 2 + ...messages.g.dart => my_app_messages.g.dart} | 58 +++--- .../my_application/pubspec.yaml | 16 +- .../lib/my_shopping_cart.dart | 2 +- .../lib/src/shopping_cart_en_US_empty.g.dart | 2 + ...s.g.dart => shopping_cart_messages.g.dart} | 49 +++-- .../my_shopping_cart/pubspec.yaml | 4 +- pkgs/messages/pubspec.yaml | 2 +- .../bin/messages_builder.dart | 15 +- pkgs/messages_builder/lib/arb_parser.dart | 8 +- pkgs/messages_builder/lib/builder.dart | 174 ---------------- .../lib/code_generation/class_generation.dart | 2 +- ...eneration.dart => classes_generation.dart} | 39 ++-- .../{code.dart => code_generation.dart} | 47 +++-- .../lib/code_generation/field_generation.dart | 16 +- .../code_generation/import_generation.dart | 9 +- .../message_file_metadata.dart | 15 ++ .../code_generation/method_generation.dart | 17 +- .../lib/generation_options.dart | 13 +- .../lib/message_code_builder.dart | 187 ++++++++++++++++++ .../lib/message_data_builder.dart | 60 +++--- .../lib/message_parser/message_parser.dart | 4 +- .../lib/message_with_metadata.dart | 20 +- pkgs/messages_builder/pubspec.yaml | 3 +- .../test/web_deserializer_native_test.dart | 4 +- 67 files changed, 977 insertions(+), 439 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} (55%) 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 create mode 100644 pkgs/messages/examples_flutter/my_application/android/.gitignore create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/build.gradle create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/debug/AndroidManifest.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/AndroidManifest.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/kotlin/com/example/my_application/MainActivity.kt create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable/launch_background.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values-night/styles.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/strings.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/styles.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/app/src/profile/AndroidManifest.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/build.gradle create mode 100644 pkgs/messages/examples_flutter/my_application/android/deMessages/build.gradle create mode 100644 pkgs/messages/examples_flutter/my_application/android/deMessages/src/main/AndroidManifest.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/enMessages/build.gradle create mode 100644 pkgs/messages/examples_flutter/my_application/android/enMessages/src/main/AndroidManifest.xml create mode 100644 pkgs/messages/examples_flutter/my_application/android/gradle.properties create mode 100644 pkgs/messages/examples_flutter/my_application/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 pkgs/messages/examples_flutter/my_application/android/settings.gradle create mode 100644 pkgs/messages/examples_flutter/my_application/assets/l10n/messages_de.arb create mode 100644 pkgs/messages/examples_flutter/my_application/assets/messages_de.arb.json create mode 100644 pkgs/messages/examples_flutter/my_application/deferred_components_loading_units.yaml 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.g.dart create mode 100644 pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart rename pkgs/messages/examples_flutter/my_application/lib/src/{messages.g.dart => my_app_messages.g.dart} (78%) create mode 100644 pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_en_US_empty.g.dart rename pkgs/messages/examples_flutter/my_shopping_cart/lib/src/{messages.g.dart => shopping_cart_messages.g.dart} (87%) delete mode 100644 pkgs/messages_builder/lib/builder.dart rename pkgs/messages_builder/lib/code_generation/{library_generation.dart => classes_generation.dart} (62%) 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 create mode 100644 pkgs/messages_builder/lib/message_code_builder.dart diff --git a/pkgs/messages/CHANGELOG.md b/pkgs/messages/CHANGELOG.md index 6996952a..e645e982 100644 --- a/pkgs/messages/CHANGELOG.md +++ b/pkgs/messages/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.3.0-wip +## 0.3.0 - Adapt to output data files to assets. +- Use a `dart run messages` command. ## 0.2.0 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..56705fe5 --- /dev/null +++ b/pkgs/messages/example/lib/AboutPage_en_empty.g.dart @@ -0,0 +1,2 @@ +// 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..7b2fd73c --- /dev/null +++ b/pkgs/messages/example/lib/AboutPage_fr_empty.g.dart @@ -0,0 +1,2 @@ +// 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 55% rename from pkgs/messages/example/lib/messages.g.dart rename to pkgs/messages/example/lib/AboutPage_messages.g.dart index fc227262..d90d9be5 100644 --- a/pkgs/messages/example/lib/messages.g.dart +++ b/pkgs/messages/example/lib/AboutPage_messages.g.dart @@ -1,30 +1,13 @@ // Generated by package:messages_builder. -// ignore_for_file: non_constant_identifier_names +// ignore_for_file: library_prefixes, non_constant_identifier_names +// ignore_for_file: unused_import 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 +43,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 +87,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..0f40a982 --- /dev/null +++ b/pkgs/messages/example/lib/HomePage_de_empty.g.dart @@ -0,0 +1,2 @@ +// 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..56705fe5 --- /dev/null +++ b/pkgs/messages/example/lib/HomePage_en_empty.g.dart @@ -0,0 +1,2 @@ +// 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..261e05dd --- /dev/null +++ b/pkgs/messages/example/lib/HomePage_messages.g.dart @@ -0,0 +1,110 @@ +// Generated by package:messages_builder. + +// ignore_for_file: library_prefixes, non_constant_identifier_names +// ignore_for_file: unused_import + +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/android/.gitignore b/pkgs/messages/examples_flutter/my_application/android/.gitignore new file mode 100644 index 00000000..55afd919 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/pkgs/messages/examples_flutter/my_application/android/app/build.gradle b/pkgs/messages/examples_flutter/my_application/android/app/build.gradle new file mode 100644 index 00000000..a6bf1f84 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/build.gradle @@ -0,0 +1,48 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.my_application" + compileSdk = 34 + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.my_application" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 33 + targetSdk = 34 + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} + +dependencies { + implementation "com.google.android.play:core:1.10.3" +} diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/debug/AndroidManifest.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/AndroidManifest.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3313e125 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/kotlin/com/example/my_application/MainActivity.kt b/pkgs/messages/examples_flutter/my_application/android/app/src/main/kotlin/com/example/my_application/MainActivity.kt new file mode 100644 index 00000000..aa284586 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/kotlin/com/example/my_application/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.my_application + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable/launch_background.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values-night/styles.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/strings.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..50b0d418 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + enMessages + deMessages + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/styles.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/app/src/profile/AndroidManifest.xml b/pkgs/messages/examples_flutter/my_application/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/build.gradle b/pkgs/messages/examples_flutter/my_application/android/build.gradle new file mode 100644 index 00000000..d2ffbffa --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/pkgs/messages/examples_flutter/my_application/android/deMessages/build.gradle b/pkgs/messages/examples_flutter/my_application/android/deMessages/build.gradle new file mode 100644 index 00000000..67b61c24 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/deMessages/build.gradle @@ -0,0 +1,47 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +apply plugin: "com.android.dynamic-feature" + +android { + namespace = "com.example.my_application.deMessages" + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + sourceSets { + applicationVariants.all { variant -> + main.assets.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_assets" + main.jniLibs.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_libs" + } + } + + defaultConfig { + minSdk = 33 + targetSdk = 34 + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } +} + +dependencies { + implementation(project(":app")) +} diff --git a/pkgs/messages/examples_flutter/my_application/android/deMessages/src/main/AndroidManifest.xml b/pkgs/messages/examples_flutter/my_application/android/deMessages/src/main/AndroidManifest.xml new file mode 100644 index 00000000..83782bdd --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/deMessages/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/enMessages/build.gradle b/pkgs/messages/examples_flutter/my_application/android/enMessages/build.gradle new file mode 100644 index 00000000..8db3cdfc --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/enMessages/build.gradle @@ -0,0 +1,47 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +apply plugin: "com.android.dynamic-feature" + +android { + namespace = "com.example.my_application.enMessages" + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + sourceSets { + applicationVariants.all { variant -> + main.assets.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_assets" + main.jniLibs.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_libs" + } + } + + defaultConfig { + minSdk = 33 + targetSdk = 34 + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } +} + +dependencies { + implementation(project(":app")) +} diff --git a/pkgs/messages/examples_flutter/my_application/android/enMessages/src/main/AndroidManifest.xml b/pkgs/messages/examples_flutter/my_application/android/enMessages/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c0a35924 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/enMessages/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/pkgs/messages/examples_flutter/my_application/android/gradle.properties b/pkgs/messages/examples_flutter/my_application/android/gradle.properties new file mode 100644 index 00000000..25971708 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/pkgs/messages/examples_flutter/my_application/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/messages/examples_flutter/my_application/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..7bb2df6b --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/pkgs/messages/examples_flutter/my_application/android/settings.gradle b/pkgs/messages/examples_flutter/my_application/android/settings.gradle new file mode 100644 index 00000000..b5bcf325 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app", ':enMessages', ':deMessages' 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/assets/l10n/messages_de.arb b/pkgs/messages/examples_flutter/my_application/assets/l10n/messages_de.arb new file mode 100644 index 00000000..05899197 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/assets/l10n/messages_de.arb @@ -0,0 +1,6 @@ +{ + "@@locale": "de_DE", + "@@context": "my_app", + "@@x-template": "messages.arb", + "current_sale_name": "{arg, select, other{Kein Sonderverkauf} winter{Winterschlussverkauf} summer{Sommerschlussverkauf}}" +} diff --git a/pkgs/messages/examples_flutter/my_application/assets/messages.arb.json b/pkgs/messages/examples_flutter/my_application/assets/messages.arb.json index f270825d..4e6bffbb 100644 --- a/pkgs/messages/examples_flutter/my_application/assets/messages.arb.json +++ b/pkgs/messages/examples_flutter/my_application/assets/messages.arb.json @@ -1 +1 @@ -[0,"en_US","IT21w/eV",0,null,[4,0,"No sale",{"winter":"Winter sale","summer":"Summer sale"}]] \ No newline at end of file +[0,"en_US","h/qGCx3k",0,null,[4,0,"No sale",{"winter":"Winter sale","summer":"Summer sale"}]] \ No newline at end of file diff --git a/pkgs/messages/examples_flutter/my_application/assets/messages_de.arb.json b/pkgs/messages/examples_flutter/my_application/assets/messages_de.arb.json new file mode 100644 index 00000000..a378e138 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/assets/messages_de.arb.json @@ -0,0 +1 @@ +[0,"de_DE","xXViKglj",0,null,[4,0,"Kein Sonderverkauf",{"winter":"Winterschlussverkauf","summer":"Sommerschlussverkauf"}]] \ No newline at end of file diff --git a/pkgs/messages/examples_flutter/my_application/deferred_components_loading_units.yaml b/pkgs/messages/examples_flutter/my_application/deferred_components_loading_units.yaml new file mode 100644 index 00000000..228df920 --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/deferred_components_loading_units.yaml @@ -0,0 +1,31 @@ +# ============================================================================== +# The contents of this file are automatically generated and it is not +# recommended to modify this file manually. +# ============================================================================== +# +# In order to prevent unexpected splitting of deferred apps, this file records +# the last generated set of loading units. It only possible to obtain the final +# configuration of loading units after compilation is complete. This means +# improperly setup deferred imports can only be detected after compilation. +# +# This file allows the build tool to detect any changes in the generated +# loading units. During the next build attempt, loading units in this file are +# compared against the newly generated loading units to check for any new or +# removed loading units. In the case where loading units do not match, the build +# will fail and ask the developer to verify that the `deferred-components` +# configuration in `pubspec.yaml` is correct. Developers should make any +# necessary changes to integrate new and changed loading units or remove no +# longer existing loading units from the configuration. The build command should +# then be re-run to continue the build process. +# +# Sometimes, changes to the generated loading units may be unintentional. If +# the list of loading units in this file is not what is expected, the app's +# deferred imports should be reviewed. Third party plugins and packages may +# also introduce deferred imports that result in unexpected loading units. +loading-units: + - id: 2 + libraries: + - package:my_application/src/my_app_de_DE_empty.g.dart + - id: 3 + libraries: + - package:my_application/src/my_app_en_US_empty.g.dart diff --git a/pkgs/messages/examples_flutter/my_application/lib/main.dart b/pkgs/messages/examples_flutter/my_application/lib/main.dart index 8c049d09..1a47fb4f 100644 --- a/pkgs/messages/examples_flutter/my_application/lib/main.dart +++ b/pkgs/messages/examples_flutter/my_application/lib/main.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:my_application/src/messages.g.dart'; +import 'package:my_application/src/my_app_messages.g.dart'; import 'package:my_shopping_cart/my_shopping_cart.dart'; void main() { 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/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..79258f2a --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_de_DE_empty.g.dart @@ -0,0 +1,2 @@ +// 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.g.dart b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart new file mode 100644 index 00000000..54fea7ce --- /dev/null +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_en_US_empty.g.dart @@ -0,0 +1,2 @@ +// 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/messages.g.dart b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_messages.g.dart similarity index 78% rename from pkgs/messages/examples_flutter/my_application/lib/src/messages.g.dart rename to pkgs/messages/examples_flutter/my_application/lib/src/my_app_messages.g.dart index 798b3dbc..09c94540 100644 --- a/pkgs/messages/examples_flutter/my_application/lib/src/messages.g.dart +++ b/pkgs/messages/examples_flutter/my_application/lib/src/my_app_messages.g.dart @@ -1,30 +1,13 @@ // Generated by package:messages_builder. -// ignore_for_file: non_constant_identifier_names +// ignore_for_file: library_prefixes, non_constant_identifier_names +// ignore_for_file: unused_import 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 '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); @@ -36,7 +19,11 @@ class MyAppMessages { final Map _messages = {}; static const _dataFiles = { - 'en_US': ('packages/my_application/assets/messages.arb.json', 'IT21w/eV') + 'de_DE': ( + 'packages/my_application/assets/messages_de.arb.json', + 'xXViKglj' + ), + 'en_US': ('packages/my_application/assets/messages.arb.json', 'h/qGCx3k') }; String get currentLocale => _currentLocale; @@ -52,6 +39,12 @@ class MyAppMessages { if (dataFile == null) { throw ArgumentError('Locale $locale is not in $knownLocales'); } + 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); final messageList = MessageListJson.fromString(data, _pluralSelector); if (messageList.preamble.hash != info?.$2) { @@ -72,3 +65,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/pubspec.yaml b/pkgs/messages/examples_flutter/my_application/pubspec.yaml index 24af6ad7..c6719285 100644 --- a/pkgs/messages/examples_flutter/my_application/pubspec.yaml +++ b/pkgs/messages/examples_flutter/my_application/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 0.1.0 environment: - sdk: ^3.6.0-270.0.dev + sdk: ^3.5.0 dependencies: flutter: @@ -25,5 +25,15 @@ dev_dependencies: flutter: uses-material-design: true - assets: - - assets/messages.arb.json + deferred-components: + - name: enMessages + libraries: + - package:my_application/src/my_app_en_US_empty.g.dart + assets: + - assets/messages.arb.json + - name: deMessages + libraries: + - package:my_application/src/my_app_de_DE_empty.g.dart + assets: + - assets/messages_de.arb.json + diff --git a/pkgs/messages/examples_flutter/my_shopping_cart/lib/my_shopping_cart.dart b/pkgs/messages/examples_flutter/my_shopping_cart/lib/my_shopping_cart.dart index d30ff2fe..b7debf51 100644 --- a/pkgs/messages/examples_flutter/my_shopping_cart/lib/my_shopping_cart.dart +++ b/pkgs/messages/examples_flutter/my_shopping_cart/lib/my_shopping_cart.dart @@ -5,7 +5,7 @@ library my_shopping_cart; import 'package:flutter/services.dart'; -import 'src/messages.g.dart'; +import 'src/shopping_cart_messages.g.dart'; class MyShoppingCart { final _messages = ShoppingCartMessages(rootBundle.loadString); diff --git a/pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_en_US_empty.g.dart b/pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_en_US_empty.g.dart new file mode 100644 index 00000000..54fea7ce --- /dev/null +++ b/pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_en_US_empty.g.dart @@ -0,0 +1,2 @@ +// 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_shopping_cart/lib/src/messages.g.dart b/pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_messages.g.dart similarity index 87% rename from pkgs/messages/examples_flutter/my_shopping_cart/lib/src/messages.g.dart rename to pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_messages.g.dart index c6c3c26b..6c24d952 100644 --- a/pkgs/messages/examples_flutter/my_shopping_cart/lib/src/messages.g.dart +++ b/pkgs/messages/examples_flutter/my_shopping_cart/lib/src/shopping_cart_messages.g.dart @@ -1,30 +1,12 @@ // Generated by package:messages_builder. -// ignore_for_file: non_constant_identifier_names +// ignore_for_file: library_prefixes, non_constant_identifier_names +// ignore_for_file: unused_import 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 'shopping_cart_en_US_empty.g.dart' deferred as shopping_cart_en_US_empty; class ShoppingCartMessages { ShoppingCartMessages(this._assetLoader); @@ -52,6 +34,10 @@ class ShoppingCartMessages { if (dataFile == null) { throw ArgumentError('Locale $locale is not in $knownLocales'); } + if (locale == 'en_US') { + await shopping_cart_en_US_empty.loadLibrary(); + } + final data = await _assetLoader(dataFile); final messageList = MessageListJson.fromString(data, _pluralSelector); if (messageList.preamble.hash != info?.$2) { @@ -72,3 +58,24 @@ class ShoppingCartMessages { String itemsInCart(int count) => _currentMessages.generateStringAtIndex(0, [count]); } + +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_shopping_cart/pubspec.yaml b/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml index 09a0a53f..21f0d74c 100644 --- a/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml +++ b/pkgs/messages/examples_flutter/my_shopping_cart/pubspec.yaml @@ -5,7 +5,7 @@ version: 0.0.1 publish_to: none environment: - sdk: ^3.6.0-270.0.dev + sdk: ^3.5.0 flutter: ">=1.17.0" dependencies: @@ -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/pubspec.yaml b/pkgs/messages/pubspec.yaml index 9c7c23bf..962903ab 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.3.0-wip +version: 0.3.0 repository: https://github.com/dart-lang/i18n/tree/main/pkgs/messages publish_to: none diff --git a/pkgs/messages_builder/bin/messages_builder.dart b/pkgs/messages_builder/bin/messages_builder.dart index b9a08917..515b8977 100644 --- a/pkgs/messages_builder/bin/messages_builder.dart +++ b/pkgs/messages_builder/bin/messages_builder.dart @@ -4,8 +4,8 @@ import 'dart:io'; -import 'package:messages_builder/builder.dart'; import 'package:messages_builder/generation_options.dart'; +import 'package:messages_builder/message_code_builder.dart'; import 'package:messages_builder/message_data_builder.dart'; /// Regenerates both data files and code for retrieving the messages from the @@ -14,26 +14,27 @@ import 'package:messages_builder/message_data_builder.dart'; /// Executed by running `dart run messages_builder` in a project with a /// dependency on `package:messages`. Future main(List args) async { - final generationOptions = await _generationOptions(); + final pubspecUri = Directory.current.uri.resolve('pubspec.yaml'); + final generationOptions = await _generationOptions(pubspecUri); final inputFolder = Directory.fromUri( Directory.current.uri.resolve('assets/').resolve('l10n/')); final outputFolder = Directory.fromUri(Directory.current.uri.resolve('assets/')); - final mapping = await MessageDataFileBuilder( + final mapping = await MessageDataBuilder( inputFolder: inputFolder, outputFolder: outputFolder, generationOptions: generationOptions, ).run(); - await MessageCallingCodeGenerator( - mapping: mapping, + await MessageCodeBuilder( + inputToOutputFiles: mapping, options: generationOptions, + pubspecUri: pubspecUri, ).build(); } -Future _generationOptions() async { - final pubspecUri = Directory.current.uri.resolve('pubspec.yaml'); +Future _generationOptions(Uri pubspecUri) async { final file = File.fromUri(pubspecUri); final pubspecContents = await file.readAsString(); return await GenerationOptions.fromPubspec(pubspecContents); 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 deleted file mode 100644 index d6c26374..00000000 --- a/pkgs/messages_builder/lib/builder.dart +++ /dev/null @@ -1,174 +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 '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/library_generation.dart'; -import 'generation_options.dart'; -import 'message_with_metadata.dart'; - -class MessageCallingCodeGenerator { - final GenerationOptions options; - final Map mapping; - - MessageCallingCodeGenerator({ - required this.options, - required this.mapping, - }); - - 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 code = CodeGenerator( - options: options, - libraries: libraries, - ).generate(); - - await options.generatedCodeFile.create(recursive: true); - await options.generatedCodeFile.writeAsString(code); - } - } - - /// 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)); - - return inputMessageFile.copyWith(messages: 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(); - } - - Future> getParsedMessageFiles() async => - Future.wait(mapping.entries - .map((p) async => ParsedMessageFile( - path: path.relative(p.value, from: Directory.current.path), - message: - await parseMessageFile(await getArbfile(p.key), options), - )) - .toList()); - - Future getArbfile(String path) async => - 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, - ) { - /// 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; - } - - return 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, - ) { - var contextMessage = 'The'; - if (context != null) { - contextMessage = 'For the messages in $context, the'; - } - final fileList = localeToResource.map((e) => '\t${e.id}').join('\n'); - print( - '''$contextMessage following files need to be declared in your assets:\n$fileList'''); - } -} - -Future parseMessageFile( - String arbFile, - GenerationOptions options, -) async { - final decoded = jsonDecode(arbFile) as Map; - final arb = Map.castFrom(decoded); - return ArbParser(options.findById).parseMessageFile(arb); -} - -class ParsedMessageFile { - final String path; - final MessagesWithMetadata message; - - ParsedMessageFile({required this.path, required this.message}); -} 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/library_generation.dart b/pkgs/messages_builder/lib/code_generation/classes_generation.dart similarity index 62% rename from pkgs/messages_builder/lib/code_generation/library_generation.dart rename to pkgs/messages_builder/lib/code_generation/classes_generation.dart index 10725044..db1f6504 100644 --- a/pkgs/messages_builder/lib/code_generation/library_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/classes_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 { +class ClassesGeneration { 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, - ); - - Library generate() { + final String initialLocale; + final List messages; + final Iterable messageFilesMetadata; + final Map emptyFiles; + + ClassesGeneration({ + required this.options, + required this.context, + required this.initialLocale, + required this.messages, + required this.messageFilesMetadata, + required this.emptyFiles, + }); + + List 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( @@ -51,6 +54,6 @@ class LibraryGeneration { methods, ).generate(); - return Library((b) => b..body.addAll(classes)); + return classes; } } 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..768eaaa8 100644 --- a/pkgs/messages_builder/lib/code_generation/code.dart +++ b/pkgs/messages_builder/lib/code_generation/code_generation.dart @@ -10,33 +10,32 @@ import 'import_generation.dart'; class CodeGenerator { final GenerationOptions options; - final List libraries; + final List classes; + final Iterable emptyFilePaths; - CodeGenerator({required this.options, required this.libraries}); + CodeGenerator({ + required this.options, + required this.classes, + 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.addAll([ + 'non_constant_identifier_names', + 'unused_import', + 'library_prefixes', + ]) + ..comments.add(options.header) + ..directives.addAll(imports) + ..body.addAll([ + ...classes, + 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 1ab73f87..56f2bf9a 100644 --- a/pkgs/messages_builder/lib/code_generation/import_generation.dart +++ b/pkgs/messages_builder/lib/code_generation/import_generation.dart @@ -8,8 +8,9 @@ import '../generation_options.dart'; class ImportGeneration { final GenerationOptions options; + final Iterable emptyFiles; - ImportGeneration(this.options); + ImportGeneration(this.options, this.emptyFiles); List generate() { final serializationImports = switch (options.deserialization) { @@ -24,9 +25,15 @@ class ImportGeneration { ], PluralSelectorType.custom => [], }; + + final deferredImports = emptyFiles.map((emptyFilePath) { + return Directive.importDeferredAs('$emptyFilePath.g.dart', emptyFilePath); + }); + return [ ...serializationImports, ...pluralImports, + ...deferredImports, ]; } } 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..02df752b --- /dev/null +++ b/pkgs/messages_builder/lib/code_generation/message_file_metadata.dart @@ -0,0 +1,15 @@ +// Copyright (c) 2024, 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 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 c21abe90..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,6 +89,7 @@ class MethodGeneration { if (dataFile == null) { throw ArgumentError('Locale \$locale is not in \$knownLocales'); } + $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_code_builder.dart b/pkgs/messages_builder/lib/message_code_builder.dart new file mode 100644 index 00000000..f224484f --- /dev/null +++ b/pkgs/messages_builder/lib/message_code_builder.dart @@ -0,0 +1,187 @@ +// 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 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +import 'arb_parser.dart'; +import 'code_generation/classes_generation.dart'; +import 'code_generation/code_generation.dart'; +import 'code_generation/message_file_metadata.dart'; +import 'generation_options.dart'; +import 'message_with_metadata.dart'; + +class MessageCodeBuilder { + final GenerationOptions options; + final Map inputToOutputFiles; + final Uri pubspecUri; + + MessageCodeBuilder({ + required this.options, + required this.inputToOutputFiles, + required this.pubspecUri, + }); + + Future build() async { + 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); + + await includeFilesInPubspec( + context, + childrensMetadata.map((e) => e.path.split('/').skip(2).join('/')), + options.generatedCodeFiles, + ); + + final dummyFilePaths = Map.fromEntries(childrensMetadata + .map((e) => e.locale) + .map((e) => MapEntry(e, [context, e, 'empty'].join('_')))); + + final library = ClassesGeneration( + options: options, + context: context, + initialLocale: parent.file.locale!, + messages: parent.file.messages, + messageFilesMetadata: childrensMetadata, + emptyFiles: dummyFilePaths) + .generate(); + + final code = CodeGenerator( + options: options, + classes: library, + emptyFilePaths: dummyFilePaths.values, + ).generate(); + + final parentPath = Directory(options.generatedCodeFiles.path); + + final codeFile = File(path.join( + parentPath.path, '${context ?? 'm${counter++}'}_messages.g.dart')); + print( + 'Generate dart file at ${path.relative(codeFile.path, from: '.')}.'); + await codeFile.create(recursive: true); + await codeFile.writeAsString(code); + + 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`. +'''); + } + } + } + + 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> parseMessageFiles() async => + Future.wait(inputToOutputFiles.entries + .map((p) async => LocatedMessageFile( + path: path.relative(p.value, from: Directory.current.path), + file: await parseMessageFile(await getArbfile(p.key), options), + )) + .toList()); + + Future getArbfile(String path) async => + await File(path).readAsString(); + + /// Either get the referenced parent file, or try to infer which it might be. + static LocatedMessageFile getParentFile( + List messageFiles, + LocatedMessageFile 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 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 potentialParent ?? currentFile; + } + + /// Display a notification to the user to include the newly generated files + /// in their assets. + Future includeFilesInPubspec( + String? context, + Iterable fileList, + Directory generatedCodeFiles, + ) async { + final contextMessage = + context != null ? 'For the messages in $context, the' : 'The'; + final fileListJoined = fileList.map((e) => '\t$e').join('\n'); + + final pubspecFile = File.fromUri(pubspecUri); + final pubspecString = await pubspecFile.readAsString(); + final yamlEditor = YamlEditor(pubspecString); + if (yamlEditor.contains(['flutter', 'assets']) || + yamlEditor.contains(['dependencies', 'flutter'])) { + if (!yamlEditor.contains(['flutter', 'assets'])) { + yamlEditor.update(['flutter'], {'assets': []}); + } + final existingAssets = + (yamlEditor.parseAt(['flutter', 'assets']).value as YamlList) + .map((element) => element as String); + final newFiles = fileList.whereNot(existingAssets.contains); + for (final file in newFiles) { + yamlEditor.appendToList(['flutter', 'assets'], file); + } + if (newFiles.isNotEmpty) { + print( + '''$contextMessage following files have been declared in your assets:\n$newFiles'''); + } + await pubspecFile.writeAsString(yamlEditor.toString()); + } else { + print( + '''$contextMessage following files must be added to your assets:\n$fileListJoined'''); + } + } +} + +Future parseMessageFile( + String arbFile, + GenerationOptions options, +) async { + final decoded = jsonDecode(arbFile) as Map; + final arb = Map.castFrom(decoded); + return ArbParser(options.findById).parseMessageFile(arb); +} + +class LocatedMessageFile { + final String path; + final MessageFile file; + + LocatedMessageFile({required this.path, required this.file}); +} + +extension on YamlEditor { + bool contains(List path) => + parseAt(path, orElse: () => wrapAsYamlNode(null)).value != null; +} diff --git a/pkgs/messages_builder/lib/message_data_builder.dart b/pkgs/messages_builder/lib/message_data_builder.dart index 9ad21ff4..69c6e5ec 100644 --- a/pkgs/messages_builder/lib/message_data_builder.dart +++ b/pkgs/messages_builder/lib/message_data_builder.dart @@ -7,23 +7,24 @@ import 'dart:io'; import 'package:messages_serializer/messages_serializer.dart'; import 'package:path/path.dart' as p; -import 'builder.dart'; import 'generation_options.dart'; +import 'message_code_builder.dart'; import 'message_with_metadata.dart'; -class MessageDataFileBuilder { +class MessageDataBuilder { final Directory inputFolder; final Directory outputFolder; final GenerationOptions generationOptions; - MessageDataFileBuilder({ + MessageDataBuilder({ required this.inputFolder, required this.outputFolder, required this.generationOptions, }); Future> run() async { - print('Starting to add arb files from $inputFolder to $outputFolder'); + print(''' +Searching "${p.relative(inputFolder.path, from: '.')}" for arb files.'''); final arbFiles = await inputFolder .list() .where((file) => file is File) @@ -31,14 +32,29 @@ class MessageDataFileBuilder { .where((path) => p.extension(path) == '.arb') .toList(); - final mapping = {}; if (arbFiles.isEmpty) { - print('No `.arb` files found in $inputFolder.'); - return mapping; + print('No arb files in ${p.relative(inputFolder.path, from: '.')}.'); + return {}; } - for (final arbFilePath in arbFiles) { - print('Generating $arbFilePath, bundle this in your assets.'); + final serializer = JsonSerializer(generationOptions.findById); + final inputOutputPairs = Map.fromEntries(arbFiles.map( + (inputPath) { + final assetName = p.setExtension( + p.basename(inputPath), + '.arb${serializer.extension}', + ); + final outputDataPath = p.join(outputFolder.path, assetName); + return MapEntry(inputPath, outputDataPath); + }, + )); + + if (arbFiles.isNotEmpty) { + print('Generating data files from arb sources.'); + } + for (final MapEntry(key: arbFilePath, value: outputDataPath) + in inputOutputPairs.entries) { + stdout.write('${p.relative(arbFilePath, from: '.')} --> '); final arbFileUri = Uri.file(arbFilePath); final arbFileContents = await File.fromUri(arbFileUri).readAsString(); final messageBundle = await parseMessageFile( @@ -46,26 +62,24 @@ class MessageDataFileBuilder { generationOptions, ); - final serializer = JsonSerializer(generationOptions.findById); - final data = _arbToData(messageBundle, arbFilePath, serializer); - final assetName = p.setExtension( - p.basename(arbFilePath), - '.arb${serializer.extension}', - ); - - final outputDataPath = outputFolder.uri.resolve(assetName); - final dataFile = File.fromUri(outputDataPath); - await dataFile.create(); - await dataFile.writeAsString(data); - mapping[arbFilePath] = outputDataPath.path; + stdout.writeln('${p.relative(outputDataPath, from: '.')}.'); + final dataFile = File(outputDataPath); + if (await dataFile.exists() && (await dataFile.readAsString() == data)) { + continue; + } else { + await dataFile.create(); + await dataFile.writeAsString(data); + print(''' +${p.relative(arbFilePath, from: '.')} --> ${p.relative(outputDataPath, from: '.')}'''); + } } - return mapping; + return inputOutputPairs; } 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/pubspec.yaml b/pkgs/messages_builder/pubspec.yaml index 2d364a5e..f4074c21 100644 --- a/pkgs/messages_builder/pubspec.yaml +++ b/pkgs/messages_builder/pubspec.yaml @@ -20,7 +20,8 @@ dependencies: messages_serializer: path: ../messages_serializer path: ^1.8.2 - yaml: ^3.1.1 + yaml: ^3.1.2 + yaml_edit: ^2.2.1 dev_dependencies: dart_flutter_team_lints: ^3.0.0 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;