i18ntk 2.1.0 → 2.3.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 +87 -50
- package/main/i18ntk-analyze.js +63 -63
- package/main/i18ntk-backup-class.js +37 -41
- package/main/i18ntk-backup.js +28 -30
- package/main/i18ntk-complete.js +75 -74
- package/main/i18ntk-doctor.js +7 -6
- package/main/i18ntk-fixer.js +3 -3
- package/main/i18ntk-init.js +49 -13
- package/main/i18ntk-scanner.js +2 -2
- package/main/i18ntk-sizing.js +36 -37
- package/main/i18ntk-summary.js +4 -4
- package/main/i18ntk-ui.js +95 -96
- package/main/i18ntk-usage.js +31 -19
- package/main/i18ntk-validate.js +78 -27
- package/main/manage/commands/AnalyzeCommand.js +71 -73
- package/main/manage/commands/CommandRouter.js +15 -12
- package/main/manage/commands/FixerCommand.js +94 -38
- package/main/manage/commands/ScannerCommand.js +2 -2
- package/main/manage/commands/ValidateCommand.js +87 -36
- package/main/manage/index.js +165 -152
- package/main/manage/managers/DebugMenu.js +6 -6
- package/main/manage/managers/InteractiveMenu.js +6 -6
- package/main/manage/managers/LanguageMenu.js +12 -6
- package/main/manage/managers/SettingsMenu.js +6 -6
- package/main/manage/services/AuthenticationService.js +5 -6
- package/main/manage/services/ConfigurationService.js +22 -34
- package/main/manage/services/FileManagementService.js +6 -6
- package/main/manage/services/InitService.js +44 -8
- package/main/manage/services/UsageService.js +24 -12
- package/package.json +21 -42
- package/settings/settings-cli.js +5 -5
- package/settings/settings-manager.js +984 -968
- package/ui-locales/de.json +12 -11
- package/ui-locales/en.json +12 -11
- package/ui-locales/es.json +12 -11
- package/ui-locales/fr.json +12 -11
- package/ui-locales/ja.json +12 -11
- package/ui-locales/ru.json +12 -11
- package/ui-locales/zh.json +12 -11
- package/utils/config-helper.js +27 -16
- package/utils/config-manager.js +8 -7
- package/utils/i18n-helper.js +161 -166
- package/utils/init-helper.js +3 -2
- package/utils/json-output.js +11 -10
- package/{scripts → utils}/locale-optimizer.js +61 -60
- package/utils/logger.js +4 -4
- package/utils/safe-json.js +3 -3
- package/utils/secure-backup.js +8 -7
- package/utils/setup-enforcer.js +63 -98
- 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/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 -236
- package/scripts/fix-and-purify-i18n.js +0 -233
- package/scripts/fix-locale-control-chars.js +0 -110
- package/scripts/lint-locales.js +0 -80
- package/scripts/prepublish-dev.js +0 -221
- package/scripts/prepublish.js +0 -362
- 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 -195
- package/scripts/verify-deprecations.js +0 -157
- package/scripts/verify-translations.js +0 -63
- package/utils/security-fixed.js +0 -609
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
|
|
@@ -37,6 +37,7 @@ const { loadTranslations, t } = require('../utils/i18n-helper');
|
|
|
37
37
|
const configManager = require('../settings/settings-manager');
|
|
38
38
|
const SecurityUtils = require('../utils/security');
|
|
39
39
|
const { getUnifiedConfig } = require('../utils/config-helper');
|
|
40
|
+
const { logger } = require('../utils/logger');
|
|
40
41
|
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
41
42
|
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
42
43
|
|
|
@@ -83,7 +84,7 @@ class I18nSizingAnalyzer {
|
|
|
83
84
|
|
|
84
85
|
// Initialize i18n with UI language from config
|
|
85
86
|
const uiLanguage = options.uiLanguage || config.uiLanguage || 'en';
|
|
86
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
87
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
87
88
|
this.stats = {
|
|
88
89
|
files: {},
|
|
89
90
|
languages: {},
|
|
@@ -165,7 +166,7 @@ class I18nSizingAnalyzer {
|
|
|
165
166
|
|
|
166
167
|
// Analyze file sizes
|
|
167
168
|
analyzeFileSizes(files) {
|
|
168
|
-
|
|
169
|
+
logger.info(t("sizing.analyzing_file_sizes"));
|
|
169
170
|
|
|
170
171
|
files.forEach(({ language, file, path: filePath, files: langFiles }) => {
|
|
171
172
|
if (langFiles) {
|
|
@@ -220,7 +221,7 @@ class I18nSizingAnalyzer {
|
|
|
220
221
|
|
|
221
222
|
// Analyze translation content
|
|
222
223
|
analyzeTranslationContent(files) {
|
|
223
|
-
|
|
224
|
+
logger.info(t("sizing.analyzing_translation_content"));
|
|
224
225
|
|
|
225
226
|
files.forEach(({ language, path: filePath, files: langFiles }) => {
|
|
226
227
|
try {
|
|
@@ -266,7 +267,7 @@ class I18nSizingAnalyzer {
|
|
|
266
267
|
});
|
|
267
268
|
|
|
268
269
|
} catch (error) {
|
|
269
|
-
|
|
270
|
+
logger.error(t("sizing.failed_to_parse_language_error", { language, errorMessage: error.message }));
|
|
270
271
|
}
|
|
271
272
|
});
|
|
272
273
|
}
|
|
@@ -316,14 +317,14 @@ class I18nSizingAnalyzer {
|
|
|
316
317
|
|
|
317
318
|
// Generate size comparison analysis
|
|
318
319
|
generateSizeComparison() {
|
|
319
|
-
|
|
320
|
+
logger.info(t("sizing.generating_size_comparisons"));
|
|
320
321
|
|
|
321
322
|
const languages = Object.keys(this.stats.languages);
|
|
322
323
|
const baseLanguage = languages[0]; // Use first language as baseline
|
|
323
324
|
|
|
324
325
|
if (!baseLanguage) {
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
logger.warn(t("sizing.no_languages_found_for_comparison"));
|
|
327
|
+
return;
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
this.stats.summary = {
|
|
@@ -495,7 +496,7 @@ class I18nSizingAnalyzer {
|
|
|
495
496
|
async generateHumanReadableReport() {
|
|
496
497
|
if (!this.outputReport) return;
|
|
497
498
|
|
|
498
|
-
|
|
499
|
+
logger.info(t("sizing.generating_detailed_report"));
|
|
499
500
|
|
|
500
501
|
const validatedOutputDir = SecurityUtils.validatePath(this.outputDir, process.cwd());
|
|
501
502
|
if (!validatedOutputDir) {
|
|
@@ -518,7 +519,7 @@ class I18nSizingAnalyzer {
|
|
|
518
519
|
let textReport = this.generateTextReport(timestamp);
|
|
519
520
|
const textSuccess = await SecurityUtils.safeWriteFile(textReportPath, textReport, process.cwd());
|
|
520
521
|
if (textSuccess) {
|
|
521
|
-
|
|
522
|
+
logger.info(t("sizing.human_report_saved", { reportPath: textReportPath }));
|
|
522
523
|
}
|
|
523
524
|
|
|
524
525
|
// Generate JSON for programmatic access
|
|
@@ -664,8 +665,8 @@ Generated: ${new Date().toISOString()}
|
|
|
664
665
|
|
|
665
666
|
const success = await SecurityUtils.safeWriteFile(csvPath, csvContent, process.cwd());
|
|
666
667
|
if (success) {
|
|
667
|
-
|
|
668
|
-
|
|
668
|
+
logger.info(t("sizing.csv_report_saved_to", { csvPath }));
|
|
669
|
+
SecurityUtils.logSecurityEvent('CSV report saved', 'info', { csvPath });
|
|
669
670
|
} else {
|
|
670
671
|
throw new Error(t("sizing.failedToSaveCsvError"));
|
|
671
672
|
}
|
|
@@ -673,21 +674,20 @@ Generated: ${new Date().toISOString()}
|
|
|
673
674
|
|
|
674
675
|
// Main analysis method
|
|
675
676
|
async analyze() {
|
|
676
|
-
const
|
|
677
|
-
const startTime = perfTimer.now();
|
|
677
|
+
const startTime = Date.now();
|
|
678
678
|
|
|
679
679
|
try {
|
|
680
|
-
|
|
681
|
-
|
|
680
|
+
logger.info(t("sizing.starting_i18n_sizing_analysis"));
|
|
681
|
+
logger.info(t("sizing.source_directory", { sourceDir: this.sourceDir }));
|
|
682
682
|
|
|
683
683
|
const files = this.getLanguageFiles();
|
|
684
684
|
|
|
685
685
|
if (files.length === 0) {
|
|
686
|
-
|
|
687
|
-
|
|
686
|
+
logger.warn(t("sizing.no_translation_files_found"));
|
|
687
|
+
return;
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
|
|
690
|
+
logger.info(t("sizing.found_languages", { languages: files.map(f => f.language).join(', ') }));
|
|
691
691
|
|
|
692
692
|
this.analyzeFileSizes(files);
|
|
693
693
|
this.analyzeTranslationContent(files);
|
|
@@ -696,17 +696,17 @@ Generated: ${new Date().toISOString()}
|
|
|
696
696
|
if (this.format === 'table') {
|
|
697
697
|
this.displayFolderResults();
|
|
698
698
|
} else if (this.format === 'json') {
|
|
699
|
-
|
|
699
|
+
logger.info(t("sizing.analysisStats", { stats: JSON.stringify(this.stats, null, 2) }));
|
|
700
700
|
}
|
|
701
701
|
|
|
702
702
|
await this.generateHumanReadableReport();
|
|
703
703
|
|
|
704
|
-
const endTime =
|
|
705
|
-
|
|
704
|
+
const endTime = Date.now();
|
|
705
|
+
logger.info(t("sizing.analysis_completed", { duration: ((endTime - startTime) / 1000).toFixed(2) }));
|
|
706
706
|
|
|
707
707
|
} catch (error) {
|
|
708
|
-
|
|
709
|
-
|
|
708
|
+
logger.error(t("sizing.analysis_failed", { errorMessage: error.message }));
|
|
709
|
+
process.exit(1);
|
|
710
710
|
}
|
|
711
711
|
}
|
|
712
712
|
|
|
@@ -906,20 +906,19 @@ Options:
|
|
|
906
906
|
// Main analysis method
|
|
907
907
|
async analyze() {
|
|
908
908
|
try {
|
|
909
|
-
|
|
910
|
-
|
|
909
|
+
logger.info(t("sizing.starting_analysis"));
|
|
910
|
+
logger.info(t("sizing.source_directory", { sourceDir: this.sourceDir }));
|
|
911
911
|
|
|
912
|
-
const
|
|
913
|
-
const startTime = perfTimer.now();
|
|
912
|
+
const startTime = Date.now();
|
|
914
913
|
|
|
915
914
|
// Get language files
|
|
916
915
|
const files = this.getLanguageFiles();
|
|
917
916
|
if (files.length === 0) {
|
|
918
|
-
|
|
919
|
-
|
|
917
|
+
logger.warn(t("sizing.no_translation_files_found"));
|
|
918
|
+
return { success: false, error: "No translation files found" };
|
|
920
919
|
}
|
|
921
920
|
|
|
922
|
-
|
|
921
|
+
logger.info(t("sizing.found_files", { count: files.length }));
|
|
923
922
|
|
|
924
923
|
// Analyze file sizes
|
|
925
924
|
this.analyzeFileSizes(files);
|
|
@@ -936,16 +935,16 @@ Options:
|
|
|
936
935
|
// Generate reports if requested
|
|
937
936
|
await this.generateHumanReadableReport();
|
|
938
937
|
|
|
939
|
-
const endTime =
|
|
940
|
-
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
938
|
+
const endTime = Date.now();
|
|
939
|
+
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
941
940
|
|
|
942
|
-
|
|
941
|
+
logger.info(t("sizing.analysis_completed", { duration }));
|
|
943
942
|
|
|
944
943
|
return { success: true, stats: this.stats };
|
|
945
944
|
|
|
946
945
|
} catch (error) {
|
|
947
|
-
|
|
948
|
-
|
|
946
|
+
logger.error(t("sizing.analysis_failed", { errorMessage: error.message }));
|
|
947
|
+
return { success: false, error: error.message };
|
|
949
948
|
}
|
|
950
949
|
}
|
|
951
950
|
}
|
|
@@ -961,4 +960,4 @@ if (require.main === module) {
|
|
|
961
960
|
});
|
|
962
961
|
}
|
|
963
962
|
|
|
964
|
-
module.exports = I18nSizingAnalyzer;
|
|
963
|
+
module.exports = I18nSizingAnalyzer;
|
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
|
@@ -11,13 +11,13 @@ const legacyConfigManager = require('../utils/config-manager');
|
|
|
11
11
|
const { getIcon, isUnicodeSupported } = require('../utils/terminal-icons');
|
|
12
12
|
const configManager = new SettingsManager();
|
|
13
13
|
|
|
14
|
-
class UIi18n {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.currentLanguage = 'en';
|
|
17
|
-
this.translations = {};
|
|
18
|
-
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
19
|
-
this.availableLanguages = [];
|
|
20
|
-
this.configFile = path.resolve(configManager.configFile);
|
|
14
|
+
class UIi18n {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.currentLanguage = 'en';
|
|
17
|
+
this.translations = {};
|
|
18
|
+
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
19
|
+
this.availableLanguages = [];
|
|
20
|
+
this.configFile = path.resolve(configManager.configFile);
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
// Initialize with safe defaults
|
|
@@ -25,13 +25,13 @@ this.translations = {};
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
initialize() {
|
|
29
|
-
try {
|
|
30
|
-
const config = configManager.loadSettings ? configManager.loadSettings() : configManager.getConfig ? configManager.getConfig() : {};
|
|
31
|
-
|
|
32
|
-
// Use safe defaults if config is not available
|
|
33
|
-
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
34
|
-
this.availableLanguages = this.detectAvailableLanguages();
|
|
28
|
+
initialize() {
|
|
29
|
+
try {
|
|
30
|
+
const config = configManager.loadSettings ? configManager.loadSettings() : configManager.getConfig ? configManager.getConfig() : {};
|
|
31
|
+
|
|
32
|
+
// Use safe defaults if config is not available
|
|
33
|
+
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
34
|
+
this.availableLanguages = this.detectAvailableLanguages();
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
const configuredLanguage = config?.language || config?.uiLanguage || 'en';
|
|
@@ -42,71 +42,70 @@ this.translations = {};
|
|
|
42
42
|
} else {
|
|
43
43
|
this.loadLanguage('en');
|
|
44
44
|
}
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.warn('UIi18n: Failed to initialize with config, using defaults:', error.message);
|
|
47
|
-
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
48
|
-
this.availableLanguages = this.detectAvailableLanguages();
|
|
49
|
-
this.loadLanguage('en');
|
|
50
|
-
}
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn('UIi18n: Failed to initialize with config, using defaults:', error.message);
|
|
47
|
+
this.uiLocalesDir = this.resolveUiLocalesDir();
|
|
48
|
+
this.availableLanguages = this.detectAvailableLanguages();
|
|
49
|
+
this.loadLanguage('en');
|
|
50
|
+
}
|
|
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
|
+
}
|
|
98
97
|
/**
|
|
99
98
|
/**
|
|
100
99
|
* Detect which UI locales are currently installed
|
|
101
100
|
* @returns {string[]} Array of available language codes
|
|
102
101
|
*/
|
|
103
|
-
detectAvailableLanguages() {
|
|
104
|
-
const all = ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'];
|
|
105
|
-
return all.filter(lang => {
|
|
106
|
-
const filePath = path.join(this.uiLocalesDir, `${lang}.json`);
|
|
107
|
-
return SecurityUtils.safeExistsSync(filePath, this.getValidationBase(filePath));
|
|
108
|
-
});
|
|
109
|
-
}
|
|
102
|
+
detectAvailableLanguages() {
|
|
103
|
+
const all = ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'];
|
|
104
|
+
return all.filter(lang => {
|
|
105
|
+
const filePath = path.join(this.uiLocalesDir, `${lang}.json`);
|
|
106
|
+
return SecurityUtils.safeExistsSync(filePath, this.getValidationBase(filePath));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
110
109
|
|
|
111
110
|
/**
|
|
112
111
|
* Refresh the list of available languages and ensure current language is valid
|
|
@@ -137,13 +136,13 @@ this.translations = {};
|
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
try {
|
|
140
|
-
// Primary: Use monolith JSON file (en.json, de.json, etc.)
|
|
141
|
-
const monolithTranslationFile = path.join(this.uiLocalesDir, `${language}.json`);
|
|
142
|
-
|
|
143
|
-
if (SecurityUtils.safeExistsSync(monolithTranslationFile, this.getValidationBase(monolithTranslationFile))) {
|
|
144
|
-
try {
|
|
145
|
-
const content = SecurityUtils.safeReadFileSync(monolithTranslationFile, path.dirname(monolithTranslationFile), 'utf8');
|
|
146
|
-
const fullTranslations = JSON.parse(content);
|
|
139
|
+
// Primary: Use monolith JSON file (en.json, de.json, etc.)
|
|
140
|
+
const monolithTranslationFile = path.join(this.uiLocalesDir, `${language}.json`);
|
|
141
|
+
|
|
142
|
+
if (SecurityUtils.safeExistsSync(monolithTranslationFile, this.getValidationBase(monolithTranslationFile))) {
|
|
143
|
+
try {
|
|
144
|
+
const content = SecurityUtils.safeReadFileSync(monolithTranslationFile, path.dirname(monolithTranslationFile), 'utf8');
|
|
145
|
+
const fullTranslations = JSON.parse(content);
|
|
147
146
|
|
|
148
147
|
// Flatten the nested structure for easier key access
|
|
149
148
|
this.translations = this.flattenTranslations(fullTranslations);
|
|
@@ -157,14 +156,14 @@ this.translations = {};
|
|
|
157
156
|
this.translations = {};
|
|
158
157
|
}
|
|
159
158
|
} else {
|
|
160
|
-
// Fallback: Use folder-based structure if monolith file doesn't exist
|
|
161
|
-
const langDir = path.join(this.uiLocalesDir, language);
|
|
162
|
-
|
|
163
|
-
const langDirStats = SecurityUtils.safeStatSync(langDir, this.getValidationBase(langDir));
|
|
164
|
-
if (langDirStats && langDirStats.isDirectory()) {
|
|
165
|
-
const files = fs.readdirSync(langDir).filter(file => file.endsWith('.json'));
|
|
166
|
-
if (debugEnabled) {
|
|
167
|
-
console.log(`UI: Found files in ${langDir}: ${files.join(', ')}`);
|
|
159
|
+
// Fallback: Use folder-based structure if monolith file doesn't exist
|
|
160
|
+
const langDir = path.join(this.uiLocalesDir, language);
|
|
161
|
+
|
|
162
|
+
const langDirStats = SecurityUtils.safeStatSync(langDir, this.getValidationBase(langDir));
|
|
163
|
+
if (langDirStats && langDirStats.isDirectory()) {
|
|
164
|
+
const files = fs.readdirSync(langDir).filter(file => file.endsWith('.json'));
|
|
165
|
+
if (debugEnabled) {
|
|
166
|
+
console.log(`UI: Found files in ${langDir}: ${files.join(', ')}`);
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
for (const file of files) {
|
|
@@ -428,12 +427,12 @@ this.translations = {};
|
|
|
428
427
|
* @param {object} replacements - Object with replacement values
|
|
429
428
|
* @returns {string|null} English translation or null if not found
|
|
430
429
|
*/
|
|
431
|
-
getEnglishFallback(keyPath, replacements = {}) {
|
|
432
|
-
try {
|
|
433
|
-
const englishFile = path.join(this.uiLocalesDir, 'en.json');
|
|
434
|
-
if (SecurityUtils.safeExistsSync(englishFile, this.getValidationBase(englishFile))) {
|
|
435
|
-
const englishContent = SecurityUtils.safeReadFileSync(englishFile, path.dirname(englishFile), 'utf8');
|
|
436
|
-
const englishTranslations = JSON.parse(englishContent);
|
|
430
|
+
getEnglishFallback(keyPath, replacements = {}) {
|
|
431
|
+
try {
|
|
432
|
+
const englishFile = path.join(this.uiLocalesDir, 'en.json');
|
|
433
|
+
if (SecurityUtils.safeExistsSync(englishFile, this.getValidationBase(englishFile))) {
|
|
434
|
+
const englishContent = SecurityUtils.safeReadFileSync(englishFile, path.dirname(englishFile), 'utf8');
|
|
435
|
+
const englishTranslations = JSON.parse(englishContent);
|
|
437
436
|
|
|
438
437
|
// Use the same flattening approach for consistency
|
|
439
438
|
const flattenedEnglish = this.flattenTranslations(englishTranslations);
|
|
@@ -570,4 +569,4 @@ this.translations = {};
|
|
|
570
569
|
}
|
|
571
570
|
|
|
572
571
|
// Export the class, not a singleton instance
|
|
573
|
-
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
|
});
|
|
@@ -786,10 +786,22 @@ Analysis Features (v1.8.3):
|
|
|
786
786
|
return keys;
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
-
collectPlaceholderKeys(obj, prefix = '', language) {
|
|
790
|
-
const patterns = this.placeholderStyles[language] || [];
|
|
791
|
-
const regexes = patterns.
|
|
792
|
-
|
|
789
|
+
collectPlaceholderKeys(obj, prefix = '', language) {
|
|
790
|
+
const patterns = this.placeholderStyles[language] || [];
|
|
791
|
+
const regexes = patterns.reduce((compiled, pattern) => {
|
|
792
|
+
try {
|
|
793
|
+
compiled.push(new RegExp(pattern));
|
|
794
|
+
} catch (error) {
|
|
795
|
+
SecurityUtils.logSecurityEvent('Invalid placeholder regex pattern skipped', 'warn', {
|
|
796
|
+
component: 'i18ntk-usage',
|
|
797
|
+
language,
|
|
798
|
+
pattern,
|
|
799
|
+
error: error.message
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return compiled;
|
|
803
|
+
}, []);
|
|
804
|
+
if (typeof obj !== 'object' || obj === null) return;
|
|
793
805
|
|
|
794
806
|
for (const [key, value] of Object.entries(obj)) {
|
|
795
807
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
@@ -1543,7 +1555,7 @@ Analysis Features (v1.8.3):
|
|
|
1543
1555
|
await this.initialize();
|
|
1544
1556
|
}
|
|
1545
1557
|
|
|
1546
|
-
await SecurityUtils.logSecurityEvent('analysis_started', { component: 'i18ntk-usage' });
|
|
1558
|
+
await SecurityUtils.logSecurityEvent('analysis_started', 'info', { component: 'i18ntk-usage' });
|
|
1547
1559
|
|
|
1548
1560
|
console.log(t('usage.checkUsage.title'));
|
|
1549
1561
|
console.log(t("usage.checkUsage.message"));
|
|
@@ -1708,7 +1720,7 @@ Analysis Features (v1.8.3):
|
|
|
1708
1720
|
outputLines.forEach(line => console.log(line));
|
|
1709
1721
|
}
|
|
1710
1722
|
|
|
1711
|
-
await SecurityUtils.logSecurityEvent('analysis_completed', {
|
|
1723
|
+
await SecurityUtils.logSecurityEvent('analysis_completed', 'info', {
|
|
1712
1724
|
component: 'i18ntk-usage',
|
|
1713
1725
|
stats: {
|
|
1714
1726
|
availableKeys: this.availableKeys.size,
|
|
@@ -1749,7 +1761,7 @@ Analysis Features (v1.8.3):
|
|
|
1749
1761
|
console.error(t("checkUsage.usage_analysis_failed"));
|
|
1750
1762
|
console.error(error.message);
|
|
1751
1763
|
|
|
1752
|
-
await SecurityUtils.logSecurityEvent('analysis_failed', {
|
|
1764
|
+
await SecurityUtils.logSecurityEvent('analysis_failed', 'error', {
|
|
1753
1765
|
component: 'i18ntk-usage',
|
|
1754
1766
|
error: error.message
|
|
1755
1767
|
});
|
|
@@ -1837,4 +1849,4 @@ if (require.main === module) {
|
|
|
1837
1849
|
// Normal direct execution
|
|
1838
1850
|
main();
|
|
1839
1851
|
}
|
|
1840
|
-
}
|
|
1852
|
+
}
|