i18ntk 2.0.4 → 2.2.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/README.md +38 -60
- package/main/i18ntk-analyze.js +49 -44
- package/main/i18ntk-complete.js +75 -74
- package/main/i18ntk-fixer.js +3 -3
- package/main/i18ntk-init.js +5 -5
- package/main/i18ntk-scanner.js +2 -2
- package/main/i18ntk-sizing.js +35 -35
- package/main/i18ntk-summary.js +4 -4
- package/main/i18ntk-ui.js +54 -8
- package/main/i18ntk-usage.js +14 -14
- package/main/i18ntk-validate.js +6 -5
- package/main/manage/commands/AnalyzeCommand.js +40 -35
- package/main/manage/commands/FixerCommand.js +2 -2
- package/main/manage/commands/ScannerCommand.js +2 -2
- package/main/manage/commands/ValidateCommand.js +9 -9
- package/main/manage/index.js +147 -75
- package/main/manage/managers/LanguageMenu.js +7 -2
- package/main/manage/services/UsageService.js +7 -7
- package/package.json +269 -290
- package/settings/settings-cli.js +3 -3
- package/ui-locales/de.json +161 -166
- package/ui-locales/en.json +13 -18
- package/ui-locales/es.json +171 -184
- package/ui-locales/fr.json +155 -161
- package/ui-locales/ja.json +192 -243
- package/ui-locales/ru.json +145 -196
- package/ui-locales/zh.json +179 -185
- package/utils/cli-helper.js +26 -98
- package/utils/extractors/regex.js +39 -12
- package/utils/i18n-helper.js +88 -40
- package/{scripts → utils}/locale-optimizer.js +61 -60
- package/utils/security-check-improved.js +16 -13
- package/utils/security.js +6 -4
- package/main/i18ntk-go.js +0 -283
- package/main/i18ntk-java.js +0 -380
- package/main/i18ntk-js.js +0 -512
- package/main/i18ntk-manage.js +0 -1694
- package/main/i18ntk-php.js +0 -462
- package/main/i18ntk-py.js +0 -379
- package/main/i18ntk-settings.js +0 -23
- package/main/manage/index-fixed.js +0 -1447
- package/main/manage/services/ConfigurationService-fixed.js +0 -449
- package/scripts/build-lite.js +0 -279
- package/scripts/deprecate-versions.js +0 -317
- package/scripts/export-translations.js +0 -84
- package/scripts/fix-all-i18n.js +0 -215
- package/scripts/fix-and-purify-i18n.js +0 -213
- package/scripts/fix-locale-control-chars.js +0 -110
- package/scripts/lint-locales.js +0 -80
- package/scripts/prepublish.js +0 -348
- package/scripts/security-check.js +0 -117
- package/scripts/sync-translations.js +0 -151
- package/scripts/sync-ui-locales.js +0 -20
- package/scripts/validate-all-translations.js +0 -139
- package/scripts/verify-deprecations.js +0 -157
- package/scripts/verify-translations.js +0 -63
- package/utils/security-fixed.js +0 -607
package/main/i18ntk-sizing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* I18n Sizing Analyzer
|
|
@@ -33,10 +33,12 @@
|
|
|
33
33
|
|
|
34
34
|
const fs = require('fs');
|
|
35
35
|
const path = require('path');
|
|
36
|
+
const { performance } = require('perf_hooks');
|
|
36
37
|
const { loadTranslations, t } = require('../utils/i18n-helper');
|
|
37
38
|
const configManager = require('../settings/settings-manager');
|
|
38
39
|
const SecurityUtils = require('../utils/security');
|
|
39
40
|
const { getUnifiedConfig } = require('../utils/config-helper');
|
|
41
|
+
const { logger } = require('../utils/logger');
|
|
40
42
|
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
41
43
|
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
42
44
|
|
|
@@ -83,7 +85,7 @@ class I18nSizingAnalyzer {
|
|
|
83
85
|
|
|
84
86
|
// Initialize i18n with UI language from config
|
|
85
87
|
const uiLanguage = options.uiLanguage || config.uiLanguage || 'en';
|
|
86
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
88
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
87
89
|
this.stats = {
|
|
88
90
|
files: {},
|
|
89
91
|
languages: {},
|
|
@@ -165,7 +167,7 @@ class I18nSizingAnalyzer {
|
|
|
165
167
|
|
|
166
168
|
// Analyze file sizes
|
|
167
169
|
analyzeFileSizes(files) {
|
|
168
|
-
|
|
170
|
+
logger.info(t("sizing.analyzing_file_sizes"));
|
|
169
171
|
|
|
170
172
|
files.forEach(({ language, file, path: filePath, files: langFiles }) => {
|
|
171
173
|
if (langFiles) {
|
|
@@ -220,7 +222,7 @@ class I18nSizingAnalyzer {
|
|
|
220
222
|
|
|
221
223
|
// Analyze translation content
|
|
222
224
|
analyzeTranslationContent(files) {
|
|
223
|
-
|
|
225
|
+
logger.info(t("sizing.analyzing_translation_content"));
|
|
224
226
|
|
|
225
227
|
files.forEach(({ language, path: filePath, files: langFiles }) => {
|
|
226
228
|
try {
|
|
@@ -266,7 +268,7 @@ class I18nSizingAnalyzer {
|
|
|
266
268
|
});
|
|
267
269
|
|
|
268
270
|
} catch (error) {
|
|
269
|
-
|
|
271
|
+
logger.error(t("sizing.failed_to_parse_language_error", { language, errorMessage: error.message }));
|
|
270
272
|
}
|
|
271
273
|
});
|
|
272
274
|
}
|
|
@@ -316,14 +318,14 @@ class I18nSizingAnalyzer {
|
|
|
316
318
|
|
|
317
319
|
// Generate size comparison analysis
|
|
318
320
|
generateSizeComparison() {
|
|
319
|
-
|
|
321
|
+
logger.info(t("sizing.generating_size_comparisons"));
|
|
320
322
|
|
|
321
323
|
const languages = Object.keys(this.stats.languages);
|
|
322
324
|
const baseLanguage = languages[0]; // Use first language as baseline
|
|
323
325
|
|
|
324
326
|
if (!baseLanguage) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
+
logger.warn(t("sizing.no_languages_found_for_comparison"));
|
|
328
|
+
return;
|
|
327
329
|
}
|
|
328
330
|
|
|
329
331
|
this.stats.summary = {
|
|
@@ -495,7 +497,7 @@ class I18nSizingAnalyzer {
|
|
|
495
497
|
async generateHumanReadableReport() {
|
|
496
498
|
if (!this.outputReport) return;
|
|
497
499
|
|
|
498
|
-
|
|
500
|
+
logger.info(t("sizing.generating_detailed_report"));
|
|
499
501
|
|
|
500
502
|
const validatedOutputDir = SecurityUtils.validatePath(this.outputDir, process.cwd());
|
|
501
503
|
if (!validatedOutputDir) {
|
|
@@ -518,7 +520,7 @@ class I18nSizingAnalyzer {
|
|
|
518
520
|
let textReport = this.generateTextReport(timestamp);
|
|
519
521
|
const textSuccess = await SecurityUtils.safeWriteFile(textReportPath, textReport, process.cwd());
|
|
520
522
|
if (textSuccess) {
|
|
521
|
-
|
|
523
|
+
logger.info(t("sizing.human_report_saved", { reportPath: textReportPath }));
|
|
522
524
|
}
|
|
523
525
|
|
|
524
526
|
// Generate JSON for programmatic access
|
|
@@ -664,8 +666,8 @@ Generated: ${new Date().toISOString()}
|
|
|
664
666
|
|
|
665
667
|
const success = await SecurityUtils.safeWriteFile(csvPath, csvContent, process.cwd());
|
|
666
668
|
if (success) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
+
logger.info(t("sizing.csv_report_saved_to", { csvPath }));
|
|
670
|
+
SecurityUtils.logSecurityEvent('CSV report saved', 'info', { csvPath });
|
|
669
671
|
} else {
|
|
670
672
|
throw new Error(t("sizing.failedToSaveCsvError"));
|
|
671
673
|
}
|
|
@@ -673,21 +675,20 @@ Generated: ${new Date().toISOString()}
|
|
|
673
675
|
|
|
674
676
|
// Main analysis method
|
|
675
677
|
async analyze() {
|
|
676
|
-
const
|
|
677
|
-
const startTime = perfTimer.now();
|
|
678
|
+
const startTime = performance.now();
|
|
678
679
|
|
|
679
680
|
try {
|
|
680
|
-
|
|
681
|
-
|
|
681
|
+
logger.info(t("sizing.starting_i18n_sizing_analysis"));
|
|
682
|
+
logger.info(t("sizing.source_directory", { sourceDir: this.sourceDir }));
|
|
682
683
|
|
|
683
684
|
const files = this.getLanguageFiles();
|
|
684
685
|
|
|
685
686
|
if (files.length === 0) {
|
|
686
|
-
|
|
687
|
-
|
|
687
|
+
logger.warn(t("sizing.no_translation_files_found"));
|
|
688
|
+
return;
|
|
688
689
|
}
|
|
689
690
|
|
|
690
|
-
|
|
691
|
+
logger.info(t("sizing.found_languages", { languages: files.map(f => f.language).join(', ') }));
|
|
691
692
|
|
|
692
693
|
this.analyzeFileSizes(files);
|
|
693
694
|
this.analyzeTranslationContent(files);
|
|
@@ -696,17 +697,17 @@ Generated: ${new Date().toISOString()}
|
|
|
696
697
|
if (this.format === 'table') {
|
|
697
698
|
this.displayFolderResults();
|
|
698
699
|
} else if (this.format === 'json') {
|
|
699
|
-
|
|
700
|
+
logger.info(t("sizing.analysisStats", { stats: JSON.stringify(this.stats, null, 2) }));
|
|
700
701
|
}
|
|
701
702
|
|
|
702
703
|
await this.generateHumanReadableReport();
|
|
703
704
|
|
|
704
|
-
const endTime =
|
|
705
|
-
|
|
705
|
+
const endTime = performance.now();
|
|
706
|
+
logger.info(t("sizing.analysis_completed", { duration: (endTime - startTime).toFixed(2) }));
|
|
706
707
|
|
|
707
708
|
} catch (error) {
|
|
708
|
-
|
|
709
|
-
|
|
709
|
+
logger.error(t("sizing.analysis_failed", { errorMessage: error.message }));
|
|
710
|
+
process.exit(1);
|
|
710
711
|
}
|
|
711
712
|
}
|
|
712
713
|
|
|
@@ -906,20 +907,19 @@ Options:
|
|
|
906
907
|
// Main analysis method
|
|
907
908
|
async analyze() {
|
|
908
909
|
try {
|
|
909
|
-
|
|
910
|
-
|
|
910
|
+
logger.info(t("sizing.starting_analysis"));
|
|
911
|
+
logger.info(t("sizing.source_directory", { sourceDir: this.sourceDir }));
|
|
911
912
|
|
|
912
|
-
const
|
|
913
|
-
const startTime = perfTimer.now();
|
|
913
|
+
const startTime = performance.now();
|
|
914
914
|
|
|
915
915
|
// Get language files
|
|
916
916
|
const files = this.getLanguageFiles();
|
|
917
917
|
if (files.length === 0) {
|
|
918
|
-
|
|
919
|
-
|
|
918
|
+
logger.warn(t("sizing.no_translation_files_found"));
|
|
919
|
+
return { success: false, error: "No translation files found" };
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
-
|
|
922
|
+
logger.info(t("sizing.found_files", { count: files.length }));
|
|
923
923
|
|
|
924
924
|
// Analyze file sizes
|
|
925
925
|
this.analyzeFileSizes(files);
|
|
@@ -936,16 +936,16 @@ Options:
|
|
|
936
936
|
// Generate reports if requested
|
|
937
937
|
await this.generateHumanReadableReport();
|
|
938
938
|
|
|
939
|
-
const endTime =
|
|
939
|
+
const endTime = performance.now();
|
|
940
940
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
941
941
|
|
|
942
|
-
|
|
942
|
+
logger.info(t("sizing.analysis_completed", { duration }));
|
|
943
943
|
|
|
944
944
|
return { success: true, stats: this.stats };
|
|
945
945
|
|
|
946
946
|
} catch (error) {
|
|
947
|
-
|
|
948
|
-
|
|
947
|
+
logger.error(t("sizing.analysis_failed", { errorMessage: error.message }));
|
|
948
|
+
return { success: false, error: error.message };
|
|
949
949
|
}
|
|
950
950
|
}
|
|
951
951
|
}
|
package/main/i18ntk-summary.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
@@ -19,7 +19,7 @@ const SetupEnforcer = require('../utils/setup-enforcer');
|
|
|
19
19
|
}
|
|
20
20
|
})();
|
|
21
21
|
|
|
22
|
-
loadTranslations(
|
|
22
|
+
loadTranslations('en', path.resolve(__dirname, '..', 'ui-locales'));
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class I18nSummaryReporter {
|
|
@@ -54,7 +54,7 @@ class I18nSummaryReporter {
|
|
|
54
54
|
this.config = baseConfig;
|
|
55
55
|
|
|
56
56
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
57
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
57
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
58
58
|
|
|
59
59
|
this.sourceDir = this.config.sourceDir;
|
|
60
60
|
|
|
@@ -831,7 +831,7 @@ class I18nSummaryReporter {
|
|
|
831
831
|
this.config = { ...this.config, ...baseConfig };
|
|
832
832
|
|
|
833
833
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
834
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
834
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
835
835
|
if (!this.config.sourceDir) {
|
|
836
836
|
this.config.sourceDir = this.detectI18nDirectory();
|
|
837
837
|
}
|
package/main/i18ntk-ui.js
CHANGED
|
@@ -15,7 +15,7 @@ class UIi18n {
|
|
|
15
15
|
constructor() {
|
|
16
16
|
this.currentLanguage = 'en';
|
|
17
17
|
this.translations = {};
|
|
18
|
-
this.uiLocalesDir =
|
|
18
|
+
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
19
19
|
this.availableLanguages = [];
|
|
20
20
|
this.configFile = path.resolve(configManager.configFile);
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ this.translations = {};
|
|
|
30
30
|
const config = configManager.loadSettings ? configManager.loadSettings() : configManager.getConfig ? configManager.getConfig() : {};
|
|
31
31
|
|
|
32
32
|
// Use safe defaults if config is not available
|
|
33
|
-
this.uiLocalesDir =
|
|
33
|
+
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
34
34
|
this.availableLanguages = this.detectAvailableLanguages();
|
|
35
35
|
|
|
36
36
|
|
|
@@ -44,11 +44,56 @@ this.translations = {};
|
|
|
44
44
|
}
|
|
45
45
|
} catch (error) {
|
|
46
46
|
console.warn('UIi18n: Failed to initialize with config, using defaults:', error.message);
|
|
47
|
-
this.uiLocalesDir =
|
|
47
|
+
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
48
48
|
this.availableLanguages = this.detectAvailableLanguages();
|
|
49
49
|
this.loadLanguage('en');
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
getValidationBase(targetPath) {
|
|
54
|
+
const fallbackBase = path.resolve(__dirname, '..');
|
|
55
|
+
if (!targetPath || typeof targetPath !== 'string') {
|
|
56
|
+
return fallbackBase;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let current = path.resolve(path.dirname(targetPath));
|
|
60
|
+
while (true) {
|
|
61
|
+
try {
|
|
62
|
+
if (fs.statSync(current).isDirectory()) {
|
|
63
|
+
return current;
|
|
64
|
+
}
|
|
65
|
+
} catch (_) {
|
|
66
|
+
// Continue walking up until an existing directory is found.
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const parent = path.dirname(current);
|
|
70
|
+
if (parent === current) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
current = parent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return fallbackBase;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
resolveUiLocalesDir() {
|
|
80
|
+
const candidates = [
|
|
81
|
+
path.resolve(__dirname, '..', 'ui-locales')
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
try {
|
|
86
|
+
const stats = SecurityUtils.safeStatSync(candidate, this.getValidationBase(candidate));
|
|
87
|
+
if (stats && stats.isDirectory()) {
|
|
88
|
+
return candidate;
|
|
89
|
+
}
|
|
90
|
+
} catch (_) {
|
|
91
|
+
// Try next candidate.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return candidates[0];
|
|
96
|
+
}
|
|
52
97
|
/**
|
|
53
98
|
/**
|
|
54
99
|
* Detect which UI locales are currently installed
|
|
@@ -58,7 +103,7 @@ this.translations = {};
|
|
|
58
103
|
const all = ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'];
|
|
59
104
|
return all.filter(lang => {
|
|
60
105
|
const filePath = path.join(this.uiLocalesDir, `${lang}.json`);
|
|
61
|
-
return SecurityUtils.safeExistsSync(filePath);
|
|
106
|
+
return SecurityUtils.safeExistsSync(filePath, this.getValidationBase(filePath));
|
|
62
107
|
});
|
|
63
108
|
}
|
|
64
109
|
|
|
@@ -94,7 +139,7 @@ this.translations = {};
|
|
|
94
139
|
// Primary: Use monolith JSON file (en.json, de.json, etc.)
|
|
95
140
|
const monolithTranslationFile = path.join(this.uiLocalesDir, `${language}.json`);
|
|
96
141
|
|
|
97
|
-
if (SecurityUtils.safeExistsSync(monolithTranslationFile)) {
|
|
142
|
+
if (SecurityUtils.safeExistsSync(monolithTranslationFile, this.getValidationBase(monolithTranslationFile))) {
|
|
98
143
|
try {
|
|
99
144
|
const content = SecurityUtils.safeReadFileSync(monolithTranslationFile, path.dirname(monolithTranslationFile), 'utf8');
|
|
100
145
|
const fullTranslations = JSON.parse(content);
|
|
@@ -114,7 +159,8 @@ this.translations = {};
|
|
|
114
159
|
// Fallback: Use folder-based structure if monolith file doesn't exist
|
|
115
160
|
const langDir = path.join(this.uiLocalesDir, language);
|
|
116
161
|
|
|
117
|
-
|
|
162
|
+
const langDirStats = SecurityUtils.safeStatSync(langDir, this.getValidationBase(langDir));
|
|
163
|
+
if (langDirStats && langDirStats.isDirectory()) {
|
|
118
164
|
const files = fs.readdirSync(langDir).filter(file => file.endsWith('.json'));
|
|
119
165
|
if (debugEnabled) {
|
|
120
166
|
console.log(`UI: Found files in ${langDir}: ${files.join(', ')}`);
|
|
@@ -384,7 +430,7 @@ this.translations = {};
|
|
|
384
430
|
getEnglishFallback(keyPath, replacements = {}) {
|
|
385
431
|
try {
|
|
386
432
|
const englishFile = path.join(this.uiLocalesDir, 'en.json');
|
|
387
|
-
if (SecurityUtils.safeExistsSync(englishFile)) {
|
|
433
|
+
if (SecurityUtils.safeExistsSync(englishFile, this.getValidationBase(englishFile))) {
|
|
388
434
|
const englishContent = SecurityUtils.safeReadFileSync(englishFile, path.dirname(englishFile), 'utf8');
|
|
389
435
|
const englishTranslations = JSON.parse(englishContent);
|
|
390
436
|
|
|
@@ -523,4 +569,4 @@ this.translations = {};
|
|
|
523
569
|
}
|
|
524
570
|
|
|
525
571
|
// Export the class, not a singleton instance
|
|
526
|
-
module.exports = UIi18n;
|
|
572
|
+
module.exports = UIi18n;
|
package/main/i18ntk-usage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* I18NTK USAGE ANALYSIS TOOLKIT - Version 1.8.3
|
|
4
4
|
*
|
|
@@ -55,7 +55,7 @@ const SetupEnforcer = require('../utils/setup-enforcer');
|
|
|
55
55
|
}
|
|
56
56
|
})();
|
|
57
57
|
|
|
58
|
-
loadTranslations(
|
|
58
|
+
loadTranslations('en', path.resolve(__dirname, '..', 'ui-locales'));
|
|
59
59
|
|
|
60
60
|
async function getConfig() {
|
|
61
61
|
return await getUnifiedConfig('usage');
|
|
@@ -120,7 +120,7 @@ class I18nUsageAnalyzer {
|
|
|
120
120
|
|
|
121
121
|
// Load translations for UI
|
|
122
122
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
123
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
123
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
124
124
|
const projectRoot = path.resolve(this.config.projectRoot || '.');
|
|
125
125
|
const detected = detectFramework(projectRoot);
|
|
126
126
|
if (detected) {
|
|
@@ -169,9 +169,9 @@ class I18nUsageAnalyzer {
|
|
|
169
169
|
this.config.includeExtensions = ['.js', '.jsx', '.ts', '.tsx', '.py', '.pyx', '.pyi'];
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
await SecurityUtils.logSecurityEvent(t('usage.analyzerInitialized'), { component: 'i18ntk-usage' });
|
|
172
|
+
await SecurityUtils.logSecurityEvent(t('usage.analyzerInitialized'), 'info', { component: 'i18ntk-usage' });
|
|
173
173
|
} catch (error) {
|
|
174
|
-
await SecurityUtils.logSecurityEvent(t('usage.analyzerInitFailed'), { component: 'i18ntk-usage', error: error.message });
|
|
174
|
+
await SecurityUtils.logSecurityEvent(t('usage.analyzerInitFailed'), 'error', { component: 'i18ntk-usage', error: error.message });
|
|
175
175
|
throw error;
|
|
176
176
|
}
|
|
177
177
|
}
|
|
@@ -276,7 +276,7 @@ class I18nUsageAnalyzer {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
} catch (error) {
|
|
279
|
-
await SecurityUtils.logSecurityEvent(t('usage.translationDiscoveryError'), {
|
|
279
|
+
await SecurityUtils.logSecurityEvent(t('usage.translationDiscoveryError'), 'error', {
|
|
280
280
|
component: 'i18ntk-usage',
|
|
281
281
|
directory: currentDir,
|
|
282
282
|
error: error.message
|
|
@@ -350,7 +350,7 @@ class I18nUsageAnalyzer {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
} catch (error) {
|
|
353
|
-
await SecurityUtils.logSecurityEvent(t('usage.fileTraversalError'), {
|
|
353
|
+
await SecurityUtils.logSecurityEvent(t('usage.fileTraversalError'), 'error', {
|
|
354
354
|
component: 'i18ntk-usage',
|
|
355
355
|
directory: currentDir,
|
|
356
356
|
error: error.message
|
|
@@ -393,7 +393,7 @@ class I18nUsageAnalyzer {
|
|
|
393
393
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
394
394
|
|
|
395
395
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
396
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
396
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
397
397
|
if (!Array.isArray(this.config.translationPatterns)) {
|
|
398
398
|
this.config.translationPatterns = [
|
|
399
399
|
/t\(['"`]([^'"`]+)['"`]/g,
|
|
@@ -614,7 +614,7 @@ class I18nUsageAnalyzer {
|
|
|
614
614
|
} catch (error) {
|
|
615
615
|
console.error(t('usage.analysisFailedError'), error.message);
|
|
616
616
|
this.closeReadline();
|
|
617
|
-
SecurityUtils.logSecurityEvent(t('usage.usageAnalysisFailed'), {
|
|
617
|
+
SecurityUtils.logSecurityEvent(t('usage.usageAnalysisFailed'), 'error', {
|
|
618
618
|
component: 'i18ntk-usage',
|
|
619
619
|
error: error.message
|
|
620
620
|
});
|
|
@@ -740,7 +740,7 @@ Analysis Features (v1.8.3):
|
|
|
740
740
|
if (isDebug) {
|
|
741
741
|
console.error(error.stack);
|
|
742
742
|
}
|
|
743
|
-
await SecurityUtils.logSecurityEvent(t('usage.translationFileParseError'), {
|
|
743
|
+
await SecurityUtils.logSecurityEvent(t('usage.translationFileParseError'), 'error', {
|
|
744
744
|
component: 'i18ntk-usage',
|
|
745
745
|
file: fileInfo.filePath,
|
|
746
746
|
error: error.message
|
|
@@ -748,7 +748,7 @@ Analysis Features (v1.8.3):
|
|
|
748
748
|
}
|
|
749
749
|
}
|
|
750
750
|
} catch (error) {
|
|
751
|
-
await SecurityUtils.logSecurityEvent(t('usage.translationKeysLoadError'), {
|
|
751
|
+
await SecurityUtils.logSecurityEvent(t('usage.translationKeysLoadError'), 'error', {
|
|
752
752
|
component: 'i18ntk-usage',
|
|
753
753
|
error: error.message
|
|
754
754
|
});
|
|
@@ -1543,7 +1543,7 @@ Analysis Features (v1.8.3):
|
|
|
1543
1543
|
await this.initialize();
|
|
1544
1544
|
}
|
|
1545
1545
|
|
|
1546
|
-
await SecurityUtils.logSecurityEvent('analysis_started', { component: 'i18ntk-usage' });
|
|
1546
|
+
await SecurityUtils.logSecurityEvent('analysis_started', 'info', { component: 'i18ntk-usage' });
|
|
1547
1547
|
|
|
1548
1548
|
console.log(t('usage.checkUsage.title'));
|
|
1549
1549
|
console.log(t("usage.checkUsage.message"));
|
|
@@ -1708,7 +1708,7 @@ Analysis Features (v1.8.3):
|
|
|
1708
1708
|
outputLines.forEach(line => console.log(line));
|
|
1709
1709
|
}
|
|
1710
1710
|
|
|
1711
|
-
await SecurityUtils.logSecurityEvent('analysis_completed', {
|
|
1711
|
+
await SecurityUtils.logSecurityEvent('analysis_completed', 'info', {
|
|
1712
1712
|
component: 'i18ntk-usage',
|
|
1713
1713
|
stats: {
|
|
1714
1714
|
availableKeys: this.availableKeys.size,
|
|
@@ -1749,7 +1749,7 @@ Analysis Features (v1.8.3):
|
|
|
1749
1749
|
console.error(t("checkUsage.usage_analysis_failed"));
|
|
1750
1750
|
console.error(error.message);
|
|
1751
1751
|
|
|
1752
|
-
await SecurityUtils.logSecurityEvent('analysis_failed', {
|
|
1752
|
+
await SecurityUtils.logSecurityEvent('analysis_failed', 'error', {
|
|
1753
1753
|
component: 'i18ntk-usage',
|
|
1754
1754
|
error: error.message
|
|
1755
1755
|
});
|
package/main/i18ntk-validate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* I18NTK TRANSLATION VALIDATION TOOLKIT
|
|
@@ -60,7 +60,7 @@ const SetupEnforcer = require('../utils/setup-enforcer');
|
|
|
60
60
|
}
|
|
61
61
|
})();
|
|
62
62
|
|
|
63
|
-
loadTranslations(
|
|
63
|
+
loadTranslations('en', path.resolve(__dirname, '..', 'ui-locales'));
|
|
64
64
|
|
|
65
65
|
class I18nValidator {
|
|
66
66
|
constructor(config = {}) {
|
|
@@ -87,7 +87,7 @@ class I18nValidator {
|
|
|
87
87
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
88
88
|
|
|
89
89
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
90
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
90
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
91
91
|
|
|
92
92
|
SecurityUtils.logSecurityEvent(
|
|
93
93
|
'I18n validator initializing',
|
|
@@ -892,7 +892,8 @@ class I18nValidator {
|
|
|
892
892
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
893
893
|
|
|
894
894
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
895
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
895
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
896
|
+
this.sourceDir = this.config.sourceDir;
|
|
896
897
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
897
898
|
} else {
|
|
898
899
|
await this.initialize();
|
|
@@ -983,7 +984,7 @@ if (require.main === module) {
|
|
|
983
984
|
// Initialize translations for CLI usage
|
|
984
985
|
const config = configManager.getConfig();
|
|
985
986
|
const uiLanguage = config.language || 'en';
|
|
986
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
987
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
987
988
|
|
|
988
989
|
SecurityUtils.logSecurityEvent(t('validate.scriptExecution'), 'info', {
|
|
989
990
|
script: 'i18ntk-validate.js',
|
|
@@ -11,12 +11,12 @@ const path = require('path');
|
|
|
11
11
|
const cliHelper = require('../../../utils/cli-helper');
|
|
12
12
|
const { loadTranslations, t } = require('../../../utils/i18n-helper');
|
|
13
13
|
const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../../../utils/config-helper');
|
|
14
|
-
const SecurityUtils = require('../../../utils/security');
|
|
15
|
-
const AdminCLI = require('../../../utils/admin-cli');
|
|
16
|
-
const watchLocales = require('../../../utils/watch-locales');
|
|
17
|
-
const JsonOutput = require('../../../utils/json-output');
|
|
18
|
-
|
|
19
|
-
loadTranslations('en', path.resolve(__dirname, '../../../
|
|
14
|
+
const SecurityUtils = require('../../../utils/security');
|
|
15
|
+
const AdminCLI = require('../../../utils/admin-cli');
|
|
16
|
+
const watchLocales = require('../../../utils/watch-locales');
|
|
17
|
+
const JsonOutput = require('../../../utils/json-output');
|
|
18
|
+
|
|
19
|
+
loadTranslations('en', path.resolve(__dirname, '../../../ui-locales'));
|
|
20
20
|
|
|
21
21
|
const PROJECT_ROOT = process.cwd();
|
|
22
22
|
|
|
@@ -74,7 +74,7 @@ class AnalyzeCommand {
|
|
|
74
74
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
75
75
|
|
|
76
76
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
77
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
77
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
78
78
|
|
|
79
79
|
this.sourceDir = this.config.sourceDir;
|
|
80
80
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
@@ -150,6 +150,17 @@ class AnalyzeCommand {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// Get all available languages
|
|
153
|
+
isValidLanguageCode(code) {
|
|
154
|
+
if (!code || typeof code !== 'string') return false;
|
|
155
|
+
return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
isExcludedLanguageDirectory(name) {
|
|
159
|
+
if (!name || typeof name !== 'string') return true;
|
|
160
|
+
const lowered = name.toLowerCase();
|
|
161
|
+
return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
getAvailableLanguages() {
|
|
154
165
|
try {
|
|
155
166
|
const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
|
|
@@ -164,7 +175,18 @@ class AnalyzeCommand {
|
|
|
164
175
|
const directories = items
|
|
165
176
|
.filter(item => item.isDirectory())
|
|
166
177
|
.map(item => item.name)
|
|
167
|
-
.filter(name =>
|
|
178
|
+
.filter(name =>
|
|
179
|
+
name !== 'node_modules' &&
|
|
180
|
+
!name.startsWith('.') &&
|
|
181
|
+
name !== this.config.sourceLanguage &&
|
|
182
|
+
!this.isExcludedLanguageDirectory(name) &&
|
|
183
|
+
this.isValidLanguageCode(name)
|
|
184
|
+
)
|
|
185
|
+
.filter(name => {
|
|
186
|
+
const dirPath = path.join(this.sourceDir, name);
|
|
187
|
+
const dirItems = SecurityUtils.safeReaddirSync(dirPath, process.cwd(), { withFileTypes: true }) || [];
|
|
188
|
+
return dirItems.some(item => item.isFile() && item.name.endsWith('.json'));
|
|
189
|
+
});
|
|
168
190
|
|
|
169
191
|
// Check for monolith files (language.json files)
|
|
170
192
|
const files = items
|
|
@@ -177,31 +199,14 @@ class AnalyzeCommand {
|
|
|
177
199
|
// Add monolith files as languages (without .json extension)
|
|
178
200
|
const monolithLanguages = files
|
|
179
201
|
.map(file => file.replace('.json', ''))
|
|
180
|
-
.filter(lang =>
|
|
202
|
+
.filter(lang =>
|
|
203
|
+
!languages.includes(lang) &&
|
|
204
|
+
lang !== this.config.sourceLanguage &&
|
|
205
|
+
!this.isExcludedLanguageDirectory(lang) &&
|
|
206
|
+
this.isValidLanguageCode(lang)
|
|
207
|
+
);
|
|
181
208
|
languages.push(...monolithLanguages);
|
|
182
209
|
|
|
183
|
-
// Check for nested structures
|
|
184
|
-
for (const dir of directories) {
|
|
185
|
-
const dirPath = path.join(this.sourceDir, dir);
|
|
186
|
-
try {
|
|
187
|
-
const dirItems = SecurityUtils.safeReaddirSync(dirPath, process.cwd(), { withFileTypes: true });
|
|
188
|
-
if (dirItems) {
|
|
189
|
-
const jsonFiles = dirItems
|
|
190
|
-
.filter(item => item.isFile() && item.name.endsWith('.json'))
|
|
191
|
-
.map(item => item.name.replace('.json', ''));
|
|
192
|
-
|
|
193
|
-
// If directory contains JSON files, it's likely a language directory
|
|
194
|
-
if (jsonFiles.length > 0) {
|
|
195
|
-
if (!languages.includes(dir)) {
|
|
196
|
-
languages.push(dir);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
} catch (error) {
|
|
201
|
-
// Skip directories we can't read
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
210
|
return [...new Set(languages)].sort();
|
|
206
211
|
} catch (error) {
|
|
207
212
|
console.error('Error reading source directory:', error.message);
|
|
@@ -714,7 +719,6 @@ class AnalyzeCommand {
|
|
|
714
719
|
throw new Error(t('analyze.failedToWriteReportFile') || 'Failed to write report file securely');
|
|
715
720
|
}
|
|
716
721
|
|
|
717
|
-
console.log(`Report saved to: ${reportPath}`);
|
|
718
722
|
return reportPath;
|
|
719
723
|
|
|
720
724
|
} catch (error) {
|
|
@@ -787,13 +791,14 @@ class AnalyzeCommand {
|
|
|
787
791
|
|
|
788
792
|
// Save report
|
|
789
793
|
const reportPath = await this.saveReport(language, report);
|
|
794
|
+
const processedCount = results.length + 1;
|
|
790
795
|
|
|
791
796
|
if (!args.json) {
|
|
792
797
|
console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
|
|
793
798
|
console.log(t('analyze.progress', {
|
|
794
|
-
translated:
|
|
799
|
+
translated: processedCount,
|
|
795
800
|
total: languages.length
|
|
796
|
-
}) || ` Progress: ${
|
|
801
|
+
}) || ` Progress: ${processedCount}/${languages.length} languages processed`);
|
|
797
802
|
console.log(t('analyze.reportSaved', { reportPath }) || ` Report saved: ${reportPath}`);
|
|
798
803
|
}
|
|
799
804
|
|
|
@@ -891,7 +896,7 @@ class AnalyzeCommand {
|
|
|
891
896
|
this.config = { ...baseConfig, ...this.config };
|
|
892
897
|
|
|
893
898
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
894
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
899
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
895
900
|
|
|
896
901
|
this.sourceDir = this.config.sourceDir;
|
|
897
902
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
@@ -64,7 +64,7 @@ class FixerCommand {
|
|
|
64
64
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
65
65
|
|
|
66
66
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
67
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
67
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
68
68
|
|
|
69
69
|
this.sourceDir = this.config.sourceDir;
|
|
70
70
|
this.outputDir = this.config.outputDir;
|
|
@@ -567,7 +567,7 @@ class FixerCommand {
|
|
|
567
567
|
this.config = { ...baseConfig, ...this.config };
|
|
568
568
|
|
|
569
569
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
570
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
570
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
571
571
|
|
|
572
572
|
this.sourceDir = this.config.sourceDir;
|
|
573
573
|
this.outputDir = this.config.outputDir;
|