i18ntk 3.3.0 → 4.0.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.
- package/CHANGELOG.md +29 -2
- package/README.md +157 -15
- package/SECURITY.md +14 -8
- package/main/i18ntk-backup.js +305 -62
- package/main/i18ntk-scanner.js +188 -49
- package/main/i18ntk-sizing.js +223 -29
- package/main/i18ntk-usage.js +203 -3
- package/main/i18ntk-validate.js +107 -3
- package/main/manage/commands/FixerCommand.js +23 -21
- package/main/manage/index.js +13 -7
- package/main/manage/services/FileManagementService.js +12 -6
- package/package.json +2 -2
- package/runtime/i18ntk.d.ts +22 -16
- package/runtime/index.d.ts +9 -7
- package/runtime/index.js +240 -50
- package/ui-locales/en.json +1 -1
- package/utils/translate/protection.js +147 -6
- package/utils/watch-locales.js +183 -36
package/main/i18ntk-usage.js
CHANGED
|
@@ -84,6 +84,11 @@ class I18nUsageAnalyzer {
|
|
|
84
84
|
this.startTime = Date.now(); // Track performance metrics
|
|
85
85
|
this.version = '1.10.1'; // Version tracking
|
|
86
86
|
|
|
87
|
+
// Dead key detection properties
|
|
88
|
+
this.deadKeys = new Map();
|
|
89
|
+
this.cleanupMode = false;
|
|
90
|
+
this.dryRunDelete = false;
|
|
91
|
+
|
|
87
92
|
// Use global translation function
|
|
88
93
|
this.rl = null;
|
|
89
94
|
}
|
|
@@ -187,7 +192,9 @@ class I18nUsageAnalyzer {
|
|
|
187
192
|
help: a.help || a.h,
|
|
188
193
|
noPrompt: a.noPrompt ?? a['no-prompt'],
|
|
189
194
|
strict: a.strict,
|
|
190
|
-
debug: a.debug
|
|
195
|
+
debug: a.debug,
|
|
196
|
+
cleanup: a.cleanup ?? a['cleanup'],
|
|
197
|
+
dryRunDelete: a.dryRunDelete ?? a['dry-run-delete']
|
|
191
198
|
};
|
|
192
199
|
}
|
|
193
200
|
|
|
@@ -376,6 +383,13 @@ class I18nUsageAnalyzer {
|
|
|
376
383
|
console.log('🔍 Debug mode enabled');
|
|
377
384
|
}
|
|
378
385
|
|
|
386
|
+
if (args.cleanup) {
|
|
387
|
+
this.cleanupMode = true;
|
|
388
|
+
}
|
|
389
|
+
if (args.dryRunDelete) {
|
|
390
|
+
this.dryRunDelete = true;
|
|
391
|
+
}
|
|
392
|
+
|
|
379
393
|
try {
|
|
380
394
|
// Ensure config is always initialized
|
|
381
395
|
if (!this.config) {
|
|
@@ -599,6 +613,33 @@ class I18nUsageAnalyzer {
|
|
|
599
613
|
}));
|
|
600
614
|
}
|
|
601
615
|
|
|
616
|
+
if (this.cleanupMode) {
|
|
617
|
+
const deadKeys = this.findDeadKeys();
|
|
618
|
+
console.log('\n' + t('usage.deadKeysDetectionTitle'));
|
|
619
|
+
console.log(t('usage.deadKeysCount', { count: deadKeys.length }));
|
|
620
|
+
|
|
621
|
+
const highConfidence = deadKeys.filter(dk => dk.confidence >= 0.8).length;
|
|
622
|
+
const mediumConfidence = deadKeys.filter(dk => dk.confidence >= 0.4 && dk.confidence < 0.8).length;
|
|
623
|
+
const lowConfidence = deadKeys.filter(dk => dk.confidence < 0.4).length;
|
|
624
|
+
|
|
625
|
+
console.log(t('usage.deadKeysConfidenceBreakdown', { high: highConfidence, medium: mediumConfidence, low: lowConfidence }));
|
|
626
|
+
|
|
627
|
+
if (deadKeys.length > 0) {
|
|
628
|
+
console.log('\n' + t('usage.deadKeysSample'));
|
|
629
|
+
deadKeys.slice(0, 10).forEach(dk => {
|
|
630
|
+
console.log(` ${dk.key} [${(dk.confidence * 100).toFixed(0)}%] - ${dk.reason}`);
|
|
631
|
+
});
|
|
632
|
+
if (deadKeys.length > 10) {
|
|
633
|
+
console.log(t('usage.deadKeysMore', { count: deadKeys.length - 10 }));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (this.dryRunDelete) {
|
|
638
|
+
const reportPath = this.saveDeadKeysReport(deadKeys, args.outputDir || this.config.outputDir || './i18ntk-reports/usage');
|
|
639
|
+
console.log(t('usage.deadKeysReportSaved', { path: reportPath }));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
602
643
|
if (args.outputReport) {
|
|
603
644
|
const report = this.generateUsageReport();
|
|
604
645
|
await this.saveReport(report, args.outputDir);
|
|
@@ -625,7 +666,7 @@ class I18nUsageAnalyzer {
|
|
|
625
666
|
// Show help message
|
|
626
667
|
showHelp() {
|
|
627
668
|
console.log(`
|
|
628
|
-
📊 i18ntk usage - Translation key usage analysis (v1.
|
|
669
|
+
📊 i18ntk usage - Translation key usage analysis (v1.10.1)
|
|
629
670
|
|
|
630
671
|
Usage:
|
|
631
672
|
node i18ntk-usage.js [options]
|
|
@@ -642,14 +683,17 @@ Options:
|
|
|
642
683
|
--validate-placeholders Enable placeholder key validation
|
|
643
684
|
--framework-detect Enable framework-specific pattern detection
|
|
644
685
|
--performance-mode Enable performance metrics tracking
|
|
686
|
+
--cleanup Enable dead key detection for cleanup mode
|
|
687
|
+
--dry-run-delete Save dead keys report without deleting (requires --cleanup)
|
|
645
688
|
--help, -h Show this help message
|
|
646
689
|
|
|
647
690
|
Examples:
|
|
648
691
|
node i18ntk-usage.js --source-dir=./src --i18n-dir=./translations --output-report
|
|
649
692
|
npm run i18ntk:usage -- --strict --debug --validate-placeholders
|
|
650
693
|
node i18ntk-usage.js --no-prompt --performance-mode --output-dir=./reports
|
|
694
|
+
node i18ntk-usage.js --cleanup --dry-run-delete
|
|
651
695
|
|
|
652
|
-
Analysis Features (v1.
|
|
696
|
+
Analysis Features (v1.10.1):
|
|
653
697
|
• Detects unused translation keys
|
|
654
698
|
• Identifies missing translation keys
|
|
655
699
|
• Shows translation completeness by language
|
|
@@ -662,6 +706,7 @@ Analysis Features (v1.8.3):
|
|
|
662
706
|
• Key complexity analysis
|
|
663
707
|
• Security-enhanced path validation
|
|
664
708
|
• Detailed reporting with validation errors
|
|
709
|
+
• Dead key detection with confidence scoring
|
|
665
710
|
`);
|
|
666
711
|
}
|
|
667
712
|
|
|
@@ -1133,6 +1178,161 @@ Analysis Features (v1.8.3):
|
|
|
1133
1178
|
return missing;
|
|
1134
1179
|
}
|
|
1135
1180
|
|
|
1181
|
+
findDeadKeys() {
|
|
1182
|
+
const unusedKeys = this.findUnusedKeys();
|
|
1183
|
+
const deadKeys = [];
|
|
1184
|
+
|
|
1185
|
+
for (const key of unusedKeys) {
|
|
1186
|
+
let confidence = 0.9;
|
|
1187
|
+
let reason = 'Key not found in any source file';
|
|
1188
|
+
|
|
1189
|
+
if (this._matchesDynamicPattern(key)) {
|
|
1190
|
+
confidence = 0.3;
|
|
1191
|
+
reason = 'Key matches dynamic template pattern (likely used)';
|
|
1192
|
+
} else if (this._keyInSourceComments(key)) {
|
|
1193
|
+
confidence = 0.5;
|
|
1194
|
+
reason = 'Key referenced in comments/JSDoc';
|
|
1195
|
+
} else if (this._parentFileRecentlyModified(key)) {
|
|
1196
|
+
confidence = 0.4;
|
|
1197
|
+
reason = 'Translation file modified within last 30 days';
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
deadKeys.push({ key, confidence, reason });
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
deadKeys.sort((a, b) => b.confidence - a.confidence);
|
|
1204
|
+
return deadKeys;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
_matchesDynamicPattern(key) {
|
|
1208
|
+
const keyParts = key.split('.');
|
|
1209
|
+
if (keyParts.length < 2) return false;
|
|
1210
|
+
|
|
1211
|
+
const dynamicPatterns = [
|
|
1212
|
+
/t\(`[^`]*\$\{[^}]*\}[^`]*`\)/g,
|
|
1213
|
+
/i18n\.t\(`[^`]*\$\{[^}]*\}[^`]*`\)/g,
|
|
1214
|
+
/useTranslation\(\)\.t\(`[^`]*\$\{[^}]*\}[^`]*`\)/g
|
|
1215
|
+
];
|
|
1216
|
+
|
|
1217
|
+
try {
|
|
1218
|
+
const sourceFiles = Array.from(this.fileUsage.keys());
|
|
1219
|
+
for (const filePath of sourceFiles) {
|
|
1220
|
+
const fullPath = path.join(this.sourceDir, filePath);
|
|
1221
|
+
if (!SecurityUtils.safeExistsSync(fullPath, this.sourceDir)) continue;
|
|
1222
|
+
|
|
1223
|
+
const content = SecurityUtils.safeReadFileSync(fullPath, this.sourceDir, 'utf8');
|
|
1224
|
+
if (!content) continue;
|
|
1225
|
+
|
|
1226
|
+
for (const pattern of dynamicPatterns) {
|
|
1227
|
+
const matches = content.match(pattern);
|
|
1228
|
+
if (matches) {
|
|
1229
|
+
for (const match of matches) {
|
|
1230
|
+
const matchLower = match.toLowerCase();
|
|
1231
|
+
if (keyParts.some(part => matchLower.includes(part.toLowerCase()))) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
} catch (e) {
|
|
1239
|
+
// Silently fail - dynamic pattern detection is best-effort
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
_keyInSourceComments(key) {
|
|
1246
|
+
const commentPatterns = [
|
|
1247
|
+
/\/\/[^\n]*/g,
|
|
1248
|
+
/\/\*[\s\S]*?\*\//g,
|
|
1249
|
+
/\/\*\*[\s\S]*?\*\//g
|
|
1250
|
+
];
|
|
1251
|
+
|
|
1252
|
+
try {
|
|
1253
|
+
const sourceFiles = Array.from(this.fileUsage.keys());
|
|
1254
|
+
for (const filePath of sourceFiles) {
|
|
1255
|
+
const fullPath = path.join(this.sourceDir, filePath);
|
|
1256
|
+
if (!SecurityUtils.safeExistsSync(fullPath, this.sourceDir)) continue;
|
|
1257
|
+
|
|
1258
|
+
const content = SecurityUtils.safeReadFileSync(fullPath, this.sourceDir, 'utf8');
|
|
1259
|
+
if (!content) continue;
|
|
1260
|
+
|
|
1261
|
+
for (const pattern of commentPatterns) {
|
|
1262
|
+
const comments = content.match(pattern);
|
|
1263
|
+
if (comments) {
|
|
1264
|
+
for (const comment of comments) {
|
|
1265
|
+
if (comment.includes(key)) {
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
} catch (e) {
|
|
1273
|
+
// Silently fail - comment detection is best-effort
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
_parentFileRecentlyModified(key) {
|
|
1280
|
+
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
|
|
1281
|
+
const now = Date.now();
|
|
1282
|
+
|
|
1283
|
+
try {
|
|
1284
|
+
for (const [filePath] of this.translationFiles) {
|
|
1285
|
+
if (!SecurityUtils.safeExistsSync(filePath, this.i18nDir)) continue;
|
|
1286
|
+
|
|
1287
|
+
const content = SecurityUtils.safeReadFileSync(filePath, this.i18nDir, 'utf8');
|
|
1288
|
+
if (!content) continue;
|
|
1289
|
+
|
|
1290
|
+
if (content.includes(key)) {
|
|
1291
|
+
const stats = SecurityUtils.safeStatSync(filePath, this.i18nDir);
|
|
1292
|
+
if (stats && stats.mtime) {
|
|
1293
|
+
const mtimeMs = new Date(stats.mtime).getTime();
|
|
1294
|
+
if ((now - mtimeMs) <= thirtyDaysMs) {
|
|
1295
|
+
return true;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
} catch (e) {
|
|
1301
|
+
// Silently fail - file stat is best-effort
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
generateDeadKeysReport(deadKeys) {
|
|
1308
|
+
const allCleanupReady = deadKeys.length === 0 || deadKeys.every(dk => dk.confidence >= 0.8);
|
|
1309
|
+
|
|
1310
|
+
return {
|
|
1311
|
+
deadKeys,
|
|
1312
|
+
totalAvailableKeys: this.availableKeys.size,
|
|
1313
|
+
totalUsedKeys: this.usedKeys.size,
|
|
1314
|
+
deadKeyCount: deadKeys.length,
|
|
1315
|
+
cleanupReady: allCleanupReady,
|
|
1316
|
+
generatedAt: new Date().toISOString(),
|
|
1317
|
+
version: this.version
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
saveDeadKeysReport(deadKeys, outputDir) {
|
|
1322
|
+
const report = this.generateDeadKeysReport(deadKeys);
|
|
1323
|
+
const resolvedDir = path.resolve(outputDir || './i18ntk-reports/usage');
|
|
1324
|
+
|
|
1325
|
+
if (!SecurityUtils.safeExistsSync(resolvedDir, process.cwd())) {
|
|
1326
|
+
SecurityUtils.safeMkdirSync(resolvedDir, process.cwd(), { recursive: true });
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const filename = '.dead-keys.json';
|
|
1330
|
+
const filepath = path.join(resolvedDir, filename);
|
|
1331
|
+
|
|
1332
|
+
SecurityUtils.safeWriteFileSync(filepath, JSON.stringify(report, null, 2), resolvedDir, 'utf8');
|
|
1333
|
+
return filepath;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1136
1336
|
// Find files that use specific keys
|
|
1137
1337
|
findKeyUsage(searchKey) {
|
|
1138
1338
|
const usage = [];
|
package/main/i18ntk-validate.js
CHANGED
|
@@ -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
|
|
|
@@ -167,6 +168,8 @@ class I18nValidator {
|
|
|
167
168
|
const key = sanitizedArg.substring(2);
|
|
168
169
|
if (['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(key)) {
|
|
169
170
|
baseArgs.uiLanguage = key;
|
|
171
|
+
} else if (key === 'enforce-key-style') {
|
|
172
|
+
baseArgs.enforceKeyStyle = true;
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
175
|
});
|
|
@@ -573,6 +576,25 @@ class I18nValidator {
|
|
|
573
576
|
// Validate structure
|
|
574
577
|
const structural = this.validateStructure(sourceContent, targetContent, language, fileName);
|
|
575
578
|
|
|
579
|
+
// Check key naming conventions
|
|
580
|
+
if (this.config.enforceKeyStyle) {
|
|
581
|
+
const keyNamingResult = this.validateKeyNaming(sourceContent);
|
|
582
|
+
keyNamingResult.violations.forEach(v => {
|
|
583
|
+
this.addWarning(
|
|
584
|
+
`Key naming violation in ${language}/${fileName}`,
|
|
585
|
+
{ key: v.key, suggestedFix: v.suggestedFix, reason: v.reason, style: keyNamingResult.style }
|
|
586
|
+
);
|
|
587
|
+
this.keyNamingViolations.push({
|
|
588
|
+
language,
|
|
589
|
+
fileName,
|
|
590
|
+
key: v.key,
|
|
591
|
+
suggestedFix: v.suggestedFix,
|
|
592
|
+
reason: v.reason,
|
|
593
|
+
style: keyNamingResult.style
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
576
598
|
// Validate translations
|
|
577
599
|
const translations = this.validateTranslation(targetContent, language, fileName);
|
|
578
600
|
this.checkPlaceholders(sourceContent, targetContent, language, fileName);
|
|
@@ -652,7 +674,68 @@ class I18nValidator {
|
|
|
652
674
|
return warnings;
|
|
653
675
|
}
|
|
654
676
|
|
|
655
|
-
|
|
677
|
+
validateKeyNaming(sourceObj, style) {
|
|
678
|
+
const keyStyle = style || this.config.keyStyle || 'dot.notation';
|
|
679
|
+
const allKeys = this.getAllKeys(sourceObj);
|
|
680
|
+
const validators = {
|
|
681
|
+
'dot.notation': /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/,
|
|
682
|
+
'snake_case': /^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*$/,
|
|
683
|
+
'camelCase': /^[a-z][a-zA-Z0-9]*$/,
|
|
684
|
+
'kebab-case': /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/,
|
|
685
|
+
'flat': /^[a-zA-Z][a-zA-Z0-9]*$/
|
|
686
|
+
};
|
|
687
|
+
const regex = validators[keyStyle];
|
|
688
|
+
if (!regex) {
|
|
689
|
+
return { violations: [], totalKeys: allKeys.size, violationCount: 0, style: keyStyle };
|
|
690
|
+
}
|
|
691
|
+
const violations = [];
|
|
692
|
+
for (const key of allKeys) {
|
|
693
|
+
const sanitizedKey = SecurityUtils.sanitizeInput(key);
|
|
694
|
+
if (!regex.test(sanitizedKey)) {
|
|
695
|
+
violations.push({
|
|
696
|
+
key: sanitizedKey,
|
|
697
|
+
suggestedFix: this.suggestKeyFix(sanitizedKey, keyStyle),
|
|
698
|
+
reason: `Key "${sanitizedKey}" does not match "${keyStyle}" naming convention`
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
violations,
|
|
704
|
+
totalKeys: allKeys.size,
|
|
705
|
+
violationCount: violations.length,
|
|
706
|
+
style: keyStyle
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
suggestKeyFix(key, style) {
|
|
711
|
+
const sanitizedKey = SecurityUtils.sanitizeInput(key);
|
|
712
|
+
const segments = [];
|
|
713
|
+
const rawTokens = sanitizedKey.split(/[._\-]/);
|
|
714
|
+
for (const token of rawTokens) {
|
|
715
|
+
if (!token) continue;
|
|
716
|
+
const camelTokens = token.split(/(?=[A-Z])/).filter(Boolean);
|
|
717
|
+
segments.push(...camelTokens);
|
|
718
|
+
}
|
|
719
|
+
if (segments.length === 0) {
|
|
720
|
+
return sanitizedKey;
|
|
721
|
+
}
|
|
722
|
+
switch (style) {
|
|
723
|
+
case 'dot.notation':
|
|
724
|
+
return segments.map(s => s.toLowerCase()).join('.');
|
|
725
|
+
case 'snake_case':
|
|
726
|
+
return segments.map(s => s.toLowerCase()).join('_');
|
|
727
|
+
case 'camelCase':
|
|
728
|
+
return segments.map((s, i) => i === 0 ? s.toLowerCase() : s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join('');
|
|
729
|
+
case 'kebab-case':
|
|
730
|
+
return segments.map(s => s.toLowerCase()).join('-');
|
|
731
|
+
case 'flat':
|
|
732
|
+
return segments.map((s, i) => i === 0 ? s.toLowerCase() : s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join('');
|
|
733
|
+
default:
|
|
734
|
+
return sanitizedKey;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Show help message
|
|
656
739
|
showHelp() {
|
|
657
740
|
console.log(t('validate.help_message'));
|
|
658
741
|
}
|
|
@@ -895,8 +978,29 @@ class I18nValidator {
|
|
|
895
978
|
console.log('');
|
|
896
979
|
});
|
|
897
980
|
}
|
|
898
|
-
|
|
899
|
-
//
|
|
981
|
+
|
|
982
|
+
// Key naming violations summary
|
|
983
|
+
if (this.keyNamingViolations.length > 0) {
|
|
984
|
+
console.log('');
|
|
985
|
+
console.log(t('validate.separator'));
|
|
986
|
+
console.log('🔑 Key Naming Convention Violations');
|
|
987
|
+
console.log('');
|
|
988
|
+
const displayStyle = this.config.keyStyle || 'dot.notation';
|
|
989
|
+
console.log(` Expected style: ${displayStyle}`);
|
|
990
|
+
console.log(` Violations found: ${this.keyNamingViolations.length}`);
|
|
991
|
+
console.log('');
|
|
992
|
+
console.log(' Suggested fixes:');
|
|
993
|
+
this.keyNamingViolations.slice(0, 10).forEach((v, i) => {
|
|
994
|
+
console.log(` ${i + 1}. "${v.key}" → "${v.suggestedFix}" (${v.language}/${v.fileName})`);
|
|
995
|
+
});
|
|
996
|
+
if (this.keyNamingViolations.length > 10) {
|
|
997
|
+
console.log(` ... and ${this.keyNamingViolations.length - 10} more`);
|
|
998
|
+
}
|
|
999
|
+
console.log('');
|
|
1000
|
+
console.log(' 💡 Tip: Use i18ntk:fix-keys to auto-fix or manually rename keys.');
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Recommendations
|
|
900
1004
|
console.log('');
|
|
901
1005
|
console.log(t('validate.separator'));
|
|
902
1006
|
console.log(t('validate.recommendationsSection'));
|
|
@@ -530,20 +530,20 @@ class FixerCommand {
|
|
|
530
530
|
this.dryRun = args.dryRun || false;
|
|
531
531
|
this.force = args.force || false;
|
|
532
532
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
console.log(t('fixer.
|
|
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
|
-
|
|
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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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', {
|
|
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 }));
|
package/main/manage/index.js
CHANGED
|
@@ -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) {
|
|
@@ -129,7 +129,10 @@ module.exports = class FileManagementService {
|
|
|
129
129
|
{ path: path.join(process.cwd(), 'scripts', 'debug', 'logs'), name: 'Debug Logs', type: 'logs' },
|
|
130
130
|
{ path: path.join(process.cwd(), 'scripts', 'debug', 'reports'), name: 'Debug Reports', type: 'reports' },
|
|
131
131
|
{ path: path.join(process.cwd(), 'settings', 'backups'), name: 'Settings Backups', type: 'backups' },
|
|
132
|
-
{ path: path.join(process.cwd(), 'utils', 'i18ntk-reports'), name: 'Utils Reports', type: 'reports' }
|
|
132
|
+
{ path: path.join(process.cwd(), 'utils', 'i18ntk-reports'), name: 'Utils Reports', type: 'reports' },
|
|
133
|
+
{ path: path.join(process.cwd(), '.cache'), name: 'Cache', type: 'cache', includeAllFiles: true },
|
|
134
|
+
{ path: path.join(process.cwd(), 'settings', '.cache'), name: 'Settings Cache', type: 'cache', includeAllFiles: true },
|
|
135
|
+
{ path: path.join(process.cwd(), '.cache-ultra'), name: 'Performance Cache', type: 'cache', includeAllFiles: true }
|
|
133
136
|
].filter(dir => dir.path && typeof dir.path === 'string');
|
|
134
137
|
|
|
135
138
|
try {
|
|
@@ -140,7 +143,9 @@ module.exports = class FileManagementService {
|
|
|
140
143
|
// Check which directories exist and have files
|
|
141
144
|
for (const dir of targetDirs) {
|
|
142
145
|
if (SecurityUtils.safeExistsSync(dir.path)) {
|
|
143
|
-
const files = this.getAllReportFiles(dir.path
|
|
146
|
+
const files = this.getAllReportFiles(dir.path, {
|
|
147
|
+
includeAllFiles: dir.includeAllFiles === true
|
|
148
|
+
});
|
|
144
149
|
if (files.length > 0) {
|
|
145
150
|
availableDirs.push({
|
|
146
151
|
...dir,
|
|
@@ -267,12 +272,13 @@ module.exports = class FileManagementService {
|
|
|
267
272
|
* @param {string} dir - Directory to scan
|
|
268
273
|
* @returns {string[]} Array of file paths
|
|
269
274
|
*/
|
|
270
|
-
getAllReportFiles(dir) {
|
|
275
|
+
getAllReportFiles(dir, options = {}) {
|
|
271
276
|
if (!dir || typeof dir !== 'string') {
|
|
272
277
|
return [];
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
let files = [];
|
|
281
|
+
const includeAllFiles = options.includeAllFiles === true;
|
|
276
282
|
|
|
277
283
|
try {
|
|
278
284
|
if (!SecurityUtils.safeExistsSync(dir)) {
|
|
@@ -287,8 +293,8 @@ module.exports = class FileManagementService {
|
|
|
287
293
|
const stat = fs.statSync(fullPath);
|
|
288
294
|
|
|
289
295
|
if (stat.isDirectory()) {
|
|
290
|
-
files.push(...this.getAllReportFiles(fullPath));
|
|
291
|
-
} else if (
|
|
296
|
+
files.push(...this.getAllReportFiles(fullPath, options));
|
|
297
|
+
} else if (includeAllFiles || (
|
|
292
298
|
// Common report file extensions
|
|
293
299
|
item.endsWith('.json') ||
|
|
294
300
|
item.endsWith('.html') ||
|
|
@@ -303,7 +309,7 @@ module.exports = class FileManagementService {
|
|
|
303
309
|
item.includes('report_') ||
|
|
304
310
|
item.includes('analysis-') ||
|
|
305
311
|
item.includes('validation-')
|
|
306
|
-
) {
|
|
312
|
+
)) {
|
|
307
313
|
files.push(fullPath);
|
|
308
314
|
}
|
|
309
315
|
} catch (error) {
|