diff --git a/lib/localize_and_translate.dart b/lib/localize_and_translate.dart index b3584ef..d27ec4a 100644 --- a/lib/localize_and_translate.dart +++ b/lib/localize_and_translate.dart @@ -1,3 +1,4 @@ +export 'package:localize_and_translate/src/assets/asset_loader_network.dart'; export 'package:localize_and_translate/src/assets/asset_loader_root_bundle_json.dart'; export 'package:localize_and_translate/src/constants/enums.dart'; export 'package:localize_and_translate/src/core/localize_and_translate.dart'; diff --git a/lib/src/assets/asset_loader_network.dart b/lib/src/assets/asset_loader_network.dart new file mode 100644 index 0000000..6a5b391 --- /dev/null +++ b/lib/src/assets/asset_loader_network.dart @@ -0,0 +1,67 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:localize_and_translate/src/assets/asset_loader_base.dart'; +import 'package:localize_and_translate/src/constants/db_keys.dart'; +import 'package:localize_and_translate/src/db/usecases.dart'; +import 'package:localize_and_translate/src/models/m_localization.dart'; + +/// [AssetLoaderNetwork] is the asset loader for root bundle. +/// It loads the assets from the root bundle. +class AssetLoaderNetwork implements AssetLoaderBase { + /// [AssetLoaderNetwork] constructor + /// [endPoint] is the path of the json endPoint + const AssetLoaderNetwork(this.endPoint); + + /// [endPoint] is the path of the json endPoint + final String endPoint; + + @override + Future> load() async { + final Dio dio = Dio( + BaseOptions( + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + sendTimeout: const Duration(seconds: 30), + headers: const { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + receiveDataWhenStatusError: true, + ), + ); + + final List data = DBUseCases.localesFromDBString(DBUseCases.read(DBKeys.locales)); + final Map result = {}; + + for (final Locale locale in data) { + final Response response = await dio.get( + endPoint, + queryParameters: { + 'lang': locale.languageCode, + }, + ); + + MLocalization mLocalization = MLocalization(); + + if (response.data is Map) { + mLocalization = MLocalization.fromJson(response.data as Map); + } + + final Map newValues = {}; + final Map flatten = mLocalization.flattenLocalization(); + + for (final String key in flatten.keys) { + newValues[DBKeys.buildPrefix( + key: key, + languageCode: locale.languageCode, + countryCode: locale.countryCode, + )] = flatten[key]; + } + + result.addAll(newValues); + } + + debugPrint('--LocalizeAndTranslate-- Translated Strings: ${result.length}'); + return result; + } +} diff --git a/lib/src/assets/asset_loader_root_bundle_json.dart b/lib/src/assets/asset_loader_root_bundle_json.dart index d633f17..7dcb43a 100644 --- a/lib/src/assets/asset_loader_root_bundle_json.dart +++ b/lib/src/assets/asset_loader_root_bundle_json.dart @@ -17,8 +17,7 @@ class AssetLoaderRootBundleJson implements AssetLoaderBase { @override Future> load() async { - final AssetManifest assetManifest = - await AssetManifest.loadFromAssetBundle(rootBundle); + final AssetManifest assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle); final Iterable paths = assetManifest.listAssets().where( (String element) => element.contains(directory), ); @@ -33,9 +32,7 @@ class AssetLoaderRootBundleJson implements AssetLoaderBase { if (fileNameNoExtension.contains('-')) { languageCode = fileNameNoExtension.split('-').first; - countryCode = fileNameNoExtension.split('-').length > 2 - ? fileNameNoExtension.split('-').elementAt(1) - : null; + countryCode = fileNameNoExtension.split('-').length > 2 ? fileNameNoExtension.split('-').elementAt(1) : null; } else { languageCode = fileNameNoExtension; } diff --git a/lib/src/constants/db_keys.dart b/lib/src/constants/db_keys.dart index d318e58..713f6b2 100644 --- a/lib/src/constants/db_keys.dart +++ b/lib/src/constants/db_keys.dart @@ -36,6 +36,9 @@ class DBKeys { required String languageCode, required String? countryCode, }) { + if (countryCode == 'null') { + countryCode = ''; + } return 'tr__${languageCode}_${countryCode ?? ''}_$key'; } } diff --git a/lib/src/models/m_localization.dart b/lib/src/models/m_localization.dart new file mode 100644 index 0000000..83d4e02 --- /dev/null +++ b/lib/src/models/m_localization.dart @@ -0,0 +1,83 @@ +/// [MLocalization] is a model class that represents the localization data. +class MLocalization { + /// [MLocalization] constructor + MLocalization({this.screens}); + + /// [fromJson] is a factory method that creates a [MLocalization] instance from a JSON object. + factory MLocalization.fromJson(Map json) { + final Map screens = {}; + json.forEach((String key, dynamic value) { + if (key != 'version') { + screens[key] = MLocalizationNode.fromJson(value as Map); + } + }); + return MLocalization( + screens: screens, + ); + } + + /// [flattenLocalization] is a method that flattens the localization data. + Map flattenLocalization() { + final Map flatMap = {}; + + void flatten(Map? nodes, String parentKey) { + if (nodes == null) { + return; + } + + nodes.forEach((String key, MLocalizationNode node) { + final String currentKey = parentKey.isEmpty ? key : '$parentKey.$key'; + + if (node.title != null) { + // If it's a leaf, add it to the flat map + flatMap[currentKey] = node.title!; + } else if (node.children != null) { + // If it has children, continue flattening + flatten(node.children, currentKey); + } + }); + } + + flatten(screens, ''); + return flatMap; + } + + /// [screens] is a map of screen names to their respective [MLocalizationNode]. + final Map? screens; +} + +/// [MLocalizationNode] is a model class that represents a node in the localization data. +class MLocalizationNode { + /// [MLocalizationNode] constructor + MLocalizationNode({this.title, this.children}); + + /// [fromJson] is a factory method that creates a [MLocalizationNode] instance from a JSON object. + factory MLocalizationNode.fromJson(Map json) { + // Check if the current JSON object has no nested structure + final bool isLeafNode = json.values.every((dynamic value) => value is String || value is! Map); + + if (isLeafNode) { + // If all values are non-map types, treat this as a leaf node + return MLocalizationNode(title: json['title'] as String?); + } + + // Otherwise, recursively parse children + final Map children = {}; + json.forEach((String key, dynamic value) { + if (value is Map) { + children[key] = MLocalizationNode.fromJson(value); + } + }); + + return MLocalizationNode(children: children); + } + + /// [isLeaf] is a boolean that indicates if this node is a leaf node. + bool get isLeaf => title != null; + + /// [title] is the localized string for this node. + final String? title; + + /// [children] is a map of child nodes. + final Map? children; +} diff --git a/pubspec.yaml b/pubspec.yaml index a6a564c..0be9462 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: hive: ^2.2.3 hive_flutter: ^1.1.0 intl: ^0.18.1 + dio: ^5.7.0 dev_dependencies: flutter_test: