diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1957e4b..5a8f0a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 0.4.0
+- **Enhanced Locale Config**: Added `useFallbackTranslationsForEmptyResources`, `ignorePluralRules`, `extraAssetLoaders`, and `errorWidget` to `PlayxLocaleConfig`.
+- **Script Code Support**: Added `scriptCode` natively to `XLocale` and updated locale matching in `PlayxLocaleController` to properly utilize it.
+- **Deep Translation Merging**: `TranslationManager` now supports combining JSON translation maps securely from multiple asset loaders.
+- **Device Sync Persistence**: `updateToDeviceLocale()` now saves a special flag in preferences (index `-1`). When the application boots, instead of overriding the device's current locale with a historically cached exact locale or `startLocale`, the controller auto-routes strictly to whatever the current `deviceLocale` dictates. This allows developers to offer a persistent "system default" setting.
+- **Synchronous Any-Locale Translation**: Added `preloadSupportedLocales` to `PlayxLocaleConfig`. When enabled, it caches all dictionaries during initialization, allowing developers to query translations for any loaded `Locale? locale` parametrically using `tr(..., locale: ...)` synchronously, completely out-of-bounds, without overriding the app environment.
+
## 0.3.1
- Bumping `playx_core` dependency to `^0.7.4`.
- Renaming `isRtl` and `isLtr` getters to `isCurrentLocaleRtl` and `isCurrentLocaleLtr` respectively for better clarity.
diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies
index b0d50d8..55df1fd 100644
--- a/example/.flutter-plugins-dependencies
+++ b/example/.flutter-plugins-dependencies
@@ -1 +1 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_android-2.2.17/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2025-11-18 00:44:55.505853","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
\ No newline at end of file
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage_darwin","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_darwin-0.2.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage-10.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_android-2.2.17/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.10/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_darwin","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_darwin-0.2.0/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-3.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-4.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-2.1.0/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_darwin","flutter_secure_storage_linux","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_darwin","dependencies":[]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2026-04-01 15:49:06.975748","version":"3.38.7","swift_package_manager_enabled":{"ios":false,"macos":false}}
\ No newline at end of file
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 7c56964..1dc6cf7 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/example/ios/Podfile b/example/ios/Podfile
index e549ee2..620e46e 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
new file mode 100644
index 0000000..d3ab58f
--- /dev/null
+++ b/example/ios/Podfile.lock
@@ -0,0 +1,37 @@
+PODS:
+ - Flutter (1.0.0)
+ - flutter_secure_storage_darwin (10.0.0):
+ - Flutter
+ - FlutterMacOS
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
+ - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ flutter_secure_storage_darwin:
+ :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
+ path_provider_foundation:
+ :path: ".symlinks/plugins/path_provider_foundation/darwin"
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+
+SPEC CHECKSUMS:
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+ flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
+ path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
+ shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
+
+PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
+
+COCOAPODS: 1.16.2
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index f109a11..69c3c1f 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -8,8 +8,10 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 16CD4ECFBE07BDC4CA6BF884 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2322ACF59C699FB546362789 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 4D1582C0AC34C1CB875C0726 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C2C1BDF23FC2D0A835E2D70 /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@@ -42,12 +44,19 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 2322ACF59C699FB546362789 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3A366468B591D70BECBCFCC7 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 61FA2CD2201312E546D2F2BE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 6C2C1BDF23FC2D0A835E2D70 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7784ABE32650A73F12B90C3C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 832C59ECF11E0EC739EFFEAD /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ 8EFCD8E2BC201DBAE2FA8EAD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -55,13 +64,23 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ CFC30A6A7E5CB2FD3AF9BE5D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 0F2B610A87E313099CCC1B99 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 4D1582C0AC34C1CB875C0726 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 16CD4ECFBE07BDC4CA6BF884 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -76,6 +95,29 @@
path = RunnerTests;
sourceTree = "";
};
+ 4966E70334CBA96494A45E4D /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ CFC30A6A7E5CB2FD3AF9BE5D /* Pods-Runner.debug.xcconfig */,
+ 61FA2CD2201312E546D2F2BE /* Pods-Runner.release.xcconfig */,
+ 8EFCD8E2BC201DBAE2FA8EAD /* Pods-Runner.profile.xcconfig */,
+ 832C59ECF11E0EC739EFFEAD /* Pods-RunnerTests.debug.xcconfig */,
+ 7784ABE32650A73F12B90C3C /* Pods-RunnerTests.release.xcconfig */,
+ 3A366468B591D70BECBCFCC7 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 6A130E9E42AB80BB44C04A6D /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 2322ACF59C699FB546362789 /* Pods_Runner.framework */,
+ 6C2C1BDF23FC2D0A835E2D70 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -94,6 +136,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
+ 4966E70334CBA96494A45E4D /* Pods */,
+ 6A130E9E42AB80BB44C04A6D /* Frameworks */,
);
sourceTree = "";
};
@@ -128,8 +172,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
+ 2A7FAE604EB726A2C50B5838 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
+ 0F2B610A87E313099CCC1B99 /* Frameworks */,
);
buildRules = (
);
@@ -145,12 +191,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ 948DA71D134D31F494F59C83 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 7A26DFE72E842B242B098E44 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -222,6 +270,28 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 2A7FAE604EB726A2C50B5838 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -238,6 +308,45 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
+ 7A26DFE72E842B242B098E44 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 948DA71D134D31F494F59C83 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -346,7 +455,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -379,6 +488,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 832C59ECF11E0EC739EFFEAD /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -396,6 +506,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 7784ABE32650A73F12B90C3C /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -411,6 +522,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 3A366468B591D70BECBCFCC7 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -473,7 +585,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -524,7 +636,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 30daaf5..70b2fe0 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -16,6 +16,7 @@ Future main() async {
startLocale: locales.first,
fallbackLocale: locales.first,
useFallbackTranslations: true,
+ preloadSupportedLocales: true,
);
await PlayxLocalization.boot(config: config);
@@ -66,6 +67,15 @@ class MyHomePage extends StatelessWidget {
? Colors.blueAccent
: Colors.black),
),
+ Text(
+ context.tr(AppTrans.changeLanguageTitle,
+ locale: Locale('ar')),
+ style: TextStyle(
+ fontSize: 18,
+ color: (context.tr(AppTrans.changeLanguageTitle)).isArabic
+ ? Colors.blueAccent
+ : Colors.black),
+ ),
const SizedBox(
height: 20,
),
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 37af1fe..66f641b 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,12 +5,12 @@
import FlutterMacOS
import Foundation
-import flutter_secure_storage_macos
+import flutter_secure_storage_darwin
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
- FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
+ FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 7d7ae1f..3df66a5 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -61,10 +61,10 @@ packages:
dependency: transitive
description:
name: equatable
- sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
+ sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
- version: "2.0.7"
+ version: "2.0.8"
fake_async:
dependency: transitive
description:
@@ -119,50 +119,50 @@ packages:
dependency: transitive
description:
name: flutter_secure_storage
- sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
+ sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40
url: "https://pub.dev"
source: hosted
- version: "9.2.4"
- flutter_secure_storage_linux:
+ version: "10.0.0"
+ flutter_secure_storage_darwin:
dependency: transitive
description:
- name: flutter_secure_storage_linux
- sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
+ name: flutter_secure_storage_darwin
+ sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3"
url: "https://pub.dev"
source: hosted
- version: "1.2.3"
- flutter_secure_storage_macos:
+ version: "0.2.0"
+ flutter_secure_storage_linux:
dependency: transitive
description:
- name: flutter_secure_storage_macos
- sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
+ name: flutter_secure_storage_linux
+ sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
url: "https://pub.dev"
source: hosted
- version: "3.1.3"
+ version: "3.0.0"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
- sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
+ sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
url: "https://pub.dev"
source: hosted
- version: "1.1.2"
+ version: "2.0.1"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
- sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
+ sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3"
url: "https://pub.dev"
source: hosted
- version: "1.2.1"
+ version: "2.1.0"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
- sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
+ sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
url: "https://pub.dev"
source: hosted
- version: "3.1.2"
+ version: "4.1.0"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -177,10 +177,10 @@ packages:
dependency: transitive
description:
name: get_it
- sha256: "84792561b731b6463d053e9761a5236da967c369da10b134b8585a5e18429956"
+ sha256: "568d62f0e68666fb5d95519743b3c24a34c7f19d834b0658c46e26d778461f66"
url: "https://pub.dev"
source: hosted
- version: "9.0.5"
+ version: "9.2.1"
intl:
dependency: transitive
description:
@@ -189,14 +189,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.20.2"
- js:
- dependency: transitive
- description:
- name: js
- sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
- url: "https://pub.dev"
- source: hosted
- version: "0.6.7"
leak_tracker:
dependency: transitive
description:
@@ -249,10 +241,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"
path:
dependency: transitive
description:
@@ -321,17 +313,17 @@ packages:
dependency: transitive
description:
name: playx_core
- sha256: b49e07caeda91353f04f68d52c751fdec0513faf46646c74ef785ca258fd1683
+ sha256: "1d92e7d33d1132b2fabd779149b9a95b2dbf4b8b0f8f6ea28cad76950ef12f94"
url: "https://pub.dev"
source: hosted
- version: "0.7.4"
+ version: "1.0.0"
playx_localization:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
- version: "0.3.1"
+ version: "0.4.0"
plugin_platform_interface:
dependency: transitive
description:
@@ -344,10 +336,10 @@ packages:
dependency: transitive
description:
name: shared_preferences
- sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
+ sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
url: "https://pub.dev"
source: hosted
- version: "2.5.3"
+ version: "2.5.5"
shared_preferences_android:
dependency: transitive
description:
@@ -376,10 +368,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
- sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.dev"
source: hosted
- version: "2.4.1"
+ version: "2.4.2"
shared_preferences_web:
dependency: transitive
description:
@@ -445,10 +437,10 @@ packages:
dependency: transitive
description:
name: talker_logger
- sha256: "8218836d871ea5ab1ec616cffe3cdae84e8fb44022d5cc04c95d7b220572b8fb"
+ sha256: cea1b8283a28c2118a0b197057fc5beb5b0672c75e40a48725e5e452c0278ff3
url: "https://pub.dev"
source: hosted
- version: "5.0.2"
+ version: "5.1.16"
term_glyph:
dependency: transitive
description:
@@ -461,10 +453,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
+ sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
- version: "0.7.6"
+ version: "0.7.7"
vector_math:
dependency: transitive
description:
@@ -514,5 +506,5 @@ packages:
source: hosted
version: "1.1.0"
sdks:
- dart: ">=3.8.0 <4.0.0"
- flutter: ">=3.27.0"
+ dart: ">=3.9.0 <4.0.0"
+ flutter: ">=3.35.0"
diff --git a/lib/src/config/playx_locale_config.dart b/lib/src/config/playx_locale_config.dart
index bd17e4a..6e7ea97 100644
--- a/lib/src/config/playx_locale_config.dart
+++ b/lib/src/config/playx_locale_config.dart
@@ -2,6 +2,8 @@ import 'package:flutter/cupertino.dart';
import 'package:playx_localization/src/delegate/playx_localization_delegate.dart';
import '../../playx_localization.dart';
+import '../easy_localization/file_loaders/root_bundle_file_loader.dart';
+import '../easy_localization/linked_file_resolver.dart';
/// Locale config :
/// used to configure out app locales by providing the app with the supported locales and localization settings.
@@ -64,6 +66,24 @@ class PlayxLocaleConfig {
/// Additional custom delegates, e.g., from third-party packages.
final List? extraDelegates;
+ /// If a localization key is empty in the locale file, try to use the fallbackLocale file.
+ /// Does not take effect if [useFallbackTranslations] is false.
+ /// @Default value false
+ final bool useFallbackTranslationsForEmptyResources;
+
+ /// Ignore usage of plural strings for languages that do not use plural rules.
+ /// @Default value true
+ final bool ignorePluralRules;
+
+ /// Class loader for localization files that belong to other packages.
+ /// @Default value `null`
+ final List? extraAssetLoaders;
+
+ /// When true, all supported locales are preloaded into memory when the controller initializes.
+ /// This allows translating to arbitrary locales synchronously using `tr(locale: ...)` without
+ /// updating the app locale. Warning: Doing this might consume more memory if there are many locales.
+ final bool preloadSupportedLocales;
+
/// Custom localization delegate builder.
/// This allows you to create a custom list of delegates based on the provided [PlayxLocalizationDelegate].
final List Function(
@@ -75,8 +95,16 @@ class PlayxLocaleConfig {
this.fallbackLocale,
this.useOnlyLangCode = false,
this.useFallbackTranslations = true,
+ this.useFallbackTranslationsForEmptyResources = false,
+ this.ignorePluralRules = true,
+ this.preloadSupportedLocales = false,
this.path = 'assets/translations',
- this.assetLoader = const RootBundleAssetLoader(),
+ this.assetLoader = const RootBundleAssetLoader(
+ fileLoader: RootBundleFileLoader(),
+ linkedFileResolver:
+ JsonLinkedFileResolver(fileLoader: RootBundleFileLoader()),
+ ),
+ this.extraAssetLoaders,
this.saveLocale = true,
this.logMissingKeys = false,
this.logLocaleChanges = true,
diff --git a/lib/src/controller/controller.dart b/lib/src/controller/controller.dart
index e39036c..5932e7a 100644
--- a/lib/src/controller/controller.dart
+++ b/lib/src/controller/controller.dart
@@ -36,6 +36,7 @@ class PlayxLocaleController extends ValueNotifier {
late PlayxLocalizationDelegate delegate;
Translations? _translations, _fallbackTranslations;
+ Map? _preloadedTranslations;
/// current translations loaded from assets.
Translations? get translations => _translations;
@@ -43,12 +44,19 @@ class PlayxLocaleController extends ValueNotifier {
/// current fallback translations loaded from assets.
Translations? get fallbackTranslations => _fallbackTranslations;
+ /// all preloaded translations loaded from assets, if config.preloadSupportedLocales is true.
+ Map? get preloadedTranslations => _preloadedTranslations;
+
// Returns the device locale.
Locale? deviceLocale;
static PlayxBaseLogger? get logger =>
PlayxLogger.getLogger('Playx Localization');
+ /// Whether the app locale is actively synced to the device locale
+ bool get isDeviceLocaleSelected => _isDeviceLocaleSelected;
+ bool _isDeviceLocaleSelected = false;
+
/// current locale index
int get currentIndex {
if (value == null) {
@@ -72,9 +80,12 @@ class PlayxLocaleController extends ValueNotifier {
deviceLocale = foundPlatformLocale.toLocale();
logger.i('Device Locale ${deviceLocale?.toStringWithSeparator()}');
- XLocale? lastSavedLocale = config.supportedLocales.atOrNull(
- lastKnownIndex ?? -1,
- );
+ _isDeviceLocaleSelected = lastKnownIndex == -1;
+
+ XLocale? lastSavedLocale;
+ if (lastKnownIndex != null && lastKnownIndex >= 0) {
+ lastSavedLocale = config.supportedLocales.atOrNull(lastKnownIndex);
+ }
final locale = _getStartLocale(savedLocale: lastSavedLocale);
@@ -126,15 +137,13 @@ class PlayxLocaleController extends ValueNotifier {
XLocale _getStartLocale({XLocale? savedLocale}) {
if (savedLocale != null) return savedLocale;
- if (config.startLocale != null) return config.startLocale!;
+ if (!_isDeviceLocaleSelected && config.startLocale != null) return config.startLocale!;
if (deviceLocale != null) {
- final searchedLocaleByCountryCode = supportedXLocales.firstWhereOrNull(
- (e) =>
- e.languageCode == deviceLocale!.languageCode &&
- e.countryCode == deviceLocale!.countryCode);
- if (searchedLocaleByCountryCode != null) {
- return searchedLocaleByCountryCode;
+ final searchedLocale = supportedXLocales.firstWhereOrNull(
+ (e) => e.locale.supports(deviceLocale!));
+ if (searchedLocale != null) {
+ return searchedLocale;
}
final searchedLocaleByOnlyLanguageCode =
@@ -168,9 +177,17 @@ class PlayxLocaleController extends ValueNotifier {
);
_translations = res.translations;
_fallbackTranslations = res.fallbackTranslations;
-
- Localization.load(locale.locale,
- translations: translations, fallbackTranslations: fallbackTranslations);
+ _preloadedTranslations = res.preloadedTranslations;
+
+ Localization.load(
+ locale.locale,
+ translations: _translations,
+ fallbackTranslations: _fallbackTranslations,
+ preloadedTranslations: _preloadedTranslations,
+ useFallbackTranslationsForEmptyResources:
+ config.useFallbackTranslationsForEmptyResources,
+ ignorePluralRules: config.ignorePluralRules,
+ );
}
/// update the locale to be one of the supported locales.
@@ -234,13 +251,17 @@ class PlayxLocaleController extends ValueNotifier {
/// Search for locale by language code and country code if available.
XLocale? searchLocaleByLanguageCode(
- {required String languageCode, String? countryCode}) {
- final searchedLocaleByCountryCode = supportedXLocales.firstWhereOrNull(
- (e) => e.languageCode == languageCode && e.countryCode == countryCode);
- if (searchedLocaleByCountryCode != null) {
- return searchedLocaleByCountryCode;
+ {required String languageCode, String? countryCode, String? scriptCode}) {
+ final searchLocale = Locale.fromSubtags(
+ languageCode: languageCode,
+ countryCode: countryCode,
+ scriptCode: scriptCode);
+ final searchedLocale = supportedXLocales.firstWhereOrNull(
+ (e) => e.locale.supports(searchLocale));
+ if (searchedLocale != null) {
+ return searchedLocale;
}
- //if not found by country code then search by language code only.
+ //if not found then search by language code only.
final searchedLocaleByOnlyLanguageCode = supportedXLocales
.firstWhereOrNull((e) => e.languageCode == languageCode);
if (searchedLocaleByOnlyLanguageCode != null) {
@@ -255,9 +276,10 @@ class PlayxLocaleController extends ValueNotifier {
Future updateByLanguageCode(
{required String languageCode,
String? countryCode,
+ String? scriptCode,
bool forceAppUpdate = false}) async {
final locale = searchLocaleByLanguageCode(
- languageCode: languageCode, countryCode: countryCode);
+ languageCode: languageCode, countryCode: countryCode, scriptCode: scriptCode);
if (locale != null) {
return updateTo(
locale,
@@ -271,12 +293,30 @@ class PlayxLocaleController extends ValueNotifier {
/// if the locale is not supported it will return false.
/// if [forceAppUpdate] is true it will force the app to update.
Future updateToDeviceLocale({bool forceAppUpdate = false}) async {
- final locale = deviceLocale;
- if (locale == null) return false;
- return updateByLanguageCode(
+ final foundPlatformLocale = await findSystemLocale();
+ final locale = foundPlatformLocale.toLocale();
+ deviceLocale = locale;
+ final search = searchLocaleByLanguageCode(
languageCode: locale.languageCode,
countryCode: locale.countryCode,
- forceAppUpdate: forceAppUpdate);
+ scriptCode: locale.scriptCode);
+ if (search != null) {
+ return _updateLocale(
+ locale: search,
+ forceAppUpdate: forceAppUpdate,
+ saveAsDeviceLocale: true);
+ }
+ return false;
+ }
+
+ /// Reset locale to platform locale or fallback locale.
+ Future resetLocale({bool forceAppUpdate = false}) async {
+ final foundPlatformLocale = await findSystemLocale();
+ deviceLocale = foundPlatformLocale.toLocale();
+ final locale = _getStartLocale(savedLocale: null);
+
+ logger?.i('Reset locale to ${locale.name} while the platform locale is $deviceLocale');
+ await updateTo(locale, forceAppUpdate: forceAppUpdate);
}
/// Update the locale to be one of the supported locales.
@@ -284,6 +324,7 @@ class PlayxLocaleController extends ValueNotifier {
Future _updateLocale({
required XLocale locale,
bool forceAppUpdate = false,
+ bool saveAsDeviceLocale = false,
}) async {
try {
final index = supportedXLocales.indexOf(locale);
@@ -298,8 +339,11 @@ class PlayxLocaleController extends ValueNotifier {
locale,
);
if (config.saveLocale) {
- await PlayxAsyncPrefs.setInt(_lastKnownIndexKey, index);
+ final savedIndex = saveAsDeviceLocale ? -1 : index;
+ await PlayxAsyncPrefs.setInt(_lastKnownIndexKey, savedIndex);
}
+ _isDeviceLocaleSelected = saveAsDeviceLocale;
+
final oldLocale = value;
value = locale;
diff --git a/lib/src/controller/translation_manager.dart b/lib/src/controller/translation_manager.dart
index f6ab38f..5df53e3 100644
--- a/lib/src/controller/translation_manager.dart
+++ b/lib/src/controller/translation_manager.dart
@@ -12,6 +12,7 @@ class TranslationManager {
({
Translations? translations,
Translations? fallbackTranslations,
+ Map? preloadedTranslations,
})> loadTranslations({
required XLocale locale,
bool useFallbackTranslations = true,
@@ -19,7 +20,19 @@ class TranslationManager {
required XLocale fallbackLocale,
}) async {
Map data;
+ Map? preloadedTranslations;
+
try {
+ if (config.preloadSupportedLocales) {
+ preloadedTranslations = {};
+ for (final supportedXLocale in config.supportedLocales) {
+ final supportedData = await loadTranslationData(
+ locale: supportedXLocale, config: config);
+ preloadedTranslations[supportedXLocale.locale] =
+ Translations(Map.from(supportedData));
+ }
+ }
+
data =
Map.from(await loadTranslationData(locale: locale, config: config));
final translations = Translations(data);
@@ -41,15 +54,20 @@ class TranslationManager {
final fallbackTranslations = Translations(data);
return (
translations: translations,
- fallbackTranslations: fallbackTranslations
+ fallbackTranslations: fallbackTranslations,
+ preloadedTranslations: preloadedTranslations,
);
}
- return (translations: translations, fallbackTranslations: null);
+ return (
+ translations: translations,
+ fallbackTranslations: null,
+ preloadedTranslations: preloadedTranslations,
+ );
} on FlutterError catch (e, s) {
// onLoadError(e);
PlayxLocaleController.logger
?.error('Error loading translations: ', error: e, stackTrace: s);
- return (translations: null, fallbackTranslations: null);
+ return (translations: null, fallbackTranslations: null, preloadedTranslations: null);
} catch (e, s) {
PlayxLocaleController.logger
?.error('Error loading translations: ', error: e, stackTrace: s);
@@ -57,6 +75,7 @@ class TranslationManager {
return (
translations: null,
fallbackTranslations: null,
+ preloadedTranslations: null,
);
}
}
@@ -75,17 +94,45 @@ class TranslationManager {
static Future