From 854410e2756275d011773df8976ede9e75eba675 Mon Sep 17 00:00:00 2001 From: Lucas-Feichtinger Date: Thu, 28 Aug 2025 15:13:06 +0200 Subject: [PATCH 1/6] Update generate.dart extended the generator to support yaml localization files. --- bin/generate.dart | 187 +++++++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 78 deletions(-) diff --git a/bin/generate.dart b/bin/generate.dart index 7c5cba29..7dd7de2f 100644 --- a/bin/generate.dart +++ b/bin/generate.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; const _preservedKeywords = [ 'few', @@ -43,35 +44,45 @@ GenerateOptions _generateOption(List args) { ArgParser _generateArgParser(GenerateOptions? generateOptions) { var parser = ArgParser(); - parser.addOption('source-dir', - abbr: 'S', - defaultsTo: 'resources/langs', - callback: (String? x) => generateOptions!.sourceDir = x, - help: 'Folder containing localization files'); - - parser.addOption('source-file', - abbr: 's', - callback: (String? x) => generateOptions!.sourceFile = x, - help: 'File to use for localization'); - - parser.addOption('output-dir', - abbr: 'O', - defaultsTo: 'lib/generated', - callback: (String? x) => generateOptions!.outputDir = x, - help: 'Output folder stores for the generated file'); - - parser.addOption('output-file', - abbr: 'o', - defaultsTo: 'codegen_loader.g.dart', - callback: (String? x) => generateOptions!.outputFile = x, - help: 'Output file name'); - - parser.addOption('format', - abbr: 'f', - defaultsTo: 'json', - callback: (String? x) => generateOptions!.format = x, - help: 'Support json or keys formats', - allowed: ['json', 'keys']); + parser.addOption( + 'source-dir', + abbr: 'S', + defaultsTo: 'resources/langs', + callback: (String? x) => generateOptions!.sourceDir = x, + help: 'Folder containing localization files', + ); + + parser.addOption( + 'source-file', + abbr: 's', + callback: (String? x) => generateOptions!.sourceFile = x, + help: 'File to use for localization', + ); + + parser.addOption( + 'output-dir', + abbr: 'O', + defaultsTo: 'lib/generated', + callback: (String? x) => generateOptions!.outputDir = x, + help: 'Output folder stores for the generated file', + ); + + parser.addOption( + 'output-file', + abbr: 'o', + defaultsTo: 'codegen_loader.g.dart', + callback: (String? x) => generateOptions!.outputFile = x, + help: 'Output file name', + ); + + parser.addOption( + 'format', + abbr: 'f', + defaultsTo: 'json', + callback: (String? x) => generateOptions!.format = x, + help: 'Support json or keys formats', + allowed: ['json', 'keys'], + ); parser.addFlag( 'skip-unnecessary-keys', @@ -104,8 +115,9 @@ void handleLangFiles(GenerateOptions options) async { final source = Directory.fromUri(Uri.parse(options.sourceDir!)); final output = Directory.fromUri(Uri.parse(options.outputDir!)); final sourcePath = Directory(path.join(current.path, source.path)); - final outputPath = - Directory(path.join(current.path, output.path, options.outputFile)); + final outputPath = Directory( + path.join(current.path, output.path, options.outputFile), + ); if (!await sourcePath.exists()) { stderr.writeln('Source path does not exist'); @@ -121,8 +133,10 @@ void handleLangFiles(GenerateOptions options) async { } files = [sourceFile]; } else { - //filtering format - files = files.where((f) => f.path.contains('.json')).toList(); + // Filter for .yml and .yaml files + files = files + .where((f) => f.path.endsWith('.yml') || f.path.endsWith('.yaml')) + .toList(); } if (files.isNotEmpty) { @@ -136,13 +150,18 @@ Future> dirContents(Directory dir) { var files = []; var completer = Completer>(); var lister = dir.list(recursive: false); - lister.listen((file) => files.add(file), - onDone: () => completer.complete(files)); + lister.listen( + (file) => files.add(file), + onDone: () => completer.complete(files), + ); return completer.future; } -void generateFile(List files, Directory outputPath, - GenerateOptions options) async { +void generateFile( + List files, + Directory outputPath, + GenerateOptions options, +) async { var generatedFile = File(outputPath.path); if (!generatedFile.existsSync()) { generatedFile.createSync(recursive: true); @@ -157,9 +176,6 @@ void generateFile(List files, Directory outputPath, case 'keys': await _writeKeys(classBuilder, files, options.skipUnnecessaryKeys); break; - // case 'csv': - // await _writeCsv(classBuilder, files); - // break; default: stderr.writeln('Format not supported'); } @@ -170,28 +186,36 @@ void generateFile(List files, Directory outputPath, stdout.writeln('All done! File generated in ${outputPath.path}'); } -Future _writeKeys(StringBuffer classBuilder, List files, - bool? skipUnnecessaryKeys) async { +Future _writeKeys( + StringBuffer classBuilder, + List files, + bool? skipUnnecessaryKeys, +) async { var file = ''' // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart // ignore_for_file: constant_identifier_names -abstract class LocaleKeys { +abstract class LocaleKeys { '''; final fileData = File(files.first.path); - Map translations = - json.decode(await fileData.readAsString()); + // Parse YAML file + final yamlString = await fileData.readAsString(); + final yamlData = loadYaml(yamlString); + Map translations = _yamlToMap(yamlData); file += _resolve(translations, skipUnnecessaryKeys); classBuilder.writeln(file); } -String _resolve(Map translations, bool? skipUnnecessaryKeys, - [String? accKey]) { +String _resolve( + Map translations, + bool? skipUnnecessaryKeys, [ + String? accKey, +]) { var fileContent = ''; final sortedKeys = translations.keys.toList(); @@ -205,8 +229,10 @@ String _resolve(Map translations, bool? skipUnnecessaryKeys, var ignoreKey = false; if (translations[key] is Map) { // If key does not contain keys for plural(), gender() etc. and option is enabled -> ignore it - ignoreKey = !containsPreservedKeywords( - translations[key] as Map) && + ignoreKey = + !containsPreservedKeywords( + translations[key] as Map, + ) && canIgnoreKeys; var nextAccKey = key; @@ -214,17 +240,20 @@ String _resolve(Map translations, bool? skipUnnecessaryKeys, nextAccKey = '$accKey.$key'; } - fileContent += - _resolve(translations[key], skipUnnecessaryKeys, nextAccKey); + fileContent += _resolve( + translations[key], + skipUnnecessaryKeys, + nextAccKey, + ); } if (!_preservedKeywords.contains(key)) { accKey != null && !ignoreKey ? fileContent += - ' static const ${accKey.replaceAll('.', '_')}_$key = \'$accKey.$key\';\n' + ' static const ${accKey.replaceAll('.', '_')}_$key = \'$accKey.$key\';\n' : !ignoreKey - ? fileContent += ' static const $key = \'$key\';\n' - : null; + ? fileContent += ' static const $key = \'$key\';\n' + : null; } } @@ -232,7 +261,9 @@ String _resolve(Map translations, bool? skipUnnecessaryKeys, } Future _writeJson( - StringBuffer classBuilder, List files) async { + StringBuffer classBuilder, + List files, +) async { var gFile = ''' // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart @@ -242,7 +273,7 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart' show AssetLoader; -class CodegenLoader extends AssetLoader{ +class CodegenLoader extends AssetLoader { const CodegenLoader(); @override @@ -255,12 +286,17 @@ class CodegenLoader extends AssetLoader{ final listLocales = []; for (var file in files) { - final localeName = - path.basename(file.path).replaceFirst('.json', '').replaceAll('-', '_'); + final localeName = path + .basename(file.path) + .replaceFirst(RegExp(r'\.ya?ml$'), '') + .replaceAll('-', '_'); listLocales.add('"$localeName": _$localeName'); final fileData = File(file.path); - Map? data = json.decode(await fileData.readAsString()); + // Parse YAML file + final yamlString = await fileData.readAsString(); + final yamlData = loadYaml(yamlString); + Map data = _yamlToMap(yamlData); final mapString = const JsonEncoder.withIndent(' ').convert(data); gFile += 'static const Map _$localeName = $mapString;\n'; @@ -271,22 +307,17 @@ class CodegenLoader extends AssetLoader{ classBuilder.writeln(gFile); } -// _writeCsv(StringBuffer classBuilder, List files) async { -// List listLocales = List(); -// final fileData = File(files.first.path); - -// // CSVParser csvParser = CSVParser(await fileData.readAsString()); - -// // List listLangs = csvParser.getLanguages(); -// for(String localeName in listLangs){ -// listLocales.add('"$localeName": $localeName'); -// String mapString = JsonEncoder.withIndent(" ").convert(csvParser.getLanguageMap(localeName)) ; - -// classBuilder.writeln( -// ' static const Map $localeName = ${mapString};\n'); -// } - -// classBuilder.writeln( -// ' static const Map> mapLocales = \{${listLocales.join(', ')}\};'); - -// } +// Helper function to convert YamlMap to Map +Map _yamlToMap(dynamic yaml) { + if (yaml is YamlMap) { + return yaml.map((key, value) { + if (value is YamlMap || value is YamlList) { + return MapEntry(key.toString(), _yamlToMap(value)); + } + return MapEntry(key.toString(), value); + }); + } else if (yaml is YamlList) { + return {'': yaml.map((e) => _yamlToMap(e)).toList()}; + } + return {}; +} From cfa45f1f8aace5b03aab634a1393dace1bcfc4e9 Mon Sep 17 00:00:00 2001 From: Lucas-Feichtinger Date: Thu, 28 Aug 2025 15:13:41 +0200 Subject: [PATCH 2/6] Update pubspec.yaml added yaml package to dependencies --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index beed69fe..6add1df2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: intl: '>=0.17.0-0 <0.21.0' args: ^2.3.1 path: ^1.8.1 + yaml: ^3.1.2 easy_logger: ^0.0.2 flutter_localizations: sdk: flutter From 75053687db651f126e4e80b675a75fefaf49fb22 Mon Sep 17 00:00:00 2001 From: Lucas-Feichtinger Date: Thu, 28 Aug 2025 15:14:59 +0200 Subject: [PATCH 3/6] Update CHANGELOG.md added change description --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf74e56..ba9a4262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog + +### [3.0.9] + +- added .yaml support for transaltion files in the :generate script + ### [3.0.8] - code audit and maintenance updates From a5645ac2f9d0c65479cfb9afbf2b1508194c2c80 Mon Sep 17 00:00:00 2001 From: Lucas-Feichtinger Date: Thu, 28 Aug 2025 15:36:28 +0200 Subject: [PATCH 4/6] Update generate.dart adapted the changes a bit --- bin/generate.dart | 203 ++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 106 deletions(-) diff --git a/bin/generate.dart b/bin/generate.dart index 7dd7de2f..b87afff8 100644 --- a/bin/generate.dart +++ b/bin/generate.dart @@ -44,45 +44,35 @@ GenerateOptions _generateOption(List args) { ArgParser _generateArgParser(GenerateOptions? generateOptions) { var parser = ArgParser(); - parser.addOption( - 'source-dir', - abbr: 'S', - defaultsTo: 'resources/langs', - callback: (String? x) => generateOptions!.sourceDir = x, - help: 'Folder containing localization files', - ); - - parser.addOption( - 'source-file', - abbr: 's', - callback: (String? x) => generateOptions!.sourceFile = x, - help: 'File to use for localization', - ); - - parser.addOption( - 'output-dir', - abbr: 'O', - defaultsTo: 'lib/generated', - callback: (String? x) => generateOptions!.outputDir = x, - help: 'Output folder stores for the generated file', - ); - - parser.addOption( - 'output-file', - abbr: 'o', - defaultsTo: 'codegen_loader.g.dart', - callback: (String? x) => generateOptions!.outputFile = x, - help: 'Output file name', - ); - - parser.addOption( - 'format', - abbr: 'f', - defaultsTo: 'json', - callback: (String? x) => generateOptions!.format = x, - help: 'Support json or keys formats', - allowed: ['json', 'keys'], - ); + parser.addOption('source-dir', + abbr: 'S', + defaultsTo: 'resources/langs', + callback: (String? x) => generateOptions!.sourceDir = x, + help: 'Folder containing localization files'); + + parser.addOption('source-file', + abbr: 's', + callback: (String? x) => generateOptions!.sourceFile = x, + help: 'File to use for localization'); + + parser.addOption('output-dir', + abbr: 'O', + defaultsTo: 'lib/generated', + callback: (String? x) => generateOptions!.outputDir = x, + help: 'Output folder stores for the generated file'); + + parser.addOption('output-file', + abbr: 'o', + defaultsTo: 'codegen_loader.g.dart', + callback: (String? x) => generateOptions!.outputFile = x, + help: 'Output file name'); + + parser.addOption('format', + abbr: 'f', + defaultsTo: 'json', + callback: (String? x) => generateOptions!.format = x, + help: 'Support json, yaml, or keys formats', + allowed: ['json', 'yaml', 'keys']); parser.addFlag( 'skip-unnecessary-keys', @@ -115,9 +105,8 @@ void handleLangFiles(GenerateOptions options) async { final source = Directory.fromUri(Uri.parse(options.sourceDir!)); final output = Directory.fromUri(Uri.parse(options.outputDir!)); final sourcePath = Directory(path.join(current.path, source.path)); - final outputPath = Directory( - path.join(current.path, output.path, options.outputFile), - ); + final outputPath = + Directory(path.join(current.path, output.path, options.outputFile)); if (!await sourcePath.exists()) { stderr.writeln('Source path does not exist'); @@ -133,10 +122,8 @@ void handleLangFiles(GenerateOptions options) async { } files = [sourceFile]; } else { - // Filter for .yml and .yaml files - files = files - .where((f) => f.path.endsWith('.yml') || f.path.endsWith('.yaml')) - .toList(); + //filtering format + files = files.where((f) => f.path.contains(RegExp(r'\.(json|yaml|yml)$'))).toList(); } if (files.isNotEmpty) { @@ -150,18 +137,13 @@ Future> dirContents(Directory dir) { var files = []; var completer = Completer>(); var lister = dir.list(recursive: false); - lister.listen( - (file) => files.add(file), - onDone: () => completer.complete(files), - ); + lister.listen((file) => files.add(file), + onDone: () => completer.complete(files)); return completer.future; } -void generateFile( - List files, - Directory outputPath, - GenerateOptions options, -) async { +void generateFile(List files, Directory outputPath, + GenerateOptions options) async { var generatedFile = File(outputPath.path); if (!generatedFile.existsSync()) { generatedFile.createSync(recursive: true); @@ -173,6 +155,9 @@ void generateFile( case 'json': await _writeJson(classBuilder, files); break; + case 'yaml': + await _writeYaml(classBuilder, files); + break; case 'keys': await _writeKeys(classBuilder, files, options.skipUnnecessaryKeys); break; @@ -186,36 +171,28 @@ void generateFile( stdout.writeln('All done! File generated in ${outputPath.path}'); } -Future _writeKeys( - StringBuffer classBuilder, - List files, - bool? skipUnnecessaryKeys, -) async { +Future _writeKeys(StringBuffer classBuilder, List files, + bool? skipUnnecessaryKeys) async { var file = ''' // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart // ignore_for_file: constant_identifier_names -abstract class LocaleKeys { +abstract class LocaleKeys { '''; final fileData = File(files.first.path); - // Parse YAML file - final yamlString = await fileData.readAsString(); - final yamlData = loadYaml(yamlString); - Map translations = _yamlToMap(yamlData); + Map translations = + json.decode(json.encode(loadYaml(await fileData.readAsString()))); file += _resolve(translations, skipUnnecessaryKeys); classBuilder.writeln(file); } -String _resolve( - Map translations, - bool? skipUnnecessaryKeys, [ - String? accKey, -]) { +String _resolve(Map translations, bool? skipUnnecessaryKeys, + [String? accKey]) { var fileContent = ''; final sortedKeys = translations.keys.toList(); @@ -229,10 +206,8 @@ String _resolve( var ignoreKey = false; if (translations[key] is Map) { // If key does not contain keys for plural(), gender() etc. and option is enabled -> ignore it - ignoreKey = - !containsPreservedKeywords( - translations[key] as Map, - ) && + ignoreKey = !containsPreservedKeywords( + translations[key] as Map) && canIgnoreKeys; var nextAccKey = key; @@ -240,20 +215,17 @@ String _resolve( nextAccKey = '$accKey.$key'; } - fileContent += _resolve( - translations[key], - skipUnnecessaryKeys, - nextAccKey, - ); + fileContent += + _resolve(translations[key], skipUnnecessaryKeys, nextAccKey); } if (!_preservedKeywords.contains(key)) { accKey != null && !ignoreKey ? fileContent += - ' static const ${accKey.replaceAll('.', '_')}_$key = \'$accKey.$key\';\n' + ' static const ${accKey.replaceAll('.', '_')}_$key = \'$accKey.$key\';\n' : !ignoreKey - ? fileContent += ' static const $key = \'$key\';\n' - : null; + ? fileContent += ' static const $key = \'$key\';\n' + : null; } } @@ -261,9 +233,7 @@ String _resolve( } Future _writeJson( - StringBuffer classBuilder, - List files, -) async { + StringBuffer classBuilder, List files) async { var gFile = ''' // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart @@ -273,7 +243,7 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart' show AssetLoader; -class CodegenLoader extends AssetLoader { +class CodegenLoader extends AssetLoader{ const CodegenLoader(); @override @@ -286,17 +256,12 @@ class CodegenLoader extends AssetLoader { final listLocales = []; for (var file in files) { - final localeName = path - .basename(file.path) - .replaceFirst(RegExp(r'\.ya?ml$'), '') - .replaceAll('-', '_'); + final localeName = + path.basename(file.path).replaceFirst('.json', '').replaceAll('-', '_'); listLocales.add('"$localeName": _$localeName'); final fileData = File(file.path); - // Parse YAML file - final yamlString = await fileData.readAsString(); - final yamlData = loadYaml(yamlString); - Map data = _yamlToMap(yamlData); + Map? data = json.decode(await fileData.readAsString()); final mapString = const JsonEncoder.withIndent(' ').convert(data); gFile += 'static const Map _$localeName = $mapString;\n'; @@ -307,17 +272,43 @@ class CodegenLoader extends AssetLoader { classBuilder.writeln(gFile); } -// Helper function to convert YamlMap to Map -Map _yamlToMap(dynamic yaml) { - if (yaml is YamlMap) { - return yaml.map((key, value) { - if (value is YamlMap || value is YamlList) { - return MapEntry(key.toString(), _yamlToMap(value)); - } - return MapEntry(key.toString(), value); - }); - } else if (yaml is YamlList) { - return {'': yaml.map((e) => _yamlToMap(e)).toList()}; +Future _writeYaml( + StringBuffer classBuilder, List files) async { + var gFile = ''' +// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart + +// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters, constant_identifier_names + +import 'dart:ui'; + +import 'package:easy_localization/easy_localization.dart' show AssetLoader; + +class CodegenLoader extends AssetLoader{ + const CodegenLoader(); + + @override + Future?> load(String path, Locale locale) { + return Future.value(mapLocales[locale.toString()]); + } + + '''; + + final listLocales = []; + + for (var file in files) { + final localeName = path + .basename(file.path) + .replaceFirst(RegExp(r'\.(yaml|yml)'), '') + .replaceAll('-', '_'); + listLocales.add('"$localeName": _$localeName'); + final fileData = File(file.path); + + var data = loadYaml(await fileData.readAsString()); + final mapString = const JsonEncoder.withIndent(' ').convert(data); + gFile += 'static const Map _$localeName = $mapString;\n'; } - return {}; + + gFile += + 'static const Map> mapLocales = {${listLocales.join(', ')}};'; + classBuilder.writeln(gFile); } From b82218df1e2e0c599026bd1818a291846ee5c7e0 Mon Sep 17 00:00:00 2001 From: Lucas-Feichtinger Date: Thu, 28 Aug 2025 15:41:17 +0200 Subject: [PATCH 5/6] Update generate.dart added comments back in --- bin/generate.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/bin/generate.dart b/bin/generate.dart index b87afff8..e15bb7e8 100644 --- a/bin/generate.dart +++ b/bin/generate.dart @@ -161,6 +161,9 @@ void generateFile(List files, Directory outputPath, case 'keys': await _writeKeys(classBuilder, files, options.skipUnnecessaryKeys); break; + // case 'csv': + // await _writeCsv(classBuilder, files); + // break; default: stderr.writeln('Format not supported'); } @@ -312,3 +315,23 @@ class CodegenLoader extends AssetLoader{ 'static const Map> mapLocales = {${listLocales.join(', ')}};'; classBuilder.writeln(gFile); } + +// _writeCsv(StringBuffer classBuilder, List files) async { +// List listLocales = List(); +// final fileData = File(files.first.path); + +// // CSVParser csvParser = CSVParser(await fileData.readAsString()); + +// // List listLangs = csvParser.getLanguages(); +// for(String localeName in listLangs){ +// listLocales.add('"$localeName": $localeName'); +// String mapString = JsonEncoder.withIndent(" ").convert(csvParser.getLanguageMap(localeName)) ; + +// classBuilder.writeln( +// ' static const Map $localeName = ${mapString};\n'); +// } + +// classBuilder.writeln( +// ' static const Map> mapLocales = \{${listLocales.join(', ')}\};'); + +// } From a1076080d35adfe3e4689e5d596e4281e6023db7 Mon Sep 17 00:00:00 2001 From: Lucas-Feichtinger Date: Thu, 28 Aug 2025 15:43:52 +0200 Subject: [PATCH 6/6] Update CHANGELOG.md Fix Typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba9a4262..252dd335 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### [3.0.9] -- added .yaml support for transaltion files in the :generate script +- added .yaml support for translation files in the :generate script ### [3.0.8]