diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9709356..c9d89f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ on: branches: [ main ] env: - FLUTTER_VERSION: '3.29.3' + FLUTTER_VERSION: '3.38.7' jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ba272..679dea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 1.0.0 + +- First stable release + +### Breaking Changes + +- **Minimum requirements updated**: Dart SDK `>=3.4.0 <4.0.0`, Flutter `>=3.22.0` +- Default `synthetic-package` changed from `true` to `false` to align with Flutter 3.32+ deprecation +- Import paths changed from `package:flutter_gen/gen_l10n/...` to direct source imports + +### Migration + +1. Add `synthetic-package: false` to your `l10n.yaml` (or rely on new default) +2. Update imports: + ```dart + // Before + import 'package:flutter_gen/gen_l10n/crowdin_localizations.dart'; + + // After + import 'package:your_app/l10n/crowdin_localizations.dart'; + ``` +3. Run `flutter pub run crowdin_sdk:gen` to regenerate + ## 0.8.1 - fix: add undeclared placeholders to the placeholders list diff --git a/README.md b/README.md index 368baf4..2b042ba 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

-# Crowdin Flutter SDK [](https://github.com/crowdin/flutter-sdk) +# Crowdin Flutter SDK The Crowdin Flutter SDK enables Over-The-Air (OTA) translation updates, delivering new translations from your Crowdin project directly to users without requiring app store updates. The SDK works on top of Flutter's standard localization system (`flutter_localizations`), providing a seamless bridge between your local ARB files and Crowdin's Content Delivery Network. @@ -60,7 +60,8 @@ This architecture ensures your app always has working translations (from local A ## Requirements -* Dart >=2.17.0 +* Flutter >=3.22.0 +* Dart >=3.4.0 ## Setup @@ -87,7 +88,7 @@ To manage distributions, open the Crowdin project and go to the *Translations* > ```yml dependencies: - crowdin_sdk: ^0.8.1 + crowdin_sdk: ^1.0.0 flutter_localizations: sdk: flutter @@ -103,16 +104,16 @@ To manage distributions, open the Crowdin project and go to the *Translations* > flutter pub run crowdin_sdk:gen ``` - This generates `crowdin_localizations.dart` in the `{FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n` directory. This wrapper class extends Flutter's generated localization classes to integrate Crowdin OTA translations. - + This generates `crowdin_localizations.dart` in the same directory as your `app_localizations.dart` (by default, in your `arb-dir` directory, e.g., `lib/l10n/`). This wrapper class extends Flutter's generated localization classes to integrate Crowdin OTA translations. + > **Important:** Re-run this command whenever you modify the structure of your ARB files (e.g., add/remove keys or change parameters). - Update localizationsDelegates in your project: ```dart - import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + import 'package:your_app/l10n/app_localizations.dart'; import 'package:crowdin_sdk/crowdin_sdk.dart'; - import 'package:flutter_gen/gen_l10n/crowdin_localizations.dart'; + import 'package:your_app/l10n/crowdin_localizations.dart'; ``` ```dart @@ -341,15 +342,10 @@ For more information about OAuth authorization in Crowdin, please check [this ar - Swedish - `sv`: `sv-SE` - Urdu (India) - `ur`: `ur-IN` -- Since flutter tool no longer generate a synthetic package:flutter_gen, please follow 1st way from [Migration Guide](https://docs.flutter.dev/release/breaking-changes/flutter-generate-i10n-source#migration-guide): - - Specify synthetic-package: false in the accompanying [l10n.yaml](https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#configuring-the-l10n-yaml-file) file: - `synthetic-package: false` - - - The files are generated into the path specified by arb-dir - `arb-dir: lib/i18n` - - - Or, specifically provide an output path: - `output-dir: lib/src/generated/i18n` +- **synthetic-package default**: Starting from v1.0.0, the SDK defaults to `synthetic-package: false` to align with Flutter 3.32+ deprecation of the synthetic `flutter_gen` package. If you're upgrading from an earlier version, see the [Migration Guide](https://docs.flutter.dev/release/breaking-changes/flutter-generate-i10n-source#migration-guide): + - Add `synthetic-package: false` to your `l10n.yaml` (or rely on the new default) + - Update imports from `package:flutter_gen/gen_l10n/...` to your local path (e.g., `package:your_app/l10n/...`) + - Run `flutter pub run crowdin_sdk:gen` to regenerate ## Contributing diff --git a/example/lib/main.dart b/example/lib/main.dart index 51e5d2f..e18b6d5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:crowdin_sdk/crowdin_sdk.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_gen/gen_l10n/crowdin_localizations.dart'; +import 'l10n/app_localizations.dart'; +import 'l10n/crowdin_localizations.dart'; import 'package:intl/intl.dart'; diff --git a/example/pubspec.lock b/example/pubspec.lock index 269e8d7..6992b22 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -103,7 +103,7 @@ packages: path: ".." relative: true source: path - version: "0.8.1" + version: "1.0.0" crypto: dependency: transitive description: @@ -132,10 +132,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -208,34 +208,34 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -264,10 +264,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" nm: dependency: transitive description: @@ -445,10 +445,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" typed_data: dependency: transitive description: @@ -525,10 +525,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -586,5 +586,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/lib/src/common/gen_l10n_types.dart b/lib/src/common/gen_l10n_types.dart index cbddfe7..34b4b1c 100644 --- a/lib/src/common/gen_l10n_types.dart +++ b/lib/src/common/gen_l10n_types.dart @@ -459,7 +459,7 @@ class Message { static final RegExp _pluralRE = RegExp(r'\s*\{([\w\s,]*),\s*plural\s*,'); - bool get isPlural => _pluralMatch != null && _pluralMatch!.groupCount == 1; + bool get isPlural => _pluralMatch != null && _pluralMatch.groupCount == 1; Placeholder getCountPlaceholder() { assert(isPlural); diff --git a/lib/src/crowdin_request_limiter.dart b/lib/src/crowdin_request_limiter.dart index 95959e2..9eb27be 100644 --- a/lib/src/crowdin_request_limiter.dart +++ b/lib/src/crowdin_request_limiter.dart @@ -23,7 +23,7 @@ class CrowdinRequestLimiter { bool get pauseRequests => _stopPermanently || _pauseRequests || _checkIsPausedForToday(); - init(CrowdinStorage storage) { + void init(CrowdinStorage storage) async { _storage = storage; _stopPermanently = _storage.getIsPausedPermanently() ?? false; _errorMap = _storage.getErrorMap() ?? {}; @@ -59,7 +59,7 @@ class CrowdinRequestLimiter { _storage.setErrorMap(_cleanErrorMapFromUnusedDays()); } - reset() { + void reset() { if (!_stopPermanently) { _pauseRequests = false; _errorMap = {}; diff --git a/lib/src/gen/l10n_config.dart b/lib/src/gen/l10n_config.dart index bae67e8..1c8d36f 100644 --- a/lib/src/gen/l10n_config.dart +++ b/lib/src/gen/l10n_config.dart @@ -18,7 +18,7 @@ class L10nConfig { required this.outputLocalizationFile, required this.outputDir, required this.outputClass, - this.syntheticPackage = true, + this.syntheticPackage = false, }); String get finalOutputDir => syntheticPackage @@ -41,7 +41,7 @@ class L10nConfig { String outputClass = yamlGenConfig['output-class'] ?? 'AppLocalizations'; - bool syntheticPackage = yamlGenConfig['synthetic-package'] ?? true; + bool syntheticPackage = yamlGenConfig['synthetic-package'] ?? false; return L10nConfig( arbDir: arbDir, diff --git a/lib/src/real_time_preview/crowdin_preview_manager.dart b/lib/src/real_time_preview/crowdin_preview_manager.dart index a531361..483ac0c 100644 --- a/lib/src/real_time_preview/crowdin_preview_manager.dart +++ b/lib/src/real_time_preview/crowdin_preview_manager.dart @@ -145,12 +145,11 @@ class CrowdinPreviewManager { 'Something went wrong when subscribing to translations for real-time preview. Metadata is not provided'); } else { _CrowdinMetadata metadata = _metadata!; - final webSocketTicketEvent = - 'update-draft:${metadata.wsHash}:${metadata.projectId}:${metadata.userId}'; - final ticket = await _getWebsocketTicket( - credentials: _credentials, event: webSocketTicketEvent); for (var id in finalMapping.values) { - final String event = '$webSocketTicketEvent:$langCode:$id'; + final String event = + 'update-draft:${metadata.wsHash}:${metadata.projectId}:${metadata.userId}:$langCode:$id'; + final ticket = + await _getWebsocketTicket(credentials: _credentials, event: event); if (ticket != null) { var data = jsonEncode({ 'action': 'subscribe', diff --git a/pubspec.yaml b/pubspec.yaml index bdae595..7e03f91 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,12 @@ name: crowdin_sdk description: Crowdin Flutter SDK for instant translation delivery Over-The-Air directly to your application -version: 0.8.1 +version: 1.0.0 repository: https://github.com/crowdin/flutter-sdk homepage: https://github.com/crowdin/flutter-sdk environment: - sdk: '>=2.17.0 <4.0.0' - flutter: ">=1.17.0" + sdk: '>=3.4.0 <4.0.0' + flutter: ">=3.22.0" dependencies: flutter: @@ -14,13 +14,13 @@ dependencies: http: '>=0.13.3 <2.0.0' intl: '>=0.17.0 <=0.20.2' shared_preferences: ^2.0.4 - connectivity_plus: '>=2.1.0 <7.0.0' + connectivity_plus: '>=2.1.0 <8.0.0' yaml: ^3.1.1 meta: ^1.7.0 path: ^1.8.1 oauth2: ^2.0.1 url_launcher: ^6.1.0 - app_links: '>=3.4.2 <7.0.0' + app_links: '>=3.4.2 <8.0.0' web_socket_channel: '>=2.2.0 <4.0.0' flutter_localizations: diff --git a/test/crowdin_api_test.dart b/test/crowdin_api_test.dart index 4c330bb..e2b7b97 100644 --- a/test/crowdin_api_test.dart +++ b/test/crowdin_api_test.dart @@ -101,7 +101,7 @@ void main() { test('getManifest returns null and increment error count on 400 status', () async { - await requestLimiter.init(storage); + requestLimiter.init(storage); final uri = Uri.parse('https://distributions.crowdin.net/hash/manifest.json'); @@ -119,7 +119,7 @@ void main() { 'getManifest returns null and do not call request when requests paused', () async { storage.setIsPausedPermanently(true); - await requestLimiter.init(storage); + requestLimiter.init(storage); final uri = Uri.parse('https://distributions.crowdin.net/hash/manifest.json'); diff --git a/test/crowdin_request_limiter_test.dart b/test/crowdin_request_limiter_test.dart index 8279f89..6f94785 100644 --- a/test/crowdin_request_limiter_test.dart +++ b/test/crowdin_request_limiter_test.dart @@ -34,7 +34,7 @@ void main() { test('should initialize with storage values', () async { storage.setIsPausedPermanently(true); - await requestLimiter.init(storage); + requestLimiter.init(storage); expect(storage.getIsPausedPermanently(), true); expect(requestLimiter.pauseRequests, true); }); @@ -49,13 +49,13 @@ void main() { test('should pause requests after max errors in a day', () async { storage.setErrorMap({getTodayDateString(): 10}); - await requestLimiter.init(storage); + requestLimiter.init(storage); expect(requestLimiter.pauseRequests, true); }); test('should reset error map and pause state', () async { storage.setErrorMap({getTodayDateString(): 10}); - await requestLimiter.init(storage); + requestLimiter.init(storage); expect(requestLimiter.pauseRequests, true); requestLimiter.reset(); expect(requestLimiter.pauseRequests, false); @@ -67,7 +67,7 @@ void main() { _formatter.format(DateTime.now().subtract(const Duration(days: 1))): 10, _formatter.format(DateTime.now().subtract(const Duration(days: 2))): 10, }); - await requestLimiter.init(storage); + requestLimiter.init(storage); requestLimiter.incrementErrorCounter(); expect(requestLimiter.pauseRequests, true); });