i18ntk 3.3.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -61,6 +61,10 @@ async function getConfig() {
61
61
  return await getUnifiedConfig('usage');
62
62
  }
63
63
 
64
+ function toBool(v) {
65
+ return v === true || v === 'true' || v === '1';
66
+ }
67
+
64
68
  class I18nUsageAnalyzer {
65
69
  constructor(config = {}) {
66
70
  this.config = config;
@@ -84,6 +88,12 @@ class I18nUsageAnalyzer {
84
88
  this.startTime = Date.now(); // Track performance metrics
85
89
  this.version = '1.10.1'; // Version tracking
86
90
 
91
+ // Dead key detection properties
92
+ this.deadKeys = new Map();
93
+ this.cleanupMode = false;
94
+ this.dryRunDelete = false;
95
+ this._sourceCommentsSet = null;
96
+
87
97
  // Use global translation function
88
98
  this.rl = null;
89
99
  }
@@ -187,7 +197,9 @@ class I18nUsageAnalyzer {
187
197
  help: a.help || a.h,
188
198
  noPrompt: a.noPrompt ?? a['no-prompt'],
189
199
  strict: a.strict,
190
- debug: a.debug
200
+ debug: a.debug,
201
+ cleanup: a.cleanup ?? a['cleanup'],
202
+ dryRunDelete: a.dryRunDelete ?? a['dry-run-delete']
191
203
  };
192
204
  }
193
205
 
@@ -376,6 +388,13 @@ class I18nUsageAnalyzer {
376
388
  console.log('🔍 Debug mode enabled');
377
389
  }
378
390
 
391
+ if (toBool(args.cleanup)) {
392
+ this.cleanupMode = true;
393
+ }
394
+ if (toBool(args.dryRunDelete)) {
395
+ this.dryRunDelete = true;
396
+ }
397
+
379
398
  try {
380
399
  // Ensure config is always initialized
381
400
  if (!this.config) {
@@ -519,9 +538,6 @@ class I18nUsageAnalyzer {
519
538
  // Load available translation keys first
520
539
  await this.loadAvailableKeys();
521
540
 
522
- // NEW: Detect framework patterns before analysis
523
- await this.detectFrameworkPatterns();
524
-
525
541
  // Perform usage analysis with enhanced features
526
542
  await this.analyzeUsage();
527
543
 
@@ -599,6 +615,34 @@ class I18nUsageAnalyzer {
599
615
  }));
600
616
  }
601
617
 
618
+ if (this.cleanupMode) {
619
+ this._buildSourceCommentsSet();
620
+ const deadKeys = this.findDeadKeys();
621
+ console.log('\n' + t('usage.deadKeysDetectionTitle'));
622
+ console.log(t('usage.deadKeysCount', { count: deadKeys.length }));
623
+
624
+ const highConfidence = deadKeys.filter(dk => dk.confidence >= 0.8).length;
625
+ const mediumConfidence = deadKeys.filter(dk => dk.confidence >= 0.4 && dk.confidence < 0.8).length;
626
+ const lowConfidence = deadKeys.filter(dk => dk.confidence < 0.4).length;
627
+
628
+ console.log(t('usage.deadKeysConfidenceBreakdown', { high: highConfidence, medium: mediumConfidence, low: lowConfidence }));
629
+
630
+ if (deadKeys.length > 0) {
631
+ console.log('\n' + t('usage.deadKeysSample'));
632
+ deadKeys.slice(0, 10).forEach(dk => {
633
+ console.log(` ${dk.key} [${(dk.confidence * 100).toFixed(0)}%] - ${dk.reason}`);
634
+ });
635
+ if (deadKeys.length > 10) {
636
+ console.log(t('usage.deadKeysMore', { count: deadKeys.length - 10 }));
637
+ }
638
+ }
639
+
640
+ if (this.dryRunDelete) {
641
+ const reportPath = this.saveDeadKeysReport(deadKeys, args.outputDir || this.config.outputDir || './i18ntk-reports/usage');
642
+ console.log(t('usage.deadKeysReportSaved', { path: reportPath }));
643
+ }
644
+ }
645
+
602
646
  if (args.outputReport) {
603
647
  const report = this.generateUsageReport();
604
648
  await this.saveReport(report, args.outputDir);
@@ -625,7 +669,7 @@ class I18nUsageAnalyzer {
625
669
  // Show help message
626
670
  showHelp() {
627
671
  console.log(`
628
- 📊 i18ntk usage - Translation key usage analysis (v1.8.3)
672
+ 📊 i18ntk usage - Translation key usage analysis (v1.10.1)
629
673
 
630
674
  Usage:
631
675
  node i18ntk-usage.js [options]
@@ -642,14 +686,17 @@ Options:
642
686
  --validate-placeholders Enable placeholder key validation
643
687
  --framework-detect Enable framework-specific pattern detection
644
688
  --performance-mode Enable performance metrics tracking
689
+ --cleanup Enable dead key detection for cleanup mode
690
+ --dry-run-delete Save dead keys report without deleting (requires --cleanup)
645
691
  --help, -h Show this help message
646
692
 
647
693
  Examples:
648
694
  node i18ntk-usage.js --source-dir=./src --i18n-dir=./translations --output-report
649
695
  npm run i18ntk:usage -- --strict --debug --validate-placeholders
650
696
  node i18ntk-usage.js --no-prompt --performance-mode --output-dir=./reports
697
+ node i18ntk-usage.js --cleanup --dry-run-delete
651
698
 
652
- Analysis Features (v1.8.3):
699
+ Analysis Features (v1.10.1):
653
700
  • Detects unused translation keys
654
701
  • Identifies missing translation keys
655
702
  • Shows translation completeness by language
@@ -662,6 +709,7 @@ Analysis Features (v1.8.3):
662
709
  • Key complexity analysis
663
710
  • Security-enhanced path validation
664
711
  • Detailed reporting with validation errors
712
+ • Dead key detection with confidence scoring
665
713
  `);
666
714
  }
667
715
 
@@ -1133,6 +1181,172 @@ Analysis Features (v1.8.3):
1133
1181
  return missing;
1134
1182
  }
1135
1183
 
1184
+ findDeadKeys() {
1185
+ const unusedKeys = this.findUnusedKeys();
1186
+ const deadKeys = [];
1187
+
1188
+ for (const key of unusedKeys) {
1189
+ let confidence = 0.9;
1190
+ let reason = 'Key not found in any source file';
1191
+
1192
+ if (this._matchesDynamicPattern(key)) {
1193
+ confidence = 0.3;
1194
+ reason = 'Key matches dynamic template pattern (likely used)';
1195
+ } else if (this._keyInSourceComments(key)) {
1196
+ confidence = 0.5;
1197
+ reason = 'Key referenced in comments/JSDoc';
1198
+ } else if (this._parentFileRecentlyModified(key)) {
1199
+ confidence = 0.4;
1200
+ reason = 'Translation file modified within last 30 days';
1201
+ }
1202
+
1203
+ deadKeys.push({ key, confidence, reason });
1204
+ }
1205
+
1206
+ deadKeys.sort((a, b) => b.confidence - a.confidence);
1207
+ return deadKeys;
1208
+ }
1209
+
1210
+ _matchesDynamicPattern(key) {
1211
+ const keyParts = key.split('.');
1212
+ if (keyParts.length < 2) return false;
1213
+
1214
+ const dynamicPatterns = [
1215
+ /t\(`[^`]*\$\{[^}]*\}[^`]*`\)/g,
1216
+ /i18n\.t\(`[^`]*\$\{[^}]*\}[^`]*`\)/g,
1217
+ /useTranslation\(\)\.t\(`[^`]*\$\{[^}]*\}[^`]*`\)/g
1218
+ ];
1219
+
1220
+ try {
1221
+ const sourceFiles = Array.from(this.fileUsage.keys());
1222
+ for (const filePath of sourceFiles) {
1223
+ const fullPath = path.join(this.sourceDir, filePath);
1224
+ if (!SecurityUtils.safeExistsSync(fullPath, this.sourceDir)) continue;
1225
+
1226
+ const content = SecurityUtils.safeReadFileSync(fullPath, this.sourceDir, 'utf8');
1227
+ if (!content) continue;
1228
+
1229
+ for (const pattern of dynamicPatterns) {
1230
+ const matches = content.match(pattern);
1231
+ if (matches) {
1232
+ for (const match of matches) {
1233
+ const matchLower = match.toLowerCase();
1234
+ if (keyParts.some(part => matchLower.includes(part.toLowerCase()))) {
1235
+ return true;
1236
+ }
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ } catch (e) {
1242
+ // Silently fail - dynamic pattern detection is best-effort
1243
+ }
1244
+
1245
+ return false;
1246
+ }
1247
+
1248
+ _buildSourceCommentsSet() {
1249
+ if (this._sourceCommentsSet !== null) return;
1250
+ this._sourceCommentsSet = new Set();
1251
+
1252
+ const commentPatterns = [
1253
+ /\/\/[^\n]*/g,
1254
+ /\/\*[\s\S]*?\*\//g
1255
+ ];
1256
+
1257
+ try {
1258
+ const sourceFiles = Array.from(this.fileUsage.keys());
1259
+ for (const filePath of sourceFiles) {
1260
+ const fullPath = path.join(this.sourceDir, filePath);
1261
+ if (!SecurityUtils.safeExistsSync(fullPath, this.sourceDir)) continue;
1262
+
1263
+ const content = SecurityUtils.safeReadFileSync(fullPath, this.sourceDir, 'utf8');
1264
+ if (!content) continue;
1265
+
1266
+ for (const pattern of commentPatterns) {
1267
+ const comments = content.match(pattern);
1268
+ if (comments) {
1269
+ for (const comment of comments) {
1270
+ this._sourceCommentsSet.add(comment);
1271
+ }
1272
+ }
1273
+ }
1274
+ }
1275
+ } catch (e) {
1276
+ this._sourceCommentsSet = new Set();
1277
+ }
1278
+ }
1279
+
1280
+ _keyInSourceComments(key) {
1281
+ try {
1282
+ if (!this._sourceCommentsSet || this._sourceCommentsSet.size === 0) return false;
1283
+ for (const comment of this._sourceCommentsSet) {
1284
+ if (comment.includes(key)) return true;
1285
+ }
1286
+ } catch (e) {
1287
+ // Silently fail - comment detection is best-effort
1288
+ }
1289
+
1290
+ return false;
1291
+ }
1292
+
1293
+ _parentFileRecentlyModified(key) {
1294
+ const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
1295
+ const now = Date.now();
1296
+
1297
+ try {
1298
+ for (const [filePath] of this.translationFiles) {
1299
+ if (!SecurityUtils.safeExistsSync(filePath, this.i18nDir)) continue;
1300
+
1301
+ const content = SecurityUtils.safeReadFileSync(filePath, this.i18nDir, 'utf8');
1302
+ if (!content) continue;
1303
+
1304
+ if (content.includes(key)) {
1305
+ const stats = SecurityUtils.safeStatSync(filePath, this.i18nDir);
1306
+ if (stats && stats.mtime) {
1307
+ const mtimeMs = new Date(stats.mtime).getTime();
1308
+ if ((now - mtimeMs) <= thirtyDaysMs) {
1309
+ return true;
1310
+ }
1311
+ }
1312
+ }
1313
+ }
1314
+ } catch (e) {
1315
+ // Silently fail - file stat is best-effort
1316
+ }
1317
+
1318
+ return false;
1319
+ }
1320
+
1321
+ generateDeadKeysReport(deadKeys) {
1322
+ const allCleanupReady = deadKeys.length === 0 || deadKeys.every(dk => dk.confidence >= 0.8);
1323
+
1324
+ return {
1325
+ deadKeys,
1326
+ totalAvailableKeys: this.availableKeys.size,
1327
+ totalUsedKeys: this.usedKeys.size,
1328
+ deadKeyCount: deadKeys.length,
1329
+ cleanupReady: allCleanupReady,
1330
+ generatedAt: new Date().toISOString(),
1331
+ version: this.version
1332
+ };
1333
+ }
1334
+
1335
+ saveDeadKeysReport(deadKeys, outputDir) {
1336
+ const report = this.generateDeadKeysReport(deadKeys);
1337
+ const resolvedDir = path.resolve(outputDir || './i18ntk-reports/usage');
1338
+
1339
+ if (!SecurityUtils.safeExistsSync(resolvedDir, process.cwd())) {
1340
+ SecurityUtils.safeMkdirSync(resolvedDir, process.cwd(), { recursive: true });
1341
+ }
1342
+
1343
+ const filename = '.dead-keys.json';
1344
+ const filepath = path.join(resolvedDir, filename);
1345
+
1346
+ SecurityUtils.safeWriteFileSync(filepath, JSON.stringify(report, null, 2), resolvedDir, 'utf8');
1347
+ return filepath;
1348
+ }
1349
+
1136
1350
  // Find files that use specific keys
1137
1351
  findKeyUsage(searchKey) {
1138
1352
  const usage = [];
@@ -1736,9 +1950,6 @@ Analysis Features (v1.8.3):
1736
1950
  // Close readline interface to prevent hanging
1737
1951
  this.closeReadline();
1738
1952
 
1739
- // Return instead of force exit to allow proper cleanup
1740
- return;
1741
-
1742
1953
  return {
1743
1954
  success: true,
1744
1955
  stats: {
@@ -1813,40 +2024,4 @@ if (require.main === module) {
1813
2024
  }
1814
2025
  }
1815
2026
 
1816
- module.exports = I18nUsageAnalyzer;
1817
-
1818
- // Run if called directly
1819
- if (require.main === module) {
1820
- async function main() {
1821
- try {
1822
- const cliArgs = parseCommonArgs(process.argv.slice(2));
1823
-
1824
- if (cliArgs.help) {
1825
- displayHelp('usage');
1826
- process.exit(0);
1827
- }
1828
-
1829
- // Let run() handle full initialization to avoid duplicate setup output
1830
- const analyzer = new I18nUsageAnalyzer();
1831
- await analyzer.run();
1832
- } catch (error) {
1833
- console.error('Error:', error.message);
1834
- process.exit(1);
1835
- }
1836
- }
1837
-
1838
- // Check if we're being called from the menu system (stdin has data)
1839
- const hasStdinData = !process.stdin.isTTY;
1840
-
1841
- if (hasStdinData) {
1842
- // When called from menu, consume stdin data and run with defaults
1843
- process.stdin.resume();
1844
- process.stdin.on('data', () => {});
1845
- process.stdin.on('end', () => {
1846
- main();
1847
- });
1848
- } else {
1849
- // Normal direct execution
1850
- main();
1851
- }
1852
- }
2027
+ module.exports = I18nUsageAnalyzer;
@@ -68,6 +68,7 @@ class I18nValidator {
68
68
  this.config = config;
69
69
  this.errors = [];
70
70
  this.warnings = [];
71
+ this.keyNamingViolations = [];
71
72
  this.rl = null;
72
73
  }
73
74
 
@@ -163,7 +164,10 @@ class I18nValidator {
163
164
  const args = process.argv.slice(2);
164
165
  args.forEach(arg => {
165
166
  const sanitizedArg = SecurityUtils.sanitizeInput(arg);
166
- if (sanitizedArg.startsWith('--') && !sanitizedArg.includes('=')) {
167
+ if (sanitizedArg.startsWith('--enforce-key-style')) {
168
+ const val = arg.split('=')[1];
169
+ baseArgs.enforceKeyStyle = val === undefined ? true : val !== 'false';
170
+ } else if (sanitizedArg.startsWith('--') && !sanitizedArg.includes('=')) {
167
171
  const key = sanitizedArg.substring(2);
168
172
  if (['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(key)) {
169
173
  baseArgs.uiLanguage = key;
@@ -239,7 +243,7 @@ class I18nValidator {
239
243
  const files = items
240
244
  .filter(item => {
241
245
  return item.isFile() && item.name.endsWith('.json') &&
242
- !this.config.excludeFiles.includes(item.name);
246
+ (!Array.isArray(this.config.excludeFiles) || !this.config.excludeFiles.includes(item.name));
243
247
  }).map(item => item.name);
244
248
 
245
249
  return files;
@@ -573,6 +577,25 @@ class I18nValidator {
573
577
  // Validate structure
574
578
  const structural = this.validateStructure(sourceContent, targetContent, language, fileName);
575
579
 
580
+ // Check key naming conventions
581
+ if (this.config.enforceKeyStyle) {
582
+ const keyNamingResult = this.validateKeyNaming(sourceContent);
583
+ keyNamingResult.violations.forEach(v => {
584
+ this.addWarning(
585
+ `Key naming violation in ${language}/${fileName}`,
586
+ { key: v.key, suggestedFix: v.suggestedFix, reason: v.reason, style: keyNamingResult.style }
587
+ );
588
+ this.keyNamingViolations.push({
589
+ language,
590
+ fileName,
591
+ key: v.key,
592
+ suggestedFix: v.suggestedFix,
593
+ reason: v.reason,
594
+ style: keyNamingResult.style
595
+ });
596
+ });
597
+ }
598
+
576
599
  // Validate translations
577
600
  const translations = this.validateTranslation(targetContent, language, fileName);
578
601
  this.checkPlaceholders(sourceContent, targetContent, language, fileName);
@@ -652,7 +675,69 @@ class I18nValidator {
652
675
  return warnings;
653
676
  }
654
677
 
655
- // Show help message
678
+ validateKeyNaming(sourceObj, style) {
679
+ const keyStyle = style || this.config.keyStyle || 'dot.notation';
680
+ const allKeys = this.getAllKeys(sourceObj);
681
+ const validators = {
682
+ 'dot.notation': /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/,
683
+ 'snake_case': /^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*$/,
684
+ 'camelCase': /^[a-z][a-zA-Z0-9]*$/,
685
+ 'kebab-case': /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/,
686
+ 'flat': /^[a-zA-Z][a-zA-Z0-9]*$/
687
+ };
688
+ const regex = validators[keyStyle];
689
+ if (!regex) {
690
+ return { violations: [], totalKeys: allKeys.size, violationCount: 0, style: keyStyle };
691
+ }
692
+ const violations = [];
693
+ for (const key of allKeys) {
694
+ const sanitizedKey = SecurityUtils.sanitizeInput(key);
695
+ const testKey = keyStyle === 'flat' ? sanitizedKey.split('.').pop() : sanitizedKey;
696
+ if (!regex.test(testKey)) {
697
+ violations.push({
698
+ key: sanitizedKey,
699
+ suggestedFix: this.suggestKeyFix(sanitizedKey, keyStyle),
700
+ reason: `Key "${sanitizedKey}" does not match "${keyStyle}" naming convention`
701
+ });
702
+ }
703
+ }
704
+ return {
705
+ violations,
706
+ totalKeys: allKeys.size,
707
+ violationCount: violations.length,
708
+ style: keyStyle
709
+ };
710
+ }
711
+
712
+ suggestKeyFix(key, style) {
713
+ const sanitizedKey = SecurityUtils.sanitizeInput(key);
714
+ const segments = [];
715
+ const rawTokens = sanitizedKey.split(/[._\-]/);
716
+ for (const token of rawTokens) {
717
+ if (!token) continue;
718
+ const camelTokens = token.split(/(?=[A-Z])/).filter(Boolean);
719
+ segments.push(...camelTokens);
720
+ }
721
+ if (segments.length === 0) {
722
+ return sanitizedKey;
723
+ }
724
+ switch (style) {
725
+ case 'dot.notation':
726
+ return segments.map(s => s.toLowerCase()).join('.');
727
+ case 'snake_case':
728
+ return segments.map(s => s.toLowerCase()).join('_');
729
+ case 'camelCase':
730
+ return segments.map((s, i) => i === 0 ? s.toLowerCase() : s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join('');
731
+ case 'kebab-case':
732
+ return segments.map(s => s.toLowerCase()).join('-');
733
+ case 'flat':
734
+ return segments.map(s => s.toLowerCase()).join('');
735
+ default:
736
+ return sanitizedKey;
737
+ }
738
+ }
739
+
740
+ // Show help message
656
741
  showHelp() {
657
742
  console.log(t('validate.help_message'));
658
743
  }
@@ -895,8 +980,29 @@ class I18nValidator {
895
980
  console.log('');
896
981
  });
897
982
  }
898
-
899
- // Recommendations
983
+
984
+ // Key naming violations summary
985
+ if (this.keyNamingViolations.length > 0) {
986
+ console.log('');
987
+ console.log(t('validate.separator'));
988
+ console.log('🔑 Key Naming Convention Violations');
989
+ console.log('');
990
+ const displayStyle = this.config.keyStyle || 'dot.notation';
991
+ console.log(` Expected style: ${displayStyle}`);
992
+ console.log(` Violations found: ${this.keyNamingViolations.length}`);
993
+ console.log('');
994
+ console.log(' Suggested fixes:');
995
+ this.keyNamingViolations.slice(0, 10).forEach((v, i) => {
996
+ console.log(` ${i + 1}. "${v.key}" → "${v.suggestedFix}" (${v.language}/${v.fileName})`);
997
+ });
998
+ if (this.keyNamingViolations.length > 10) {
999
+ console.log(` ... and ${this.keyNamingViolations.length - 10} more`);
1000
+ }
1001
+ console.log('');
1002
+ console.log(' 💡 Tip: Use i18ntk:fix-keys to auto-fix or manually rename keys.');
1003
+ }
1004
+
1005
+ // Recommendations
900
1006
  console.log('');
901
1007
  console.log(t('validate.separator'));
902
1008
  console.log(t('validate.recommendationsSection'));
@@ -950,6 +1056,7 @@ class I18nValidator {
950
1056
 
951
1057
  const args = this.parseArgs();
952
1058
 
1059
+ try {
953
1060
  // Ensure config is always initialized
954
1061
  if (!this.config) {
955
1062
  this.config = {};
@@ -967,6 +1074,7 @@ class I18nValidator {
967
1074
  } else {
968
1075
  await this.initialize();
969
1076
  }
1077
+ this.config.enforceKeyStyle = args.enforceKeyStyle !== undefined ? args.enforceKeyStyle : this.config.enforceKeyStyle;
970
1078
 
971
1079
  // Skip admin authentication when called from menu
972
1080
  if (!fromMenu) {
@@ -1042,6 +1150,7 @@ class I18nValidator {
1042
1150
  );
1043
1151
  throw error;
1044
1152
  }
1153
+ }
1045
1154
  }
1046
1155
 
1047
1156
 
@@ -530,20 +530,20 @@ class FixerCommand {
530
530
  this.dryRun = args.dryRun || false;
531
531
  this.force = args.force || false;
532
532
 
533
- if (!args.json) {
534
- console.log(t('fixer.starting'));
535
- console.log(t('fixer.sourceDirectory', { dir: path.resolve(this.sourceDir) }));
536
- console.log(t('fixer.dryRunMode', { mode: this.dryRun ? 'ON' : 'OFF' }));
537
- }
533
+ const languages = this.getAvailableLanguages();
534
+
535
+ if (!args.json) {
536
+ console.log(t('fixer.starting', { languages: languages.join(', ') || 'none' }));
537
+ console.log(t('fixer.sourceDirectory', { sourceDir: path.resolve(this.sourceDir) }));
538
+ console.log(t('fixer.dryRunMode', { mode: this.dryRun ? 'ON' : 'OFF' }));
539
+ }
538
540
 
539
541
  // Create backup unless disabled
540
542
  if (!args.noBackup && !this.dryRun && this.config?.backup?.enabled === true) {
541
543
  await this.createBackup();
542
544
  }
543
545
 
544
- const languages = this.getAvailableLanguages();
545
-
546
- if (languages.length === 0) {
546
+ if (languages.length === 0) {
547
547
  const error = t('fixer.noLanguages') || 'No target languages found.';
548
548
  if (args.json) {
549
549
  jsonOutput.setStatus('error', error);
@@ -573,14 +573,16 @@ class FixerCommand {
573
573
  totalIssues += fixes.totalIssues;
574
574
  totalFixed += fixes.fixedIssues;
575
575
 
576
- if (!args.json) {
577
- console.log(t('fixer.languageFixed', {
578
- language,
579
- issues: fixes.totalIssues,
580
- fixed: fixes.fixedIssues
581
- }));
582
- }
583
- }
576
+ if (!args.json) {
577
+ const skipped = Math.max(0, fixes.totalIssues - fixes.fixedIssues);
578
+ console.log(t('fixer.languageFixed', {
579
+ language,
580
+ issues: fixes.totalIssues,
581
+ fixed: fixes.fixedIssues,
582
+ skipped
583
+ }));
584
+ }
585
+ }
584
586
 
585
587
  // Prepare JSON output
586
588
  if (args.json) {
@@ -595,11 +597,11 @@ class FixerCommand {
595
597
  return { success: true, totalIssues, totalFixed, results };
596
598
  }
597
599
 
598
- // Summary
599
- console.log(t('fixer.summary'));
600
- console.log('='.repeat(50));
601
- console.log(t('fixer.totalIssues', { count: totalIssues }));
602
- console.log(t('fixer.totalFixed', { count: totalFixed }));
600
+ // Summary
601
+ console.log(t('fixer.summary'));
602
+ console.log('='.repeat(50));
603
+ console.log(t('fixer.totalIssues', { totalIssues }));
604
+ console.log(t('fixer.totalFixed', { count: totalFixed }));
603
605
 
604
606
  if (this.backupDir && !args.noBackup && this.config?.backup?.enabled === true) {
605
607
  console.log(t('fixer.backupLocation', { dir: this.backupDir }));
@@ -1220,7 +1220,10 @@ class I18nManager {
1220
1220
  { path: path.join(process.cwd(), 'scripts', 'debug', 'logs'), name: 'Debug Logs', type: 'logs' },
1221
1221
  { path: path.join(process.cwd(), 'scripts', 'debug', 'reports'), name: 'Debug Reports', type: 'reports' },
1222
1222
  { path: path.join(process.cwd(), 'settings', 'backups'), name: 'Settings Backups', type: 'backups' },
1223
- { path: path.join(process.cwd(), 'utils', 'i18ntk-reports'), name: 'Utils Reports', type: 'reports' }
1223
+ { path: path.join(process.cwd(), 'utils', 'i18ntk-reports'), name: 'Utils Reports', type: 'reports' },
1224
+ { path: path.join(process.cwd(), '.cache'), name: 'Cache', type: 'cache', includeAllFiles: true },
1225
+ { path: path.join(process.cwd(), 'settings', '.cache'), name: 'Settings Cache', type: 'cache', includeAllFiles: true },
1226
+ { path: path.join(process.cwd(), '.cache-ultra'), name: 'Performance Cache', type: 'cache', includeAllFiles: true }
1224
1227
  ].filter(dir => dir.path && typeof dir.path === 'string');
1225
1228
 
1226
1229
  try {
@@ -1233,7 +1236,9 @@ class I18nManager {
1233
1236
  for (const dir of targetDirs) {
1234
1237
  const validatedDirPath = SecurityUtils.validatePath(dir.path, projectRoot);
1235
1238
  if (validatedDirPath && SecurityUtils.safeExistsSync(validatedDirPath, projectRoot)) {
1236
- const files = this.getAllReportFiles(validatedDirPath, validatedDirPath);
1239
+ const files = this.getAllReportFiles(validatedDirPath, validatedDirPath, {
1240
+ includeAllFiles: dir.includeAllFiles === true
1241
+ });
1237
1242
  if (files.length > 0) {
1238
1243
  availableDirs.push({
1239
1244
  ...dir,
@@ -1368,12 +1373,13 @@ class I18nManager {
1368
1373
  await this.showInteractiveMenu();
1369
1374
  }
1370
1375
 
1371
- getAllReportFiles(dir, rootDir = dir) {
1376
+ getAllReportFiles(dir, rootDir = dir, options = {}) {
1372
1377
  if (!dir || typeof dir !== 'string') {
1373
1378
  return [];
1374
1379
  }
1375
1380
 
1376
- let files = [];
1381
+ let files = [];
1382
+ const includeAllFiles = options.includeAllFiles === true;
1377
1383
 
1378
1384
  try {
1379
1385
  const validatedDir = SecurityUtils.validatePath(dir, rootDir);
@@ -1399,8 +1405,8 @@ class I18nManager {
1399
1405
  const stat = fs.statSync(safeFullPath);
1400
1406
 
1401
1407
  if (stat.isDirectory()) {
1402
- files.push(...this.getAllReportFiles(safeFullPath, rootDir));
1403
- } else if (
1408
+ files.push(...this.getAllReportFiles(safeFullPath, rootDir, options));
1409
+ } else if (includeAllFiles || (
1404
1410
  // Common report file extensions
1405
1411
  item.endsWith('.json') ||
1406
1412
  item.endsWith('.html') ||
@@ -1415,7 +1421,7 @@ class I18nManager {
1415
1421
  item.includes('report_') ||
1416
1422
  item.includes('analysis-') ||
1417
1423
  item.includes('validation-')
1418
- ) {
1424
+ )) {
1419
1425
  files.push(safeFullPath);
1420
1426
  }
1421
1427
  } catch (error) {