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-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',
|
|
@@ -186,18 +186,31 @@ class I18nValidator {
|
|
|
186
186
|
this.warnings.push({ message, details, type: 'warning' });
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
// Get all available languages
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
// Get all available languages
|
|
190
|
+
isExcludedLanguageDirectory(name) {
|
|
191
|
+
if (!name || typeof name !== 'string') return true;
|
|
192
|
+
const lowered = name.toLowerCase();
|
|
193
|
+
return lowered.startsWith('backup-') ||
|
|
194
|
+
lowered === 'backup' ||
|
|
195
|
+
lowered === 'backups' ||
|
|
196
|
+
lowered === 'i18ntk-backups' ||
|
|
197
|
+
lowered === 'reports' ||
|
|
198
|
+
lowered === 'i18ntk-reports';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getAvailableLanguages() {
|
|
202
|
+
try {
|
|
203
|
+
if (!SecurityUtils.safeExistsSync(this.sourceDir)) {
|
|
204
|
+
throw new Error(`Source directory not found: ${this.sourceDir}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const languages = fs.readdirSync(this.sourceDir)
|
|
208
|
+
.filter(item => {
|
|
209
|
+
const itemPath = path.join(this.sourceDir, item);
|
|
210
|
+
return fs.statSync(itemPath).isDirectory() &&
|
|
211
|
+
item !== this.config.sourceLanguage &&
|
|
212
|
+
!this.isExcludedLanguageDirectory(item);
|
|
213
|
+
});
|
|
201
214
|
|
|
202
215
|
return languages;
|
|
203
216
|
} catch (error) {
|
|
@@ -620,10 +633,42 @@ class I18nValidator {
|
|
|
620
633
|
return warnings;
|
|
621
634
|
}
|
|
622
635
|
|
|
623
|
-
// Show help message
|
|
624
|
-
showHelp() {
|
|
625
|
-
console.log(t('validate.help_message'));
|
|
626
|
-
}
|
|
636
|
+
// Show help message
|
|
637
|
+
showHelp() {
|
|
638
|
+
console.log(t('validate.help_message'));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
saveValidationSummaryReport(results = {}, success = true) {
|
|
642
|
+
try {
|
|
643
|
+
const outputDir = path.resolve(this.config.outputDir || './i18ntk-reports');
|
|
644
|
+
SecurityUtils.safeMkdirSync(outputDir, process.cwd(), { recursive: true });
|
|
645
|
+
|
|
646
|
+
const timestamp = new Date().toISOString();
|
|
647
|
+
const safeTimestamp = timestamp.replace(/[:.]/g, '-');
|
|
648
|
+
const reportPath = path.join(outputDir, `validation-summary-${safeTimestamp}.txt`);
|
|
649
|
+
|
|
650
|
+
const lines = [];
|
|
651
|
+
lines.push('I18NTK Validation Summary');
|
|
652
|
+
lines.push('========================');
|
|
653
|
+
lines.push(`Generated: ${timestamp}`);
|
|
654
|
+
lines.push(`Result: ${success ? 'PASS' : 'FAIL'}`);
|
|
655
|
+
lines.push(`Errors: ${this.errors.length}`);
|
|
656
|
+
lines.push(`Warnings: ${this.warnings.length}`);
|
|
657
|
+
lines.push('');
|
|
658
|
+
lines.push('Language Results');
|
|
659
|
+
lines.push('----------------');
|
|
660
|
+
|
|
661
|
+
Object.entries(results).forEach(([language, validation]) => {
|
|
662
|
+
const summary = validation?.summary || {};
|
|
663
|
+
lines.push(`${language}: ${summary.percentage || 0}% (${summary.translatedKeys || 0}/${summary.totalKeys || 0})`);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
SecurityUtils.safeWriteFileSync(reportPath, lines.join('\n') + '\n', process.cwd(), 'utf8');
|
|
667
|
+
return reportPath;
|
|
668
|
+
} catch (error) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
627
672
|
|
|
628
673
|
// Main validation process
|
|
629
674
|
async validate() {
|
|
@@ -854,11 +899,16 @@ class I18nValidator {
|
|
|
854
899
|
console.log(t('validate.considerRunningUsageAnalysis'));
|
|
855
900
|
}
|
|
856
901
|
|
|
857
|
-
// Exit with appropriate code
|
|
858
|
-
const success = !hasErrors && (!hasWarnings || !this.config.strictMode);
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
902
|
+
// Exit with appropriate code
|
|
903
|
+
const success = !hasErrors && (!hasWarnings || !this.config.strictMode);
|
|
904
|
+
const summaryReportPath = this.saveValidationSummaryReport(results, success);
|
|
905
|
+
if (summaryReportPath) {
|
|
906
|
+
console.log('');
|
|
907
|
+
console.log(`📄 Validation summary report saved: ${summaryReportPath}`);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return {
|
|
911
|
+
success,
|
|
862
912
|
errors: this.errors.length,
|
|
863
913
|
warnings: this.warnings.length,
|
|
864
914
|
results
|
|
@@ -892,7 +942,8 @@ class I18nValidator {
|
|
|
892
942
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
893
943
|
|
|
894
944
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
895
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
945
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
946
|
+
this.sourceDir = this.config.sourceDir;
|
|
896
947
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
897
948
|
} else {
|
|
898
949
|
await this.initialize();
|
|
@@ -983,7 +1034,7 @@ if (require.main === module) {
|
|
|
983
1034
|
// Initialize translations for CLI usage
|
|
984
1035
|
const config = configManager.getConfig();
|
|
985
1036
|
const uiLanguage = config.language || 'en';
|
|
986
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
1037
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
987
1038
|
|
|
988
1039
|
SecurityUtils.logSecurityEvent(t('validate.scriptExecution'), 'info', {
|
|
989
1040
|
script: 'i18ntk-validate.js',
|
|
@@ -1050,4 +1101,4 @@ if (require.main === module) {
|
|
|
1050
1101
|
process.exit(ExitCodes.CONFIG_ERROR);
|
|
1051
1102
|
}
|
|
1052
1103
|
})();
|
|
1053
|
-
}
|
|
1104
|
+
}
|
|
@@ -7,16 +7,17 @@
|
|
|
7
7
|
* Contains embedded business logic from I18nAnalyzer.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const cliHelper = require('../../../utils/cli-helper');
|
|
12
|
-
const { loadTranslations, t } = require('../../../utils/i18n-helper');
|
|
13
|
-
const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../../../utils/config-helper');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const cliHelper = require('../../../utils/cli-helper');
|
|
12
|
+
const { loadTranslations, t } = require('../../../utils/i18n-helper');
|
|
13
|
+
const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../../../utils/config-helper');
|
|
14
14
|
const SecurityUtils = require('../../../utils/security');
|
|
15
15
|
const AdminCLI = require('../../../utils/admin-cli');
|
|
16
|
+
const AdminAuth = require('../../../utils/admin-auth');
|
|
16
17
|
const watchLocales = require('../../../utils/watch-locales');
|
|
17
18
|
const JsonOutput = require('../../../utils/json-output');
|
|
18
|
-
|
|
19
|
-
loadTranslations('en', path.resolve(__dirname, '../../../
|
|
19
|
+
|
|
20
|
+
loadTranslations('en', path.resolve(__dirname, '../../../ui-locales'));
|
|
20
21
|
|
|
21
22
|
const PROJECT_ROOT = process.cwd();
|
|
22
23
|
|
|
@@ -74,15 +75,14 @@ class AnalyzeCommand {
|
|
|
74
75
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
75
76
|
|
|
76
77
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
77
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
78
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
78
79
|
|
|
79
80
|
this.sourceDir = this.config.sourceDir;
|
|
80
81
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
81
82
|
this.outputDir = this.config.outputDir;
|
|
82
83
|
|
|
83
84
|
// Validate source directory exists
|
|
84
|
-
|
|
85
|
-
validateSourceDir(this.sourceDir, 'i18ntk-analyze');
|
|
85
|
+
validateSourceDir(this.sourceDir, 'i18ntk-analyze');
|
|
86
86
|
|
|
87
87
|
} catch (error) {
|
|
88
88
|
console.error(`Fatal analysis error: ${error.message}`);
|
|
@@ -149,44 +149,44 @@ class AnalyzeCommand {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
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
|
-
|
|
164
|
-
getAvailableLanguages() {
|
|
165
|
-
try {
|
|
166
|
-
const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
|
|
167
|
-
if (!items) {
|
|
168
|
-
console.error('Error reading source directory: Unable to access directory');
|
|
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
|
+
|
|
164
|
+
getAvailableLanguages() {
|
|
165
|
+
try {
|
|
166
|
+
const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
|
|
167
|
+
if (!items) {
|
|
168
|
+
console.error('Error reading source directory: Unable to access directory');
|
|
169
169
|
return [];
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
const languages = [];
|
|
173
173
|
|
|
174
174
|
// Check for directory-based structure
|
|
175
|
-
const directories = items
|
|
176
|
-
.filter(item => item.isDirectory())
|
|
177
|
-
.map(item => item.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
|
-
});
|
|
175
|
+
const directories = items
|
|
176
|
+
.filter(item => item.isDirectory())
|
|
177
|
+
.map(item => item.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
|
+
});
|
|
190
190
|
|
|
191
191
|
// Check for monolith files (language.json files)
|
|
192
192
|
const files = items
|
|
@@ -197,17 +197,17 @@ class AnalyzeCommand {
|
|
|
197
197
|
languages.push(...directories);
|
|
198
198
|
|
|
199
199
|
// Add monolith files as languages (without .json extension)
|
|
200
|
-
const monolithLanguages = files
|
|
201
|
-
.map(file => file.replace('.json', ''))
|
|
202
|
-
.filter(lang =>
|
|
203
|
-
!languages.includes(lang) &&
|
|
204
|
-
lang !== this.config.sourceLanguage &&
|
|
205
|
-
!this.isExcludedLanguageDirectory(lang) &&
|
|
206
|
-
this.isValidLanguageCode(lang)
|
|
207
|
-
);
|
|
208
|
-
languages.push(...monolithLanguages);
|
|
209
|
-
|
|
210
|
-
return [...new Set(languages)].sort();
|
|
200
|
+
const monolithLanguages = files
|
|
201
|
+
.map(file => file.replace('.json', ''))
|
|
202
|
+
.filter(lang =>
|
|
203
|
+
!languages.includes(lang) &&
|
|
204
|
+
lang !== this.config.sourceLanguage &&
|
|
205
|
+
!this.isExcludedLanguageDirectory(lang) &&
|
|
206
|
+
this.isValidLanguageCode(lang)
|
|
207
|
+
);
|
|
208
|
+
languages.push(...monolithLanguages);
|
|
209
|
+
|
|
210
|
+
return [...new Set(languages)].sort();
|
|
211
211
|
} catch (error) {
|
|
212
212
|
console.error('Error reading source directory:', error.message);
|
|
213
213
|
return [];
|
|
@@ -719,7 +719,7 @@ class AnalyzeCommand {
|
|
|
719
719
|
throw new Error(t('analyze.failedToWriteReportFile') || 'Failed to write report file securely');
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
return reportPath;
|
|
722
|
+
return reportPath;
|
|
723
723
|
|
|
724
724
|
} catch (error) {
|
|
725
725
|
console.error(`Failed to save report for ${language}:`, error.message);
|
|
@@ -786,21 +786,21 @@ class AnalyzeCommand {
|
|
|
786
786
|
console.log(t('analyze.analyzing', { language }) || `\n🔄 Analyzing ${language}...`);
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
-
const analysis = this.analyzeLanguage(language);
|
|
790
|
-
const report = this.generateLanguageReport(analysis);
|
|
791
|
-
|
|
792
|
-
// Save report
|
|
793
|
-
const reportPath = await this.saveReport(language, report);
|
|
794
|
-
const processedCount = results.length + 1;
|
|
795
|
-
|
|
796
|
-
if (!args.json) {
|
|
797
|
-
console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
|
|
798
|
-
console.log(t('analyze.progress', {
|
|
799
|
-
translated: processedCount,
|
|
800
|
-
total: languages.length
|
|
801
|
-
}) || ` Progress: ${processedCount}/${languages.length} languages processed`);
|
|
802
|
-
console.log(t('analyze.reportSaved', { reportPath }) || ` Report saved: ${reportPath}`);
|
|
803
|
-
}
|
|
789
|
+
const analysis = this.analyzeLanguage(language);
|
|
790
|
+
const report = this.generateLanguageReport(analysis);
|
|
791
|
+
|
|
792
|
+
// Save report
|
|
793
|
+
const reportPath = await this.saveReport(language, report);
|
|
794
|
+
const processedCount = results.length + 1;
|
|
795
|
+
|
|
796
|
+
if (!args.json) {
|
|
797
|
+
console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
|
|
798
|
+
console.log(t('analyze.progress', {
|
|
799
|
+
translated: processedCount,
|
|
800
|
+
total: languages.length
|
|
801
|
+
}) || ` Progress: ${processedCount}/${languages.length} languages processed`);
|
|
802
|
+
console.log(t('analyze.reportSaved', { reportPath }) || ` Report saved: ${reportPath}`);
|
|
803
|
+
}
|
|
804
804
|
|
|
805
805
|
results.push({
|
|
806
806
|
language,
|
|
@@ -896,7 +896,7 @@ class AnalyzeCommand {
|
|
|
896
896
|
this.config = { ...baseConfig, ...this.config };
|
|
897
897
|
|
|
898
898
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
899
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
899
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
900
900
|
|
|
901
901
|
this.sourceDir = this.config.sourceDir;
|
|
902
902
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
@@ -908,15 +908,13 @@ class AnalyzeCommand {
|
|
|
908
908
|
const isCalledDirectly = require.main === module;
|
|
909
909
|
if (isCalledDirectly && !args.noPrompt && !fromMenu) {
|
|
910
910
|
// Only check admin authentication when running directly and not in no-prompt mode
|
|
911
|
-
const
|
|
912
|
-
const adminAuth = new AdminAuth();
|
|
911
|
+
const adminAuth = new AdminAuth();
|
|
913
912
|
await adminAuth.initialize();
|
|
914
913
|
|
|
915
914
|
const isRequired = await adminAuth.isAuthRequired();
|
|
916
915
|
if (isRequired) {
|
|
917
916
|
console.log('\n' + t('adminCli.authRequiredForOperation', { operation: 'analyze translations' }));
|
|
918
|
-
const
|
|
919
|
-
const pin = await cliHelper.promptPin(t('adminCli.enterPin'));
|
|
917
|
+
const pin = await cliHelper.promptPin(t('adminCli.enterPin'));
|
|
920
918
|
const isValid = await this.adminAuth.verifyPin(pin);
|
|
921
919
|
|
|
922
920
|
if (!isValid) {
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* Handles command execution, authentication, and completion management.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const { t } = require('../../../utils/i18n-helper');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { t } = require('../../../utils/i18n-helper');
|
|
12
|
+
const cliHelper = require('../../../utils/cli-helper');
|
|
12
13
|
|
|
13
14
|
// Import command handlers
|
|
14
15
|
const InitCommand = require('./InitCommand');
|
|
@@ -90,20 +91,22 @@ class CommandRouter {
|
|
|
90
91
|
/**
|
|
91
92
|
* Check admin authentication for protected commands
|
|
92
93
|
*/
|
|
93
|
-
async checkAdminAuth() {
|
|
94
|
-
if (!this.adminAuth) {
|
|
95
|
-
return true; // No auth service available, allow execution
|
|
96
|
-
}
|
|
94
|
+
async checkAdminAuth(adminPin = null) {
|
|
95
|
+
if (!this.adminAuth) {
|
|
96
|
+
return true; // No auth service available, allow execution
|
|
97
|
+
}
|
|
97
98
|
|
|
98
99
|
const isRequired = await this.adminAuth.isAuthRequired();
|
|
99
100
|
if (!isRequired) {
|
|
100
101
|
return true;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
let pin = adminPin;
|
|
105
|
+
if (!pin) {
|
|
106
|
+
console.log(t('adminCli.authRequired'));
|
|
107
|
+
pin = await cliHelper.promptPin(t('adminCli.enterPin'));
|
|
108
|
+
}
|
|
109
|
+
const isValid = await this.adminAuth.verifyPin(pin);
|
|
107
110
|
|
|
108
111
|
if (!isValid) {
|
|
109
112
|
console.log(t('adminCli.invalidPin'));
|
|
@@ -141,7 +144,7 @@ class CommandRouter {
|
|
|
141
144
|
];
|
|
142
145
|
|
|
143
146
|
if (authRequiredCommands.includes(command)) {
|
|
144
|
-
const authPassed = await this.checkAdminAuth();
|
|
147
|
+
const authPassed = await this.checkAdminAuth(options.adminPin || null);
|
|
145
148
|
if (!authPassed) {
|
|
146
149
|
if (!this.isNonInteractiveMode && !isDirectCommand && this.prompt) {
|
|
147
150
|
await this.prompt(t('menu.pressEnterToContinue'));
|
|
@@ -292,4 +295,4 @@ class CommandRouter {
|
|
|
292
295
|
}
|
|
293
296
|
}
|
|
294
297
|
|
|
295
|
-
module.exports = CommandRouter;
|
|
298
|
+
module.exports = CommandRouter;
|
|
@@ -27,10 +27,21 @@ class FixerCommand {
|
|
|
27
27
|
// Initialize fixer properties
|
|
28
28
|
this.sourceDir = null;
|
|
29
29
|
this.outputDir = null;
|
|
30
|
-
this.backupDir = null;
|
|
31
|
-
this.dryRun = false;
|
|
32
|
-
this.force = false;
|
|
33
|
-
}
|
|
30
|
+
this.backupDir = null;
|
|
31
|
+
this.dryRun = false;
|
|
32
|
+
this.force = false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
isExcludedLanguageDirectory(name) {
|
|
36
|
+
if (!name || typeof name !== 'string') return true;
|
|
37
|
+
const lowered = name.toLowerCase();
|
|
38
|
+
return lowered.startsWith('backup-') ||
|
|
39
|
+
lowered === 'backup' ||
|
|
40
|
+
lowered === 'backups' ||
|
|
41
|
+
lowered === 'i18ntk-backups' ||
|
|
42
|
+
lowered === 'reports' ||
|
|
43
|
+
lowered === 'i18ntk-reports';
|
|
44
|
+
}
|
|
34
45
|
|
|
35
46
|
/**
|
|
36
47
|
* Set runtime dependencies for interactive operations
|
|
@@ -64,11 +75,11 @@ class FixerCommand {
|
|
|
64
75
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
65
76
|
|
|
66
77
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
67
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
78
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
68
79
|
|
|
69
80
|
this.sourceDir = this.config.sourceDir;
|
|
70
81
|
this.outputDir = this.config.outputDir;
|
|
71
|
-
this.backupDir = path.
|
|
82
|
+
this.backupDir = path.resolve(this.config.backup?.location || './i18ntk-backups', 'fixer');
|
|
72
83
|
|
|
73
84
|
// Validate source directory exists
|
|
74
85
|
const { validateSourceDir } = require('../../../utils/config-helper');
|
|
@@ -127,10 +138,15 @@ class FixerCommand {
|
|
|
127
138
|
const languages = [];
|
|
128
139
|
|
|
129
140
|
// Check for directory-based structure
|
|
130
|
-
const directories = items
|
|
131
|
-
.filter(item => item.isDirectory())
|
|
132
|
-
.map(item => item.name)
|
|
133
|
-
.filter(name =>
|
|
141
|
+
const directories = items
|
|
142
|
+
.filter(item => item.isDirectory())
|
|
143
|
+
.map(item => item.name)
|
|
144
|
+
.filter(name =>
|
|
145
|
+
name !== 'node_modules' &&
|
|
146
|
+
!name.startsWith('.') &&
|
|
147
|
+
name !== this.config.sourceLanguage &&
|
|
148
|
+
!this.isExcludedLanguageDirectory(name)
|
|
149
|
+
);
|
|
134
150
|
|
|
135
151
|
// Check for monolith files (language.json files)
|
|
136
152
|
const files = items
|
|
@@ -142,9 +158,13 @@ class FixerCommand {
|
|
|
142
158
|
|
|
143
159
|
// Add monolith files as languages (without .json extension)
|
|
144
160
|
const monolithLanguages = files
|
|
145
|
-
.map(file => file.replace('.json', ''))
|
|
146
|
-
.filter(lang =>
|
|
147
|
-
|
|
161
|
+
.map(file => file.replace('.json', ''))
|
|
162
|
+
.filter(lang =>
|
|
163
|
+
!languages.includes(lang) &&
|
|
164
|
+
lang !== this.config.sourceLanguage &&
|
|
165
|
+
!this.isExcludedLanguageDirectory(lang)
|
|
166
|
+
);
|
|
167
|
+
languages.push(...monolithLanguages);
|
|
148
168
|
|
|
149
169
|
return [...new Set(languages)].sort();
|
|
150
170
|
} catch (error) {
|
|
@@ -279,14 +299,21 @@ class FixerCommand {
|
|
|
279
299
|
}
|
|
280
300
|
|
|
281
301
|
// Create backup of translation files
|
|
282
|
-
async createBackup() {
|
|
283
|
-
if (this.dryRun) return;
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
302
|
+
async createBackup() {
|
|
303
|
+
if (this.dryRun) return;
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const backupEnabled = this.config?.backup?.enabled === true;
|
|
307
|
+
if (!backupEnabled) {
|
|
308
|
+
this.backupDir = null;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
313
|
+
const backupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups');
|
|
314
|
+
this.backupDir = path.join(backupRoot, 'fixer', `backup-${timestamp}`);
|
|
315
|
+
|
|
316
|
+
console.log(t('fixer.creatingBackup', { dir: this.backupDir }));
|
|
290
317
|
|
|
291
318
|
// Ensure backup directory exists
|
|
292
319
|
const dirCreated = SecurityUtils.safeMkdirSync(this.backupDir, process.cwd(), { recursive: true });
|
|
@@ -299,8 +326,8 @@ class FixerCommand {
|
|
|
299
326
|
const languages = this.getAvailableLanguages();
|
|
300
327
|
languages.push(this.config.sourceLanguage); // Include source language
|
|
301
328
|
|
|
302
|
-
for (const language of languages) {
|
|
303
|
-
const languageFiles = this.getLanguageFiles(language);
|
|
329
|
+
for (const language of languages) {
|
|
330
|
+
const languageFiles = this.getLanguageFiles(language);
|
|
304
331
|
|
|
305
332
|
for (const fileName of languageFiles) {
|
|
306
333
|
const sourcePath = path.join(this.sourceDir, language, fileName);
|
|
@@ -316,13 +343,42 @@ class FixerCommand {
|
|
|
316
343
|
SecurityUtils.safeWriteFileSync(backupPath, content, process.cwd(), 'utf8');
|
|
317
344
|
}
|
|
318
345
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.
|
|
324
|
-
}
|
|
325
|
-
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
this.cleanupOldBackups();
|
|
349
|
+
|
|
350
|
+
console.log(t('fixer.backupCreated'));
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.warn(`Failed to create backup: ${error.message}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
cleanupOldBackups() {
|
|
357
|
+
try {
|
|
358
|
+
const fixerBackupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups', 'fixer');
|
|
359
|
+
if (!SecurityUtils.safeExistsSync(fixerBackupRoot, process.cwd())) return;
|
|
360
|
+
|
|
361
|
+
const configuredKeep = parseInt(this.config?.backup?.maxBackups, 10);
|
|
362
|
+
const keepCount = Number.isInteger(configuredKeep) ? Math.min(Math.max(configuredKeep, 1), 3) : 1;
|
|
363
|
+
|
|
364
|
+
const backupDirs = SecurityUtils.safeReaddirSync(fixerBackupRoot, process.cwd(), { withFileTypes: true })
|
|
365
|
+
.filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
|
|
366
|
+
.map(entry => {
|
|
367
|
+
const dirPath = path.join(fixerBackupRoot, entry.name);
|
|
368
|
+
const stat = SecurityUtils.safeStatSync(dirPath, process.cwd());
|
|
369
|
+
return { name: entry.name, path: dirPath, mtimeMs: stat ? stat.mtimeMs : 0 };
|
|
370
|
+
})
|
|
371
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
372
|
+
|
|
373
|
+
if (backupDirs.length <= keepCount) return;
|
|
374
|
+
|
|
375
|
+
for (const staleDir of backupDirs.slice(keepCount)) {
|
|
376
|
+
fs.rmSync(staleDir.path, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.warn(`Failed to clean old backups: ${error.message}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
326
382
|
|
|
327
383
|
// Analyze translation issues for fixing
|
|
328
384
|
analyzeIssues(language, fileName) {
|
|
@@ -471,9 +527,9 @@ class FixerCommand {
|
|
|
471
527
|
}
|
|
472
528
|
|
|
473
529
|
// Create backup unless disabled
|
|
474
|
-
if (!args.noBackup && !this.dryRun) {
|
|
475
|
-
await this.createBackup();
|
|
476
|
-
}
|
|
530
|
+
if (!args.noBackup && !this.dryRun && this.config?.backup?.enabled === true) {
|
|
531
|
+
await this.createBackup();
|
|
532
|
+
}
|
|
477
533
|
|
|
478
534
|
const languages = this.getAvailableLanguages();
|
|
479
535
|
|
|
@@ -535,9 +591,9 @@ class FixerCommand {
|
|
|
535
591
|
console.log(t('fixer.totalIssues', { count: totalIssues }));
|
|
536
592
|
console.log(t('fixer.totalFixed', { count: totalFixed }));
|
|
537
593
|
|
|
538
|
-
if (this.backupDir && !args.noBackup) {
|
|
539
|
-
console.log(t('fixer.backupLocation', { dir: this.backupDir }));
|
|
540
|
-
}
|
|
594
|
+
if (this.backupDir && !args.noBackup && this.config?.backup?.enabled === true) {
|
|
595
|
+
console.log(t('fixer.backupLocation', { dir: this.backupDir }));
|
|
596
|
+
}
|
|
541
597
|
|
|
542
598
|
console.log(t('fixer.completed'));
|
|
543
599
|
|
|
@@ -567,7 +623,7 @@ class FixerCommand {
|
|
|
567
623
|
this.config = { ...baseConfig, ...this.config };
|
|
568
624
|
|
|
569
625
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
570
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../
|
|
626
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
571
627
|
|
|
572
628
|
this.sourceDir = this.config.sourceDir;
|
|
573
629
|
this.outputDir = this.config.outputDir;
|
|
@@ -621,4 +677,4 @@ class FixerCommand {
|
|
|
621
677
|
}
|
|
622
678
|
}
|
|
623
679
|
|
|
624
|
-
module.exports = FixerCommand;
|
|
680
|
+
module.exports = FixerCommand;
|
|
@@ -41,8 +41,8 @@ class ScannerCommand {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
loadLocale() {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const uiLocalesDir = path.join(__dirname, '../../../ui-locales');
|
|
45
|
+
const localeFile = path.join(uiLocalesDir, 'en.json');
|
|
46
46
|
|
|
47
47
|
try {
|
|
48
48
|
const localeContent = SecurityUtils.safeReadFileSync(localeFile, uiLocalesDir, 'utf8');
|