import 'dart:io'; import 'dart:convert'; import '../../../print.dart'; abstract class LingoHunter { /// Extracts translatable strings... static Future extractAndCreateTranslationFiles({ required String baseLang, required List langs, String? projectDirectory, String? outputDirectory, bool translateBaseLang = true, List? additionalRegExps, bool overrideRegExps = false, List fileExtensions = const ['.dart'], }) async { // 1. Find project root (improved logic) String projectRoot; if (projectDirectory != null) { // Use provided directory, but check if it's valid if (!await Directory(projectDirectory).exists()) { throw ArgumentError( "The provided projectDirectory '$projectDirectory' does not exist."); } projectRoot = projectDirectory; } else { projectRoot = await _findProjectRoot(); // Use the corrected function } // 2. Use the project root as output directory if not specified final String outputDir = outputDirectory ?? projectRoot; // 3. Output Directory Verification (Create if necessary) final Directory outputDirObj = Directory(outputDir); if (!await outputDirObj.exists()) { try { await outputDirObj.create(recursive: true); } catch (e) { throw Exception( "Failed to create output directory: $outputDir. Error: $e"); } } print("Project root directory: $projectRoot"); print("Output directory: $outputDir"); // 4. Extract translatable strings final Set strings = await extractStringsFromFlutterProject( directory: projectRoot, // Use the validated projectRoot additionalRegExps: additionalRegExps, overrideRegExps: overrideRegExps, fileExtensions: fileExtensions, ); // 5. Generate translation files await _createTranslationFiles( strings: strings, outputDirectory: outputDir, baseLang: baseLang, langs: langs, translateBaseLang: translateBaseLang, ); print("Successfully extracted strings and generated translation files."); } /// Finds the project's root directory (corrected) static Future _findProjectRoot() async { Directory current = Directory.current; int maxIterations = 10; // Prevent infinite loop int count = 0; while (count < maxIterations) { if (await File('${current.path}/pubspec.yaml').exists()) { return current.path; // Return the directory *containing* pubspec.yaml } if (current.path == current.parent.path) { break; // Reached root, stop searching } current = current.parent; count++; } // If `pubspec.yaml` was not found, throw an exception throw Exception( "`pubspec.yaml` not found in the current directory or its parents."); } /// Extracts translatable strings (no changes needed here) static Future> extractStringsFromFlutterProject({ required String directory, List? additionalRegExps, bool overrideRegExps = false, List fileExtensions = const ['.dart'], }) async { // ... (rest of the function remains the same) ... final List defaultPatterns = [ RegExp(r'"([^"]+)"\.tr\(\)'), // "string".tr() RegExp(r"'([^']+)'\.tr\(\)"), // 'string'.tr() RegExp(r'"([^"]+)"\.tr'), // "string".tr RegExp(r"'([^']+)'\.tr"), // 'string'.tr RegExp(r'"([^"]+)"\.tr\(\w+\)'), // "string".tr(context) RegExp(r"'([^']+)'\.tr\(\w+\)"), // 'string'.tr(context) RegExp(r'context\.tr\("([^"]+)"\)'), // context.tr("string") RegExp(r"context\.tr\('([^']+)'\)"), // context.tr('string') RegExp(r'tr\(\w+, "([^"]+)"\)'), // tr(context, "string") RegExp(r"tr\(\w+, '([^']+)'\)"), // tr(context, 'string') RegExp(r'tr\("([^"]+)"\)'), // tr("string") RegExp(r"tr\('([^']+)'\)"), // tr('string') RegExp(r'"([^"]+)"\.tr\(args: \[.*?\]\)'), // "string".tr(args: []) RegExp(r'"([^"]+)"\.plural\(\d+\)'), // "string".plural(3) //Intl Package Patterns RegExp(r'AppLocalizations\.of\(context\)!\.translate\("([^"]+)"\)'), ]; // Determine the patterns to use List patterns; if (overrideRegExps && additionalRegExps != null) { patterns = additionalRegExps; } else { patterns = [...defaultPatterns]; if (additionalRegExps != null) { patterns.addAll(additionalRegExps); } } final Set strings = {}; final Directory projectDirObj = Directory(directory); // Check if the directory exists *before* listing if (!await projectDirObj.exists()) { throw ArgumentError("The directory '$directory' does not exist."); } final List entities = await projectDirObj.list(recursive: true).toList(); // Filter files by the specified extensions final List filteredFiles = entities .whereType() .where((file) => fileExtensions.any((ext) => file.path.endsWith(ext))) .toList(); // Extract strings from files for (final File file in filteredFiles) { final String content = await file.readAsString(); for (final RegExp pattern in patterns) { final Iterable matches = pattern.allMatches(content); for (final RegExpMatch match in matches) { if (match.groupCount >= 1 && match.group(1) != null) { strings.add(match.group(1)!); } } } } return strings; } /// Creates translation files (no changes needed) static Future _createTranslationFiles({ required Set strings, required String outputDirectory, required String baseLang, required List langs, bool translateBaseLang = true, }) async { // ... (rest of the function remains the same) ... final Directory outputDir = Directory(outputDirectory); if (!await outputDir.exists()) { Log.print('outputDir: ${outputDir}'); await outputDir.create(recursive: true); } // Always create the base language file, even if `translateBaseLang` is false final String baseFilePath = '$outputDirectory/translations_$baseLang.json'; final Map baseStrings = { for (final string in strings) string: translateBaseLang ? string : "" }; await _writeTranslationFile(baseFilePath, baseStrings); for (final String lang in langs) { final String langFilePath = '$outputDirectory/translations_$lang.json'; final Map langStrings = { for (final string in strings) string: "" }; await _writeTranslationFile(langFilePath, langStrings); } } /// Writes a translation file (no changes needed) static Future _writeTranslationFile( String filePath, Map strings) async { // ... (rest of the function remains the same) ... final File file = File(filePath); final StringBuffer content = StringBuffer(); content.writeln('{'); int index = 0; for (final MapEntry entry in strings.entries) { final String comma = (index < strings.length - 1) ? ',' : ''; final String key = jsonEncode(entry.key).substring(1, jsonEncode(entry.key).length - 1); final String value = entry.value.isEmpty ? "" : jsonEncode(entry.value) .substring(1, jsonEncode(entry.value).length - 1); content.writeln(' "$key": "$value"$comma'); index++; } content.writeln('}'); await file.writeAsString(content.toString()); } }