Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env dart
- // ignore_for_file: avoid_print
- // find unused i18n strings
- import 'dart:convert';
- import 'dart:io';
- import 'package:path/path.dart' as p;
- void main(List<String> args) {
- final rootDir = findFlutterRoot();
- if (rootDir == null) {
- print('โ Could not find a Flutter project root (no pubspec.yaml found).');
- exit(2);
- }
- final arbPath = args.isNotEmpty
- ? args[0]
- : p.join(rootDir.path, 'lib', 'l10n', 'app_en.arb');
- final sourceDir = args.length > 1 ? args[1] : p.join(rootDir.path, 'lib');
- final arbFile = File(arbPath);
- if (!arbFile.existsSync()) {
- print('โ ARB file not found: $arbPath');
- exit(2);
- }
- final arbContent =
- jsonDecode(arbFile.readAsStringSync()) as Map<String, dynamic>;
- final keys = arbContent.keys.where((k) => !k.startsWith('@')).toList();
- print(
- '๐ Found ${keys.length} keys in ${p.relative(arbPath, from: rootDir.path)}\n');
- // Collect all Dart files except those in lib/l10n/
- final dartFiles = Directory(sourceDir)
- .listSync(recursive: true)
- .whereType<File>()
- .where((f) =>
- f.path.endsWith('.dart') &&
- !f.path.contains(
- RegExp(r'lib[\/\\]l10n[\/\\]'))) // ignore localization files
- .toList();
- print('๐ Scanning ${dartFiles.length} Dart files (excluding lib/l10n/)\n');
- // Patterns that allow newlines and spaces between symbols (multiline-safe)
- final rawPatterns = <String>[
- r'\bS\.of\([^)]*\)\s*\.\s*{}',
- r'\bS\.of\([^)]*\)\s*!\s*\.\s*{}',
- r'\bAppLocalizations\.of\([^)]*\)\s*\.\s*{}',
- r'\bAppLocalizations\.of\([^)]*\)\s*!\s*\.\s*{}',
- r'\bcontext\.l10n\s*\.\s*{}',
- r'\bl10n\([^)]*\)\s*\.\s*{}',
- r'\.\s*{}', // fallback
- ];
- final unused = <String>[];
- final usedDetails = <String, List<String>>{};
- for (final key in keys) {
- final matches = <String>[];
- final regexes = rawPatterns
- .map((p) => RegExp(p.replaceAll('{}', RegExp.escape(key)),
- multiLine: true, dotAll: true))
- .toList();
- for (final file in dartFiles) {
- final content = file.readAsStringSync();
- if (regexes.any((r) => r.hasMatch(content))) {
- matches.add(p.relative(file.path, from: rootDir.path));
- }
- }
- if (matches.isEmpty) {
- unused.add(key);
- } else {
- usedDetails[key] = matches;
- }
- }
- // Summary
- print('โ Used keys: ${usedDetails.length}');
- print('๐งน Unused keys: ${unused.length}\n');
- if (unused.isNotEmpty) {
- print('=== Unused keys ===');
- for (final k in unused) {
- print(' - $k');
- }
- }
- // Write report file
- final out = File(p.join(rootDir.path, 'i10n_usage_report.txt'));
- final buffer = StringBuffer()
- ..writeln('i10n usage report')
- ..writeln('Project root: ${rootDir.path}')
- ..writeln('ARB: ${p.relative(arbPath, from: rootDir.path)}')
- ..writeln(
- 'Scanned Dart files under: ${p.relative(sourceDir, from: rootDir.path)} (excluding lib/l10n/)\n');
- buffer.writeln('USED KEYS:\n');
- usedDetails.forEach((k, files) {
- buffer.writeln('$k -> ${files.length} files');
- for (final f in files) {
- buffer.writeln(' - $f');
- }
- });
- buffer.writeln('\nUNUSED KEYS:\n');
- for (final k in unused) {
- buffer.writeln('- $k');
- }
- out.writeAsStringSync(buffer.toString());
- print('\n๐ Detailed report written to ${out.path}');
- }
- /// Walks upward from the current working directory to find a folder containing pubspec.yaml.
- Directory? findFlutterRoot() {
- var dir = Directory.current;
- while (true) {
- final pubspec = File(p.join(dir.path, 'pubspec.yaml'));
- if (pubspec.existsSync()) return dir;
- final parent = dir.parent;
- if (parent.path == dir.path) return null;
- dir = parent;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment