import "dart:collection"; import 'dart:io'; import 'dart:convert'; import 'localization/localization_constants.dart'; import 'utils/utils.dart'; const inputPath = 'res/values/'; const outputPath = 'lib/generated/'; const localizationFileName = 'i18n.dart'; const localeListFileName = 'locales.dart'; const srcDir = 'srcDir'; const defaultLocale = 'en'; Future main(List args) async { final extraInfo = args.isNotEmpty ? args.fold({}, (Map acc, String arg) { final parts = arg.split('='); var key = normalizeKeyName(parts[0]); if (key.contains('--')) { key = key.substring(2); } acc[key] = parts.length > 1 ? parts[1].isNotEmpty ? parts[1] : inputPath : inputPath; return acc; }) : {srcDir: inputPath}; final outputDir = Directory(outputPath); if (!outputDir.existsSync()) { await outputDir.create(); } extraInfo.forEach((key, dynamic value) async { if (key != srcDir) { print('Wrong key: $key'); return; } final dirPath = value as String; final dir = Directory(dirPath); if (!await dir.exists()) { print('Wrong directory path: $dirPath'); return; } final localePath = SplayTreeMap(); await dir.list(recursive: false).forEach((element) { // Parse the locale from the file name (e.g. strings_pt_br.arb -> pt_BR), // normalizing the case so keys match LanguageService.supportedLocales. final fileName = element.uri.pathSegments.last; if (!fileName.startsWith('strings_') || !fileName.endsWith('.arb')) { print('Wrong file: ${element.path}'); return; } final parts = fileName.substring('strings_'.length, fileName.length - '.arb'.length).split('_'); final locale = parts.length > 1 ? '${parts.first.toLowerCase()}_${parts.sublist(1).join('_').toUpperCase()}' : parts.first.toLowerCase(); localePath[locale] = element.path; }); if (!localePath.keys.contains(defaultLocale)) { print("Locale list doesn't contain $defaultLocale"); return; } try { var output = ''; var locales = 'const locales = ['; output += part1; output += textDirectionDeclaration; var inputContent = File(localePath[defaultLocale].toString()).readAsStringSync(); var config = json.decode(inputContent) as Map; output += localizedStrings(config: config, hasOverride: false); output += '}' + '\n\n'; localePath.forEach((key, dynamic value) { inputContent = File(localePath[key].toString()).readAsStringSync(); config = json.decode(inputContent) as Map; locales += "'$key', "; output += 'class \$$key extends S {' + '\n'; output += ' const \$$key();' + '\n'; if (key != defaultLocale) { output += textDirectionDeclaration; output += localizedStrings(config: config, hasOverride: true); } output += '}' + '\n\n'; }); output += classDeclaration; // Flutter matches on the language subtag, so pt_BR is emitted as Locale("pt", "BR") localePath.keys.forEach((key) { final parts = key.split("_"); if (parts.length == 2) { output += ' Locale("${parts[0]}", "${parts[1]}"),\n'; } else { output += ' Locale("$key", ""),\n'; } }); output += part2; localePath.keys.forEach((key) { output += ' case "$key":' + '\n'; output += ' S.current = const \$$key();' + '\n'; output += ' return SynchronousFuture(S.current);' + '\n'; }); output += part3; await File(outputPath + localizationFileName).writeAsString(output); locales += '];'; await File(outputPath + localeListFileName).writeAsString(locales); } catch (e) { print(e.toString()); } }); } String localizedStrings({required Map config, required bool hasOverride}) { var output = ''; final pattern = RegExp('[\$]{(.*?)}'); config.forEach((key, dynamic value) { final matches = pattern.allMatches(value as String); if (hasOverride) { output += ' @override' + '\n'; } if (matches.isEmpty) { output += ' String get ${key} => \"\"\"${value}\"\"\";' + '\n'; } else { final set = matches.map((elem) => elem.group(1)).toSet().toList(); output += ' String ${key}('; for (var elem in set) { if (elem == set.last) { output += 'String ${elem}'; } else { output += 'String ${elem}, '; } } output += ') => \"\"\"${value}\"\"\";' + '\n'; } }); return output; }