222 lines
7.5 KiB
Dart
222 lines
7.5 KiB
Dart
import 'dart:io';
|
|
import 'dart:convert';
|
|
|
|
import '../../../print.dart';
|
|
|
|
abstract class LingoHunter {
|
|
/// Extracts translatable strings...
|
|
static Future<void> extractAndCreateTranslationFiles({
|
|
required String baseLang,
|
|
required List<String> langs,
|
|
String? projectDirectory,
|
|
String? outputDirectory,
|
|
bool translateBaseLang = true,
|
|
List<RegExp>? additionalRegExps,
|
|
bool overrideRegExps = false,
|
|
List<String> 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<String> 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<String> _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<Set<String>> extractStringsFromFlutterProject({
|
|
required String directory,
|
|
List<RegExp>? additionalRegExps,
|
|
bool overrideRegExps = false,
|
|
List<String> fileExtensions = const ['.dart'],
|
|
}) async {
|
|
// ... (rest of the function remains the same) ...
|
|
final List<RegExp> 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<RegExp> patterns;
|
|
if (overrideRegExps && additionalRegExps != null) {
|
|
patterns = additionalRegExps;
|
|
} else {
|
|
patterns = [...defaultPatterns];
|
|
if (additionalRegExps != null) {
|
|
patterns.addAll(additionalRegExps);
|
|
}
|
|
}
|
|
|
|
final Set<String> 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<FileSystemEntity> entities =
|
|
await projectDirObj.list(recursive: true).toList();
|
|
|
|
// Filter files by the specified extensions
|
|
final List<File> filteredFiles = entities
|
|
.whereType<File>()
|
|
.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<RegExpMatch> 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<void> _createTranslationFiles({
|
|
required Set<String> strings,
|
|
required String outputDirectory,
|
|
required String baseLang,
|
|
required List<String> 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<String, String> 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<String, String> langStrings = {
|
|
for (final string in strings) string: ""
|
|
};
|
|
await _writeTranslationFile(langFilePath, langStrings);
|
|
}
|
|
}
|
|
|
|
/// Writes a translation file (no changes needed)
|
|
static Future<void> _writeTranslationFile(
|
|
String filePath, Map<String, String> 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<String, String> 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());
|
|
}
|
|
}
|