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-manage.js
DELETED
|
@@ -1,1694 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* I18NTK MANAGEMENT TOOLKIT - MAIN MANAGER
|
|
4
|
-
*
|
|
5
|
-
* This is the main entry point for all i18n operations.
|
|
6
|
-
* It provides an interactive interface to manage translations.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* npm run i18ntk:manage
|
|
10
|
-
* npm run i18ntk:manage -- --command=init
|
|
11
|
-
* npm run i18ntk:manage -- --command=analyze
|
|
12
|
-
* npm run i18ntk:manage -- --command=validate
|
|
13
|
-
* npm run i18ntk:manage -- --command=usage
|
|
14
|
-
* npm run i18ntk:manage -- --help
|
|
15
|
-
*
|
|
16
|
-
* Alternative direct usage:
|
|
17
|
-
* node i18ntk-manage.js
|
|
18
|
-
* node i18ntk-manage.js --command=init
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const path = require('path');
|
|
22
|
-
const UIi18n = require('./i18ntk-ui');
|
|
23
|
-
const AdminAuth = require('../utils/admin-auth');
|
|
24
|
-
const SecurityUtils = require('../utils/security');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const AdminCLI = require('../utils/admin-cli');
|
|
28
|
-
const configManager = require('../settings/settings-manager');
|
|
29
|
-
const { showFrameworkWarningOnce } = require('../utils/cli-helper');
|
|
30
|
-
const I18nInitializer = require('./i18ntk-init');
|
|
31
|
-
const { I18nAnalyzer } = require('./i18ntk-analyze');
|
|
32
|
-
const I18nValidator = require('./i18ntk-validate');
|
|
33
|
-
const I18nUsageAnalyzer = require('./i18ntk-usage');
|
|
34
|
-
const I18nSizingAnalyzer = require('./i18ntk-sizing');
|
|
35
|
-
const I18nFixer = require('./i18ntk-fixer');
|
|
36
|
-
const SettingsCLI = require('../settings/settings-cli');
|
|
37
|
-
// const I18nDebugger = require('../scripts/debug/debugger');
|
|
38
|
-
const { createPrompt, isInteractive } = require('../utils/prompt-helper');
|
|
39
|
-
const { loadTranslations, t, refreshLanguageFromSettings} = require('../utils/i18n-helper');
|
|
40
|
-
// Preload translations early to avoid missing key warnings
|
|
41
|
-
loadTranslations();
|
|
42
|
-
const cliHelper = require('../utils/cli-helper');
|
|
43
|
-
const { loadConfig, saveConfig, ensureConfigDefaults } = require('../utils/config');
|
|
44
|
-
const pkg = require('../package.json');
|
|
45
|
-
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
46
|
-
|
|
47
|
-
// Setup check will be handled in the I18nManager.run() method
|
|
48
|
-
|
|
49
|
-
async function runInitFlow() {
|
|
50
|
-
const initializer = new I18nInitializer();
|
|
51
|
-
await initializer.run({ fromMenu: true });
|
|
52
|
-
const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
53
|
-
return { i18nDir: settings.i18nDir, sourceDir: settings.sourceDir };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Ensures the project is properly initialized or exits the process
|
|
58
|
-
* @param {Object} prompt - Prompt interface for user interaction
|
|
59
|
-
* @returns {Promise<Object>} Configuration object if initialized
|
|
60
|
-
*/
|
|
61
|
-
async function ensureInitializedOrExit(prompt) {
|
|
62
|
-
const { checkInitialized } = require('../utils/init-helper');
|
|
63
|
-
const cliHelper = require('../utils/cli-helper');
|
|
64
|
-
const pkg = require('../package.json');
|
|
65
|
-
|
|
66
|
-
const { initialized, config } = await checkInitialized();
|
|
67
|
-
|
|
68
|
-
if (!initialized) {
|
|
69
|
-
console.log('\nThis project is not yet initialized with i18ntk.');
|
|
70
|
-
const shouldInitialize = await cliHelper.confirm('Would you like to initialize it now?');
|
|
71
|
-
|
|
72
|
-
if (!shouldInitialize) {
|
|
73
|
-
console.log('Exiting. Please initialize the project first.');
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// The initialization will be handled by the init command
|
|
78
|
-
return config;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check if we need to prompt for framework detection
|
|
82
|
-
const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
83
|
-
|
|
84
|
-
// Ensure framework configuration exists with all required fields
|
|
85
|
-
if (!settings.framework) {
|
|
86
|
-
settings.framework = {
|
|
87
|
-
detected: false,
|
|
88
|
-
preference: null,
|
|
89
|
-
prompt: 'always',
|
|
90
|
-
lastPromptedVersion: null,
|
|
91
|
-
installed: [],
|
|
92
|
-
version: '1.0' // Schema version for future compatibility
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Check if we need to prompt for framework detection
|
|
97
|
-
if (!settings.framework.detected &&
|
|
98
|
-
settings.framework.prompt !== 'suppress' &&
|
|
99
|
-
settings.framework.lastPromptedVersion !== pkg.version) {
|
|
100
|
-
|
|
101
|
-
console.log('\nWe noticed you haven\'t set up an i18n framework yet.');
|
|
102
|
-
console.log('Would you like to detect your i18n framework automatically?');
|
|
103
|
-
|
|
104
|
-
console.log('1. Detect automatically');
|
|
105
|
-
console.log('2. I\'ll set it up manually');
|
|
106
|
-
console.log('3. Don\'t show this again');
|
|
107
|
-
|
|
108
|
-
const answer = await prompt.question('\nSelect an option (1-3): ');
|
|
109
|
-
const choice = answer.trim();
|
|
110
|
-
|
|
111
|
-
let action;
|
|
112
|
-
if (choice === '1') action = 'detect';
|
|
113
|
-
else if (choice === '2') action = 'manual';
|
|
114
|
-
else if (choice === '3') action = 'dont-show';
|
|
115
|
-
else action = 'manual'; // default fallback
|
|
116
|
-
|
|
117
|
-
if (action === 'dont-show') {
|
|
118
|
-
// Update settings to suppress future prompts for this version
|
|
119
|
-
settings.framework.prompt = 'suppress';
|
|
120
|
-
settings.framework.lastPromptedVersion = pkg.version;
|
|
121
|
-
|
|
122
|
-
if (configManager.saveSettings) {
|
|
123
|
-
await configManager.saveSettings(settings);
|
|
124
|
-
} else if (configManager.saveConfig) {
|
|
125
|
-
await configManager.saveConfig(settings);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
console.log('Framework detection prompt will be suppressed for this version.');
|
|
129
|
-
} else if (action === 'detect') {
|
|
130
|
-
// Run framework detection
|
|
131
|
-
const { detectedLanguage, detectedFramework } = await detectEnvironmentAndFramework();
|
|
132
|
-
|
|
133
|
-
if (detectedFramework && detectedFramework !== 'generic') {
|
|
134
|
-
console.log(`\nDetected framework: ${detectedFramework}`);
|
|
135
|
-
|
|
136
|
-
// Update settings with detected framework
|
|
137
|
-
settings.framework.detected = true;
|
|
138
|
-
settings.framework.preference = detectedFramework;
|
|
139
|
-
settings.framework.lastDetected = new Date().toISOString();
|
|
140
|
-
|
|
141
|
-
if (configManager.saveSettings) {
|
|
142
|
-
await configManager.saveSettings(settings);
|
|
143
|
-
} else if (configManager.saveConfig) {
|
|
144
|
-
await configManager.saveConfig(settings);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
console.log(`Framework set to: ${detectedFramework}`);
|
|
148
|
-
} else {
|
|
149
|
-
console.log('\nCould not detect a specific i18n framework.');
|
|
150
|
-
console.log('Please set up your i18n framework manually.');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return { ...config, ...settings };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Custom glob implementation using Node.js built-in modules (zero dependencies)
|
|
160
|
-
* @param {string[]} patterns - Array of glob patterns
|
|
161
|
-
* @param {Object} options - Options object with cwd and ignore properties
|
|
162
|
-
* @returns {Promise<string[]>} Array of matching file paths
|
|
163
|
-
*/
|
|
164
|
-
async function customGlob(patterns, options = {}) {
|
|
165
|
-
const fs = require('fs');
|
|
166
|
-
const path = require('path');
|
|
167
|
-
const cwd = options.cwd || process.cwd();
|
|
168
|
-
const ignorePatterns = options.ignore || [];
|
|
169
|
-
|
|
170
|
-
function matchesPattern(filename, pattern) {
|
|
171
|
-
// Simple pattern matching for **/*.{js,jsx,ts,tsx} style patterns
|
|
172
|
-
if (pattern.includes('**/*')) {
|
|
173
|
-
const extensionPart = pattern.split('*.')[1];
|
|
174
|
-
if (extensionPart) {
|
|
175
|
-
const extensions = extensionPart.replace('{', '').replace('}', '').split(',');
|
|
176
|
-
return extensions.some(ext => filename.endsWith('.' + ext.trim()));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return filename.includes(pattern.replace('**/', ''));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function shouldIgnore(filePath) {
|
|
183
|
-
return ignorePatterns.some(pattern => {
|
|
184
|
-
if (pattern.includes('**/')) {
|
|
185
|
-
const patternEnd = pattern.replace('**/', '');
|
|
186
|
-
return filePath.includes('/' + patternEnd) || filePath.includes('\\' + patternEnd);
|
|
187
|
-
}
|
|
188
|
-
return filePath.includes(pattern);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function findFiles(dir, results = []) {
|
|
193
|
-
try {
|
|
194
|
-
const items = fs.readdirSync(dir);
|
|
195
|
-
|
|
196
|
-
for (const item of items) {
|
|
197
|
-
const fullPath = path.join(dir, item);
|
|
198
|
-
const relativePath = path.relative(cwd, fullPath);
|
|
199
|
-
|
|
200
|
-
if (shouldIgnore(relativePath)) {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const stat = fs.statSync(fullPath);
|
|
206
|
-
|
|
207
|
-
if (stat.isDirectory()) {
|
|
208
|
-
findFiles(fullPath, results);
|
|
209
|
-
} else if (stat.isFile()) {
|
|
210
|
-
// Check if file matches any of our patterns
|
|
211
|
-
for (const pattern of patterns) {
|
|
212
|
-
if (matchesPattern(item, pattern)) {
|
|
213
|
-
results.push(relativePath);
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
// Skip files we can't access
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} catch (error) {
|
|
224
|
-
// Skip directories we can't access
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return results;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return findFiles(cwd);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function detectEnvironmentAndFramework() {
|
|
234
|
-
// Defensive check to ensure SecurityUtils is available
|
|
235
|
-
if (!SecurityUtils) {
|
|
236
|
-
throw new Error('SecurityUtils is not available. This may indicate a module loading issue.');
|
|
237
|
-
}
|
|
238
|
-
const fs = require('fs');
|
|
239
|
-
const path = require('path');
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
243
|
-
const pyprojectPath = path.join(process.cwd(), 'pyproject.toml');
|
|
244
|
-
const requirementsPath = path.join(process.cwd(), 'requirements.txt');
|
|
245
|
-
const goModPath = path.join(process.cwd(), 'go.mod');
|
|
246
|
-
const pomPath = path.join(process.cwd(), 'pom.xml');
|
|
247
|
-
const composerPath = path.join(process.cwd(), 'composer.json');
|
|
248
|
-
|
|
249
|
-
let detectedLanguage = 'generic';
|
|
250
|
-
let detectedFramework = 'generic';
|
|
251
|
-
|
|
252
|
-
if (SecurityUtils.safeExistsSync(packageJsonPath)) {
|
|
253
|
-
detectedLanguage = 'javascript';
|
|
254
|
-
try {
|
|
255
|
-
const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
|
|
256
|
-
const deps = {
|
|
257
|
-
...(packageJson.dependencies || {}),
|
|
258
|
-
...(packageJson.devDependencies || {}),
|
|
259
|
-
...(packageJson.peerDependencies || {})
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
// Check for i18ntk-runtime first (check both package names)
|
|
263
|
-
const hasI18nTkRuntime = deps['i18ntk-runtime'] || deps['i18ntk/runtime'];
|
|
264
|
-
|
|
265
|
-
// Check for common i18n patterns in source code if not found in package.json
|
|
266
|
-
if (!hasI18nTkRuntime) {
|
|
267
|
-
const i18nPatterns = [
|
|
268
|
-
/i18n\.t\(['\"`]/,
|
|
269
|
-
/useI18n\(/,
|
|
270
|
-
/from ['\"]i18ntk[\/\\]runtime['\"]/,
|
|
271
|
-
/require\(['\"]i18ntk[\/\\]runtime['\"]\)/
|
|
272
|
-
];
|
|
273
|
-
|
|
274
|
-
const sourceFiles = await customGlob(['src/**/*.{js,jsx,ts,tsx}'], {
|
|
275
|
-
cwd: process.cwd(),
|
|
276
|
-
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
for (const file of sourceFiles) {
|
|
280
|
-
try {
|
|
281
|
-
const content = await fs.promises.readFile(path.join(process.cwd(), file), 'utf8');
|
|
282
|
-
if (i18nPatterns.some(pattern => pattern.test(content))) {
|
|
283
|
-
detectedFramework = 'i18ntk-runtime';
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
} catch (e) {
|
|
287
|
-
// Skip files we can't read
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
detectedFramework = 'i18ntk-runtime';
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Only check other frameworks if i18ntk-runtime wasn't detected
|
|
296
|
-
if (detectedFramework !== 'i18ntk-runtime') {
|
|
297
|
-
if (deps.react || deps['react-dom']) detectedFramework = 'react';
|
|
298
|
-
else if (deps.vue || deps['vue-router']) detectedFramework = 'vue';
|
|
299
|
-
else if (deps['@angular/core']) detectedFramework = 'angular';
|
|
300
|
-
else if (deps.next) detectedFramework = 'nextjs';
|
|
301
|
-
else if (deps.nuxt) detectedFramework = 'nuxt';
|
|
302
|
-
else if (deps.svelte) detectedFramework = 'svelte';
|
|
303
|
-
else detectedFramework = 'generic';
|
|
304
|
-
}
|
|
305
|
-
} catch (error) {
|
|
306
|
-
detectedFramework = 'generic';
|
|
307
|
-
}
|
|
308
|
-
} else if (SecurityUtils.safeExistsSync(pyprojectPath) || SecurityUtils.safeExistsSync(requirementsPath)) {
|
|
309
|
-
detectedLanguage = 'python';
|
|
310
|
-
try {
|
|
311
|
-
if (SecurityUtils.safeExistsSync(requirementsPath)) {
|
|
312
|
-
const requirements = SecurityUtils.safeReadFileSync(requirementsPath, path.dirname(requirementsPath), 'utf8');
|
|
313
|
-
if (requirements.includes('django')) detectedFramework = 'django';
|
|
314
|
-
else if (requirements.includes('flask')) detectedFramework = 'flask';
|
|
315
|
-
else if (requirements.includes('fastapi')) detectedFramework = 'fastapi';
|
|
316
|
-
else detectedFramework = 'generic';
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
detectedFramework = 'generic';
|
|
320
|
-
}
|
|
321
|
-
} else if (SecurityUtils.safeExistsSync(goModPath)) {
|
|
322
|
-
detectedLanguage = 'go';
|
|
323
|
-
detectedFramework = 'generic';
|
|
324
|
-
} else if (SecurityUtils.safeExistsSync(pomPath)) {
|
|
325
|
-
detectedLanguage = 'java';
|
|
326
|
-
try {
|
|
327
|
-
const pomContent = SecurityUtils.safeReadFileSync(pomPath, path.dirname(pomPath), 'utf8');
|
|
328
|
-
if (pomContent.includes('spring-boot')) detectedFramework = 'spring-boot';
|
|
329
|
-
else if (pomContent.includes('spring')) detectedFramework = 'spring';
|
|
330
|
-
else if (pomContent.includes('quarkus')) detectedFramework = 'quarkus';
|
|
331
|
-
else detectedFramework = 'generic';
|
|
332
|
-
} catch (error) {
|
|
333
|
-
detectedFramework = 'generic';
|
|
334
|
-
}
|
|
335
|
-
} else if (SecurityUtils.safeExistsSync(composerPath)) {
|
|
336
|
-
detectedLanguage = 'php';
|
|
337
|
-
try {
|
|
338
|
-
const composer = JSON.parse(SecurityUtils.safeReadFileSync(composerPath, path.dirname(composerPath), 'utf8'));
|
|
339
|
-
const deps = composer.require || {};
|
|
340
|
-
|
|
341
|
-
if (deps['laravel/framework']) detectedFramework = 'laravel';
|
|
342
|
-
else if (deps['symfony/framework-bundle']) detectedFramework = 'symfony';
|
|
343
|
-
else if (deps['wordpress']) detectedFramework = 'wordpress';
|
|
344
|
-
else detectedFramework = 'generic';
|
|
345
|
-
} catch (error) {
|
|
346
|
-
detectedFramework = 'generic';
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return { detectedLanguage, detectedFramework };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function getFrameworkSuggestions(language) {
|
|
354
|
-
const suggestions = {
|
|
355
|
-
javascript: [
|
|
356
|
-
{ name: 'i18next', description: 'Feature-rich i18n framework for JavaScript' },
|
|
357
|
-
{ name: 'react-i18next', description: 'React integration for i18next' },
|
|
358
|
-
{ name: 'vue-i18n', description: 'Vue.js i18n plugin' },
|
|
359
|
-
{ name: 'Angular i18n', description: 'Built-in Angular i18n' }
|
|
360
|
-
],
|
|
361
|
-
typescript: [
|
|
362
|
-
{ name: 'i18next', description: 'TypeScript-first i18n framework' },
|
|
363
|
-
{ name: 'react-i18next', description: 'React + TypeScript integration' },
|
|
364
|
-
{ name: 'vue-i18n', description: 'Vue.js i18n with TypeScript support' }
|
|
365
|
-
],
|
|
366
|
-
python: [
|
|
367
|
-
{ name: 'Django i18n', description: 'Built-in Django internationalization' },
|
|
368
|
-
{ name: 'Flask-Babel', description: 'Babel integration for Flask' },
|
|
369
|
-
{ name: 'FastAPI i18n', description: 'i18n middleware for FastAPI' }
|
|
370
|
-
],
|
|
371
|
-
java: [
|
|
372
|
-
{ name: 'Spring i18n', description: 'Spring Framework internationalization' },
|
|
373
|
-
{ name: 'Spring Boot i18n', description: 'Spring Boot auto-configuration' },
|
|
374
|
-
{ name: 'Quarkus i18n', description: 'Quarkus internationalization support' }
|
|
375
|
-
],
|
|
376
|
-
go: [
|
|
377
|
-
{ name: 'go-i18n', description: 'Go i18n library with pluralization' },
|
|
378
|
-
{ name: 'nicksnyder/go-i18n', description: 'Feature-rich Go i18n' }
|
|
379
|
-
],
|
|
380
|
-
php: [
|
|
381
|
-
{ name: 'Laravel i18n', description: 'Built-in Laravel localization' },
|
|
382
|
-
{ name: 'Symfony Translation', description: 'Symfony translation component' },
|
|
383
|
-
{ name: 'WordPress i18n', description: 'WordPress localization functions' }
|
|
384
|
-
]
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
return suggestions[language] || suggestions.javascript;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Handles framework detection and prompting logic
|
|
392
|
-
* @param {Object} prompt - Prompt interface for user interaction
|
|
393
|
-
* @param {Object} cfg - Configuration object
|
|
394
|
-
* @param {string} currentVersion - Current version of the tool
|
|
395
|
-
* @returns {Promise<Object>} Updated configuration
|
|
396
|
-
*/
|
|
397
|
-
async function maybePromptFramework(prompt, cfg, currentVersion) {
|
|
398
|
-
// Load current settings to check framework configuration
|
|
399
|
-
let settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
400
|
-
|
|
401
|
-
// Ensure framework configuration exists with all required fields
|
|
402
|
-
if (!settings.framework) {
|
|
403
|
-
settings.framework = {
|
|
404
|
-
detected: false,
|
|
405
|
-
preference: null,
|
|
406
|
-
prompt: 'always',
|
|
407
|
-
lastPromptedVersion: null,
|
|
408
|
-
installed: [],
|
|
409
|
-
version: '1.0' // Schema version for future compatibility
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
// Save the updated settings
|
|
413
|
-
if (configManager.saveSettings) {
|
|
414
|
-
await configManager.saveSettings(settings);
|
|
415
|
-
} else if (configManager.saveConfig) {
|
|
416
|
-
await configManager.saveConfig(settings);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Reload settings to ensure we have the latest framework detection results
|
|
421
|
-
const freshSettings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
422
|
-
if (freshSettings.framework) {
|
|
423
|
-
settings.framework = { ...settings.framework, ...freshSettings.framework };
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Check if framework is already detected or preference is explicitly set to none
|
|
427
|
-
if (settings.framework.detected || settings.framework.preference === 'none') {
|
|
428
|
-
return cfg;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Check if DNR (Do Not Remind) is active for this version
|
|
432
|
-
if (settings.framework.prompt === 'suppress' && settings.framework.lastPromptedVersion === currentVersion) {
|
|
433
|
-
return cfg;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Reset DNR if version changed
|
|
437
|
-
if (settings.framework.prompt === 'suppress' && settings.framework.lastPromptedVersion !== currentVersion) {
|
|
438
|
-
settings.framework.prompt = 'always';
|
|
439
|
-
settings.framework.lastPromptedVersion = null;
|
|
440
|
-
|
|
441
|
-
// Save the updated settings
|
|
442
|
-
if (configManager.saveSettings) {
|
|
443
|
-
await configManager.saveSettings(settings);
|
|
444
|
-
} else if (configManager.saveConfig) {
|
|
445
|
-
await configManager.saveConfig(settings);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// This function is now handled by ensureInitializedOrExit for better flow control
|
|
450
|
-
|
|
451
|
-
return cfg;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Use unified configuration system
|
|
455
|
-
const { getUnifiedConfig, ensureInitialized, validateSourceDir } = require('../utils/config-helper');
|
|
456
|
-
|
|
457
|
-
class I18nManager {
|
|
458
|
-
constructor(config = {}) {
|
|
459
|
-
this.config = config;
|
|
460
|
-
this.rl = null;
|
|
461
|
-
this.isReadlineClosed = false;
|
|
462
|
-
this.isAuthenticated = false;
|
|
463
|
-
this.ui = null;
|
|
464
|
-
this.adminAuth = new AdminAuth();
|
|
465
|
-
|
|
466
|
-
// No longer create readline interface here - use CLI helpers
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
initializeReadline() {
|
|
470
|
-
// Use centralized CLI helper instead of direct readline
|
|
471
|
-
this.rl = null;
|
|
472
|
-
this.isReadlineClosed = false;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Initialize configuration using unified system
|
|
476
|
-
async initialize() {
|
|
477
|
-
try {
|
|
478
|
-
// Parse args here for other initialization needs (but language is already loaded)
|
|
479
|
-
const args = this.parseArgs();
|
|
480
|
-
if (args.help) {
|
|
481
|
-
this.showHelp();
|
|
482
|
-
process.exit(0);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Ensure UI is initialized (it should already be loaded in run())
|
|
486
|
-
if (!this.ui) {
|
|
487
|
-
const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
488
|
-
const uiLanguage = args.uiLanguage || settings.uiLanguage || settings.language || this.config.uiLanguage || 'en';
|
|
489
|
-
this.ui.loadLanguage(uiLanguage);
|
|
490
|
-
loadTranslations(uiLanguage);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Validate source directory exists
|
|
494
|
-
const {validateSourceDir, displayPaths} = require('../utils/config-helper');
|
|
495
|
-
try {
|
|
496
|
-
validateSourceDir(this.config.sourceDir, 'i18ntk-manage');
|
|
497
|
-
} catch (err) {
|
|
498
|
-
console.log(this.ui.t('init.requiredTitle'));
|
|
499
|
-
console.log(this.ui.t('init.requiredBody'));
|
|
500
|
-
const answer = await cliHelper.prompt(this.ui.t('init.promptRunNow'));
|
|
501
|
-
if (answer.trim().toLowerCase() === 'y') {
|
|
502
|
-
const initializer = new I18nInitializer(this.config);
|
|
503
|
-
await initializer.run({ fromMenu: true });
|
|
504
|
-
} else {
|
|
505
|
-
throw err;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
} catch (error) {
|
|
510
|
-
|
|
511
|
-
throw error;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Auto-detect i18n directory from common locations only if not configured in settings
|
|
516
|
-
detectI18nDirectory() {
|
|
517
|
-
|
|
518
|
-
const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
519
|
-
const projectRoot = path.resolve(settings.projectRoot || this.config.projectRoot || '.');
|
|
520
|
-
|
|
521
|
-
// Use per-script directory configuration if available, fallback to global sourceDir
|
|
522
|
-
const sourceDir = settings.scriptDirectories?.manage || settings.sourceDir;
|
|
523
|
-
|
|
524
|
-
if (sourceDir) {
|
|
525
|
-
this.config.sourceDir = path.resolve(projectRoot, sourceDir);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Define possible i18n paths for auto-detection
|
|
530
|
-
const possibleI18nPaths = [
|
|
531
|
-
'./locales',
|
|
532
|
-
'./src/locales',
|
|
533
|
-
'./src/i18n',
|
|
534
|
-
'./src/i18n/locales',
|
|
535
|
-
'./app/locales',
|
|
536
|
-
'./app/i18n',
|
|
537
|
-
'./public/locales',
|
|
538
|
-
'./assets/locales',
|
|
539
|
-
'./translations',
|
|
540
|
-
'./lang'
|
|
541
|
-
];
|
|
542
|
-
|
|
543
|
-
// Only auto-detect if no settings are configured
|
|
544
|
-
for (const possiblePath of possibleI18nPaths) {
|
|
545
|
-
const resolvedPath = path.resolve(projectRoot, possiblePath);
|
|
546
|
-
if (SecurityUtils.safeExistsSync(resolvedPath)) {
|
|
547
|
-
// Check if it contains language directories
|
|
548
|
-
try {
|
|
549
|
-
const items = fs.readdirSync(resolvedPath);
|
|
550
|
-
const hasLanguageDirs = items.some(item => {
|
|
551
|
-
const itemPath = path.join(resolvedPath, item);
|
|
552
|
-
return fs.statSync(itemPath).isDirectory() &&
|
|
553
|
-
['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(item);
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
if (hasLanguageDirs) {
|
|
557
|
-
this.config.sourceDir = possiblePath;
|
|
558
|
-
t('init.autoDetectedI18nDirectory', { path: possiblePath });
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
} catch (error) {
|
|
562
|
-
// Continue checking other paths
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Check if i18n framework is installed - configuration-based check without prompts
|
|
569
|
-
async checkI18nDependencies() {
|
|
570
|
-
const packageJsonPath = path.resolve('./package.json');
|
|
571
|
-
|
|
572
|
-
if (!SecurityUtils.safeExistsSync(packageJsonPath)) {
|
|
573
|
-
console.log(this.ui ? this.ui.t('errors.noPackageJson') : 'No package.json found');
|
|
574
|
-
return false; // Treat as no framework detected
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
try {
|
|
578
|
-
const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
|
|
579
|
-
// Include peerDependencies in the check
|
|
580
|
-
const dependencies = {
|
|
581
|
-
...packageJson.dependencies,
|
|
582
|
-
...packageJson.devDependencies,
|
|
583
|
-
...packageJson.peerDependencies
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const i18nFrameworks = [
|
|
587
|
-
'react-i18next',
|
|
588
|
-
'vue-i18n',
|
|
589
|
-
'angular-i18n',
|
|
590
|
-
'i18next',
|
|
591
|
-
'next-i18next',
|
|
592
|
-
'svelte-i18n',
|
|
593
|
-
'@nuxtjs/i18n',
|
|
594
|
-
'i18ntk-runtime'
|
|
595
|
-
];
|
|
596
|
-
|
|
597
|
-
const installedFrameworks = i18nFrameworks.filter(framework => dependencies[framework]);
|
|
598
|
-
|
|
599
|
-
if (installedFrameworks.length > 0) {
|
|
600
|
-
if (this.ui && this.ui.t) {
|
|
601
|
-
console.log(this.ui.t('init.detectedFrameworks', { frameworks: installedFrameworks.join(', ') }));
|
|
602
|
-
} else {
|
|
603
|
-
console.log(`Detected frameworks: ${installedFrameworks.join(', ')}`);
|
|
604
|
-
}
|
|
605
|
-
const cfg = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
606
|
-
cfg.framework = cfg.framework || {};
|
|
607
|
-
cfg.framework.detected = true;
|
|
608
|
-
cfg.framework.installed = installedFrameworks;
|
|
609
|
-
if (configManager.saveSettings) {
|
|
610
|
-
configManager.saveSettings(cfg);
|
|
611
|
-
} else if (configManager.saveConfig) {
|
|
612
|
-
configManager.saveConfig(cfg);
|
|
613
|
-
}
|
|
614
|
-
return true;
|
|
615
|
-
} else {
|
|
616
|
-
const cfg = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
617
|
-
if (cfg.framework) {
|
|
618
|
-
cfg.framework.detected = false;
|
|
619
|
-
if (configManager.saveSettings) {
|
|
620
|
-
configManager.saveSettings(cfg);
|
|
621
|
-
} else if (configManager.saveConfig) {
|
|
622
|
-
configManager.saveConfig(cfg);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
// Always signal that frameworks were not detected
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
628
|
-
} catch (error) {
|
|
629
|
-
console.log(t('init.errors.packageJsonRead'));
|
|
630
|
-
return false; // Treat as no framework detected on error
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Prompt user to continue without i18n framework
|
|
636
|
-
*/
|
|
637
|
-
async promptContinueWithoutI18n() {
|
|
638
|
-
const promptText = this.ui && this.ui.t ? this.ui.t('init.continueWithoutI18nPrompt') : 'Do you want to continue without one? (y/N)';
|
|
639
|
-
const answer = await this.prompt('\n' + promptText);
|
|
640
|
-
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Parse command line arguments
|
|
644
|
-
parseArgs() {
|
|
645
|
-
const args = process.argv.slice(2);
|
|
646
|
-
const parsed = {};
|
|
647
|
-
|
|
648
|
-
args.forEach(arg => {
|
|
649
|
-
if (arg.startsWith('--')) {
|
|
650
|
-
const [key, value] = arg.substring(2).split('=');
|
|
651
|
-
const sanitizedKey = key?.trim();
|
|
652
|
-
const sanitizedValue = value !== undefined ? value.trim() : true;
|
|
653
|
-
|
|
654
|
-
switch (sanitizedKey) {
|
|
655
|
-
case 'source-dir':
|
|
656
|
-
parsed.sourceDir = sanitizedValue;
|
|
657
|
-
break;
|
|
658
|
-
case 'i18n-dir':
|
|
659
|
-
parsed.i18nDir = sanitizedValue;
|
|
660
|
-
break;
|
|
661
|
-
case 'output-dir':
|
|
662
|
-
parsed.outputDir = sanitizedValue;
|
|
663
|
-
break;
|
|
664
|
-
case 'source-language':
|
|
665
|
-
parsed.sourceLanguage = sanitizedValue;
|
|
666
|
-
break;
|
|
667
|
-
case 'ui-language':
|
|
668
|
-
parsed.uiLanguage = sanitizedValue;
|
|
669
|
-
break;
|
|
670
|
-
case 'no-prompt':
|
|
671
|
-
parsed.noPrompt = true;
|
|
672
|
-
break;
|
|
673
|
-
case 'admin-pin':
|
|
674
|
-
parsed.adminPin = sanitizedValue || '';
|
|
675
|
-
break;
|
|
676
|
-
case 'help':
|
|
677
|
-
case 'h':
|
|
678
|
-
parsed.help = true;
|
|
679
|
-
break;
|
|
680
|
-
default:
|
|
681
|
-
// Handle language shorthand flags like --de, --fr
|
|
682
|
-
if (['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(sanitizedKey)) {
|
|
683
|
-
parsed.uiLanguage = sanitizedKey;
|
|
684
|
-
}
|
|
685
|
-
break;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
return parsed;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// Add this run method after the checkI18nDependencies method
|
|
694
|
-
async run() {
|
|
695
|
-
// Add timeout to prevent hanging
|
|
696
|
-
const args = this.parseArgs();
|
|
697
|
-
|
|
698
|
-
const timeout = setTimeout(() => {
|
|
699
|
-
console.error('❌ CLI startup timeout - something is hanging');
|
|
700
|
-
if (args.debug) {
|
|
701
|
-
console.error('🔍 DEBUG: Last known execution point reached');
|
|
702
|
-
}
|
|
703
|
-
process.exit(1);
|
|
704
|
-
}, 10000); // 10 second timeout
|
|
705
|
-
|
|
706
|
-
if (args.debug) {
|
|
707
|
-
console.log('🔍 DEBUG: Starting i18ntk-manage.js...');
|
|
708
|
-
console.log('🔍 DEBUG: Process.argv:', process.argv);
|
|
709
|
-
console.log('🔍 DEBUG: Parsed args:', args);
|
|
710
|
-
console.log('🔍 DEBUG: About to call SetupEnforcer.checkSetupCompleteAsync()');
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
let prompt;
|
|
714
|
-
try {
|
|
715
|
-
// Ensure setup is complete before running any operations
|
|
716
|
-
await SetupEnforcer.checkSetupCompleteAsync();
|
|
717
|
-
|
|
718
|
-
prompt = createPrompt({ noPrompt: args.noPrompt || Boolean(args.adminPin) });
|
|
719
|
-
const interactive = isInteractive({ noPrompt: args.noPrompt || Boolean(args.adminPin) });
|
|
720
|
-
|
|
721
|
-
// Load settings and UI language
|
|
722
|
-
const settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
|
|
723
|
-
this.ui = new UIi18n();
|
|
724
|
-
const uiLanguage = args.uiLanguage || settings.uiLanguage || settings.language || this.config.uiLanguage || 'en';
|
|
725
|
-
this.ui.loadLanguage(uiLanguage);
|
|
726
|
-
|
|
727
|
-
if (args.adminPin) {
|
|
728
|
-
this.adminAuth.verifyPin = async () => true;
|
|
729
|
-
this.prompt = async () => '';
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
if (args.help) {
|
|
733
|
-
this.showHelp();
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
let cfgAfterInitCheck = {};
|
|
738
|
-
if (interactive) {
|
|
739
|
-
cfgAfterInitCheck = await ensureInitializedOrExit(prompt);
|
|
740
|
-
const frameworksDetected = await this.checkI18nDependencies();
|
|
741
|
-
if (!frameworksDetected) {
|
|
742
|
-
await maybePromptFramework(prompt, cfgAfterInitCheck, pkg.version);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
this.config = { ...this.config, ...cfgAfterInitCheck };
|
|
747
|
-
await this.initialize();
|
|
748
|
-
|
|
749
|
-
const rawArgs = process.argv.slice(2); // Preserve original CLI args array for positional checks
|
|
750
|
-
let commandToExecute = null;
|
|
751
|
-
|
|
752
|
-
// Define valid direct commands
|
|
753
|
-
const directCommands = [
|
|
754
|
-
'init', 'analyze', 'validate', 'usage', 'scanner', 'sizing', 'complete', 'fix', 'summary', 'debug', 'workflow'
|
|
755
|
-
];
|
|
756
|
-
|
|
757
|
-
// Handle help immediately without dependency checks
|
|
758
|
-
if (args.help) {
|
|
759
|
-
this.showHelp();
|
|
760
|
-
this.safeClose();
|
|
761
|
-
process.exit(0);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// Handle debug flag
|
|
765
|
-
if (args.debug) {
|
|
766
|
-
// Enable debug mode for this session
|
|
767
|
-
const { blue } = require('../utils/colors-new');
|
|
768
|
-
console.log(blue('Debug mode enabled'));
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Check for --command= argument first
|
|
772
|
-
const commandFlagArg = rawArgs.find(arg => arg.startsWith('--command='));
|
|
773
|
-
if (commandFlagArg) {
|
|
774
|
-
commandToExecute = commandFlagArg.split('=')[1];
|
|
775
|
-
} else if (rawArgs.length > 0 && directCommands.includes(rawArgs[0])) {
|
|
776
|
-
// If no --command=, check if the first argument is a direct command
|
|
777
|
-
commandToExecute = rawArgs[0];
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
if (commandToExecute) {
|
|
781
|
-
console.log(t('ui.executingCommand', { command: commandToExecute }));
|
|
782
|
-
await this.executeCommand(commandToExecute);
|
|
783
|
-
this.safeClose();
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// If no command provided and --no-prompt is set, exit gracefully
|
|
788
|
-
if (args.noPrompt) {
|
|
789
|
-
this.safeClose();
|
|
790
|
-
process.exit(0);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Framework detection is now handled by maybePromptFramework above
|
|
794
|
-
// Skip the redundant checkI18nDependencies prompt
|
|
795
|
-
|
|
796
|
-
// Interactive mode - showInteractiveMenu will handle the title
|
|
797
|
-
await this.showInteractiveMenu();
|
|
798
|
-
|
|
799
|
-
} catch (error) {
|
|
800
|
-
if (this.ui && this.ui.t) {
|
|
801
|
-
console.error(t('common.genericError', { error: error.message }));
|
|
802
|
-
} else {
|
|
803
|
-
console.error(`Error: ${error.message}`);
|
|
804
|
-
}
|
|
805
|
-
process.exit(1);
|
|
806
|
-
} finally {
|
|
807
|
-
if (prompt && typeof prompt.close === 'function') {
|
|
808
|
-
prompt.close();
|
|
809
|
-
}
|
|
810
|
-
this.safeClose();
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
showHelp() {
|
|
815
|
-
const localT = this.ui && this.ui.t ? this.ui.t.bind(this.ui) : (key) => {
|
|
816
|
-
// Fallback help text when UI is not initialized
|
|
817
|
-
const helpTexts = {
|
|
818
|
-
'help.usage': 'Usage: npm run i18ntk [command] [options]',
|
|
819
|
-
'help.interactiveMode': 'Run without arguments for interactive mode',
|
|
820
|
-
'help.initProject': ' init - Initialize i18n project structure',
|
|
821
|
-
'help.analyzeTranslations': ' analyze - Analyze translation files',
|
|
822
|
-
'help.validateTranslations': ' validate - Validate translations for errors',
|
|
823
|
-
'help.checkUsage': ' usage - Check translation usage in code',
|
|
824
|
-
'help.showHelp': ' help - Show this help message',
|
|
825
|
-
'help.availableCommands': '\nAvailable commands:',
|
|
826
|
-
'help.initCommand': ' --command=init Initialize i18n project',
|
|
827
|
-
'help.analyzeCommand': ' --command=analyze Analyze translations',
|
|
828
|
-
'help.validateCommand': ' --command=validate Validate translations',
|
|
829
|
-
'help.usageCommand': ' --command=usage Check translation usage',
|
|
830
|
-
'help.sizingCommand': ' --command=sizing Analyze translation sizing',
|
|
831
|
-
'help.completeCommand': ' --command=complete Run complete analysis',
|
|
832
|
-
'help.summaryCommand': ' --command=summary Generate summary report',
|
|
833
|
-
'help.debugCommand': ' --command=debug Run debug utilities',
|
|
834
|
-
'help.scannerCommand': ' --command=scanner Scan for translation keys'
|
|
835
|
-
};
|
|
836
|
-
return helpTexts[key] || key;
|
|
837
|
-
};
|
|
838
|
-
|
|
839
|
-
console.log(t('help.usage'));
|
|
840
|
-
console.log(t('help.interactiveMode'));
|
|
841
|
-
console.log(t('help.initProject'));
|
|
842
|
-
console.log(t('help.analyzeTranslations'));
|
|
843
|
-
console.log(t('help.validateTranslations'));
|
|
844
|
-
console.log(t('help.checkUsage'));
|
|
845
|
-
console.log(t('help.showHelp'));
|
|
846
|
-
console.log(t('help.availableCommands'));
|
|
847
|
-
console.log(t('help.initCommand'));
|
|
848
|
-
console.log(t('help.analyzeCommand'));
|
|
849
|
-
console.log(t('help.validateCommand'));
|
|
850
|
-
console.log(t('help.usageCommand'));
|
|
851
|
-
console.log(t('help.sizingCommand'));
|
|
852
|
-
console.log(t('help.completeCommand'));
|
|
853
|
-
console.log(t('help.summaryCommand'));
|
|
854
|
-
console.log(t('help.debugCommand'));
|
|
855
|
-
console.log(t('help.scannerCommand'));
|
|
856
|
-
|
|
857
|
-
// Ensure proper exit for direct command execution
|
|
858
|
-
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
859
|
-
this.safeClose();
|
|
860
|
-
process.exit(0);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
/**
|
|
867
|
-
* Determine execution context based on options and environment
|
|
868
|
-
*/
|
|
869
|
-
getExecutionContext(options = {}) {
|
|
870
|
-
// Check if called from interactive menu
|
|
871
|
-
if (options.fromMenu === true) {
|
|
872
|
-
return { type: 'manager', source: 'interactive_menu' };
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// Check if called from workflow/autorun
|
|
876
|
-
if (options.fromWorkflow === true) {
|
|
877
|
-
return { type: 'workflow', source: 'autorun_script' };
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// Check if this is a direct command line execution
|
|
881
|
-
if (process.argv.some(arg => arg.startsWith('--command='))) {
|
|
882
|
-
return { type: 'direct', source: 'command_line' };
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// Default to direct execution
|
|
886
|
-
return { type: 'direct', source: 'unknown' };
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
async executeCommand(command, options = {}) {
|
|
890
|
-
console.log(t('menu.executingCommand', { command }));
|
|
891
|
-
|
|
892
|
-
// Enhanced context detection
|
|
893
|
-
const executionContext = this.getExecutionContext(options);
|
|
894
|
-
const isDirectCommand = executionContext.type === 'direct';
|
|
895
|
-
const isWorkflowExecution = executionContext.type === 'workflow';
|
|
896
|
-
const isManagerExecution = executionContext.type === 'manager';
|
|
897
|
-
|
|
898
|
-
// Ensure UI language is refreshed from settings for workflow and direct execution
|
|
899
|
-
if (isWorkflowExecution || isDirectCommand) {
|
|
900
|
-
this.ui.refreshLanguageFromSettings();
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Check admin authentication for all commands when PIN protection is enabled
|
|
904
|
-
const authRequiredCommands = ['init', 'analyze', 'validate', 'usage', 'scanner', 'complete', 'fix', 'sizing', 'workflow', 'status', 'delete', 'settings', 'debug'];
|
|
905
|
-
if (authRequiredCommands.includes(command)) {
|
|
906
|
-
const authPassed = await this.checkAdminAuth();
|
|
907
|
-
if (!authPassed) {
|
|
908
|
-
if (!this.isNonInteractiveMode() && !isDirectCommand) {
|
|
909
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
910
|
-
await this.showInteractiveMenu();
|
|
911
|
-
}
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
try {
|
|
917
|
-
switch (command) {
|
|
918
|
-
case 'init':
|
|
919
|
-
const initializer = new I18nInitializer(this.config);
|
|
920
|
-
await initializer.run({fromMenu: isManagerExecution});
|
|
921
|
-
break;
|
|
922
|
-
case 'analyze':
|
|
923
|
-
const analyzer = new I18nAnalyzer();
|
|
924
|
-
await analyzer.run({fromMenu: isManagerExecution});
|
|
925
|
-
break;
|
|
926
|
-
case 'validate':
|
|
927
|
-
const validator = new I18nValidator();
|
|
928
|
-
await validator.run({fromMenu: isManagerExecution});
|
|
929
|
-
break;
|
|
930
|
-
case 'usage':
|
|
931
|
-
const usageAnalyzer = new I18nUsageAnalyzer();
|
|
932
|
-
await usageAnalyzer.run({fromMenu: isManagerExecution});
|
|
933
|
-
break;
|
|
934
|
-
case 'sizing':
|
|
935
|
-
const sizingAnalyzer = new I18nSizingAnalyzer();
|
|
936
|
-
await sizingAnalyzer.run({fromMenu: isManagerExecution});
|
|
937
|
-
break;
|
|
938
|
-
case 'complete':
|
|
939
|
-
const completeTool = require('./i18ntk-complete');
|
|
940
|
-
const tool = new completeTool();
|
|
941
|
-
await tool.run({fromMenu: isManagerExecution});
|
|
942
|
-
break;
|
|
943
|
-
case 'fix':
|
|
944
|
-
const fixerTool = new I18nFixer();
|
|
945
|
-
await fixerTool.run({fromMenu: isManagerExecution});
|
|
946
|
-
break;
|
|
947
|
-
|
|
948
|
-
case 'scanner':
|
|
949
|
-
const Scanner = require('./i18ntk-scanner');
|
|
950
|
-
const scanner = new Scanner();
|
|
951
|
-
await scanner.initialize();
|
|
952
|
-
await scanner.run();
|
|
953
|
-
break;
|
|
954
|
-
|
|
955
|
-
case 'debug':
|
|
956
|
-
console.log('Debug functionality is not available in this version.');
|
|
957
|
-
break;
|
|
958
|
-
case 'help':
|
|
959
|
-
this.showHelp();
|
|
960
|
-
if (isManagerExecution && !this.isNonInteractiveMode()) {
|
|
961
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
962
|
-
await this.showInteractiveMenu();
|
|
963
|
-
} else {
|
|
964
|
-
console.log(t('workflow.exitingCompleted'));
|
|
965
|
-
this.safeClose();
|
|
966
|
-
process.exit(0);
|
|
967
|
-
}
|
|
968
|
-
return;
|
|
969
|
-
break;
|
|
970
|
-
default:
|
|
971
|
-
console.log(t('menu.unknownCommand', { command }));
|
|
972
|
-
this.showHelp();
|
|
973
|
-
// No return here, let the completion logic handle the exit/menu return
|
|
974
|
-
break;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
// Handle command completion based on execution context
|
|
978
|
-
console.log(t('operations.completed'));
|
|
979
|
-
|
|
980
|
-
if (isManagerExecution && !this.isNonInteractiveMode()) {
|
|
981
|
-
// Interactive menu execution - return to menu
|
|
982
|
-
await this.prompt(t('menu.returnToMainMenu'));
|
|
983
|
-
await this.showInteractiveMenu();
|
|
984
|
-
} else {
|
|
985
|
-
// Direct commands, non-interactive mode, or workflow execution - exit immediately
|
|
986
|
-
console.log(t('workflow.exitingCompleted'));
|
|
987
|
-
this.safeClose();
|
|
988
|
-
process.exit(0);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
} catch (error) {
|
|
992
|
-
console.error(t('common.errorExecutingCommand', { error: error.message }));
|
|
993
|
-
|
|
994
|
-
if (isManagerExecution && !this.isNonInteractiveMode()) {
|
|
995
|
-
// Interactive menu execution - show error and return to menu
|
|
996
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
997
|
-
await this.showInteractiveMenu();
|
|
998
|
-
} else if (isDirectCommand && !this.isNonInteractiveMode()) {
|
|
999
|
-
// Direct command execution - show "enter to continue" and exit with error
|
|
1000
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1001
|
-
this.safeClose();
|
|
1002
|
-
process.exit(1);
|
|
1003
|
-
} else {
|
|
1004
|
-
// Non-interactive mode or workflow execution - exit immediately with error
|
|
1005
|
-
this.safeClose();
|
|
1006
|
-
process.exit(1);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
// Add admin authentication check
|
|
1012
|
-
async checkAdminAuth() {
|
|
1013
|
-
const isRequired = await this.adminAuth.isAuthRequired();
|
|
1014
|
-
if (!isRequired) {
|
|
1015
|
-
return true;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// Check if admin PIN was provided via command line
|
|
1019
|
-
const args = this.parseArgs();
|
|
1020
|
-
if (args.adminPin) {
|
|
1021
|
-
const isValid = await this.adminAuth.verifyPin(args.adminPin);
|
|
1022
|
-
if (isValid) {
|
|
1023
|
-
console.log(t('adminCli.authSuccess'));
|
|
1024
|
-
return true;
|
|
1025
|
-
} else {
|
|
1026
|
-
console.log(t('adminCli.invalidPin'));
|
|
1027
|
-
return false;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
console.log(t('adminCli.authRequired'));
|
|
1032
|
-
const cliHelper = require('../utils/cli-helper');
|
|
1033
|
-
const pin = await cliHelper.promptPin(t('adminCli.enterPin'));
|
|
1034
|
-
const isValid = await this.adminAuth.verifyPin(pin);
|
|
1035
|
-
|
|
1036
|
-
if (!isValid) {
|
|
1037
|
-
console.log(t('adminCli.invalidPin'));
|
|
1038
|
-
return false;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
console.log(t('adminCli.authSuccess'));
|
|
1042
|
-
return true;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
async showInteractiveMenu() {
|
|
1046
|
-
|
|
1047
|
-
// Check if we're in non-interactive mode (like echo 0 | node script)
|
|
1048
|
-
if (this.isNonInteractiveMode()) {
|
|
1049
|
-
console.log(`\n${t('menu.title')}`);
|
|
1050
|
-
console.log(t('menu.separator'));
|
|
1051
|
-
console.log(`1. ${t('menu.options.init')}`);
|
|
1052
|
-
console.log(`2. ${t('menu.options.analyze')}`);
|
|
1053
|
-
console.log(`3. ${t('menu.options.validate')}`);
|
|
1054
|
-
console.log(`4. ${t('menu.options.usage')}`);
|
|
1055
|
-
console.log(`5. ${t('menu.options.complete')}`);
|
|
1056
|
-
console.log(`6. ${t('menu.options.sizing')}`);
|
|
1057
|
-
console.log(`7. ${t('menu.options.fix')}`);
|
|
1058
|
-
console.log(`8. ${t('menu.options.status')}`);
|
|
1059
|
-
console.log(`9. ${t('menu.options.delete')}`);
|
|
1060
|
-
console.log(`10. ${t('menu.options.settings')}`);
|
|
1061
|
-
console.log(`11. ${t('menu.options.help')}`);
|
|
1062
|
-
console.log(`12. ${t('menu.options.language')}`);
|
|
1063
|
-
console.log(`13. ${t('menu.options.scanner')}`);
|
|
1064
|
-
console.log(`0. ${t('menu.options.exit')}`);
|
|
1065
|
-
|
|
1066
|
-
console.log('\n' + t('menu.nonInteractiveModeWarning'));
|
|
1067
|
-
console.log(t('menu.useDirectExecution'));
|
|
1068
|
-
console.log(t('menu.useHelpForCommands'));
|
|
1069
|
-
this.safeClose();
|
|
1070
|
-
process.exit(0);
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
console.log(`\n${t('menu.title')}`);
|
|
1075
|
-
console.log(t('menu.separator'));
|
|
1076
|
-
console.log(`1. ${t('menu.options.init')}`);
|
|
1077
|
-
console.log(`2. ${t('menu.options.analyze')}`);
|
|
1078
|
-
console.log(`3. ${t('menu.options.validate')}`);
|
|
1079
|
-
console.log(`4. ${t('menu.options.usage')}`);
|
|
1080
|
-
console.log(`5. ${t('menu.options.complete')}`);
|
|
1081
|
-
console.log(`6. ${t('menu.options.sizing')}`);
|
|
1082
|
-
console.log(`7. ${t('menu.options.fix')}`);
|
|
1083
|
-
console.log(`8. ${t('menu.options.status')}`);
|
|
1084
|
-
console.log(`9. ${t('menu.options.delete')}`);
|
|
1085
|
-
console.log(`10. ${t('menu.options.settings')}`);
|
|
1086
|
-
console.log(`11. ${t('menu.options.help')}`);
|
|
1087
|
-
console.log(`12. ${t('menu.options.language')}`);
|
|
1088
|
-
console.log(`13. ${t('menu.options.scanner')}`);
|
|
1089
|
-
console.log(`0. ${t('menu.options.exit')}`);
|
|
1090
|
-
|
|
1091
|
-
const choice = await this.prompt('\n' + t('menu.selectOptionPrompt'));
|
|
1092
|
-
|
|
1093
|
-
switch (choice.trim()) {
|
|
1094
|
-
case '1':
|
|
1095
|
-
await this.executeCommand('init', {fromMenu: true});
|
|
1096
|
-
break;
|
|
1097
|
-
case '2':
|
|
1098
|
-
await this.executeCommand('analyze', {fromMenu: true});
|
|
1099
|
-
break;
|
|
1100
|
-
case '3':
|
|
1101
|
-
await this.executeCommand('validate', {fromMenu: true});
|
|
1102
|
-
break;
|
|
1103
|
-
case '4':
|
|
1104
|
-
await this.executeCommand('usage', {fromMenu: true});
|
|
1105
|
-
break;
|
|
1106
|
-
case '5':
|
|
1107
|
-
await this.executeCommand('complete', {fromMenu: true});
|
|
1108
|
-
break;
|
|
1109
|
-
case '6':
|
|
1110
|
-
await this.executeCommand('sizing', {fromMenu: true});
|
|
1111
|
-
break;
|
|
1112
|
-
case '7':
|
|
1113
|
-
await this.executeCommand('fix', {fromMenu: true});
|
|
1114
|
-
break;
|
|
1115
|
-
case '8':
|
|
1116
|
-
// Check for PIN protection
|
|
1117
|
-
const authRequired = await this.adminAuth.isAuthRequiredForScript('summaryReports');
|
|
1118
|
-
if (authRequired) {
|
|
1119
|
-
console.log(`\n${t('adminCli.protectedAccess')}`);
|
|
1120
|
-
const cliHelper = require('../utils/cli-helper');
|
|
1121
|
-
const pin = await cliHelper.promptPin(t('adminCli.enterPin') + ': ');
|
|
1122
|
-
const isValid = await this.adminAuth.verifyPin(pin);
|
|
1123
|
-
|
|
1124
|
-
if (!isValid) {
|
|
1125
|
-
console.log(t('adminCli.invalidPin'));
|
|
1126
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1127
|
-
await this.showInteractiveMenu();
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
console.log(t('adminCli.accessGranted'));
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
console.log(t('summary.status.generating'));
|
|
1135
|
-
try {
|
|
1136
|
-
const summaryTool = require('./i18ntk-summary');
|
|
1137
|
-
const summary = new summaryTool();
|
|
1138
|
-
await summary.run({ fromMenu: true });
|
|
1139
|
-
console.log(t('summary.status.completed'));
|
|
1140
|
-
|
|
1141
|
-
// Check if we're in interactive mode before prompting
|
|
1142
|
-
if (!this.isNonInteractiveMode()) {
|
|
1143
|
-
try {
|
|
1144
|
-
await this.prompt('\n' + t('debug.pressEnterToContinue'));
|
|
1145
|
-
await this.showInteractiveMenu();
|
|
1146
|
-
} catch (error) {
|
|
1147
|
-
console.log(t('menu.returning'));
|
|
1148
|
-
process.exit(0);
|
|
1149
|
-
}
|
|
1150
|
-
} else {
|
|
1151
|
-
console.log(t('status.exitingCompleted'));
|
|
1152
|
-
process.exit(0);
|
|
1153
|
-
}
|
|
1154
|
-
} catch (error) {
|
|
1155
|
-
console.error(t('common.errorGeneratingStatusSummary', { error: error.message }));
|
|
1156
|
-
|
|
1157
|
-
// Check if we're in interactive mode before prompting
|
|
1158
|
-
if (!this.isNonInteractiveMode()) {
|
|
1159
|
-
try {
|
|
1160
|
-
await this.prompt('\n' + t('debug.pressEnterToContinue'));
|
|
1161
|
-
await this.showInteractiveMenu();
|
|
1162
|
-
} catch (error) {
|
|
1163
|
-
console.log(t('menu.returning'));
|
|
1164
|
-
process.exit(0);
|
|
1165
|
-
}
|
|
1166
|
-
} else {
|
|
1167
|
-
console.log(t('common.errorExiting'));
|
|
1168
|
-
process.exit(1);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
break;
|
|
1172
|
-
case '9':
|
|
1173
|
-
await this.deleteReports();
|
|
1174
|
-
break;
|
|
1175
|
-
case '10':
|
|
1176
|
-
await this.showSettingsMenu();
|
|
1177
|
-
break;
|
|
1178
|
-
case '11':
|
|
1179
|
-
this.showHelp();
|
|
1180
|
-
await this.prompt(t('menu.returnToMainMenu'));
|
|
1181
|
-
await this.showInteractiveMenu();
|
|
1182
|
-
break;
|
|
1183
|
-
case '12':
|
|
1184
|
-
await this.showLanguageMenu();
|
|
1185
|
-
break;
|
|
1186
|
-
case '13':
|
|
1187
|
-
await this.executeCommand('scanner', {fromMenu: true});
|
|
1188
|
-
break;
|
|
1189
|
-
case '0':
|
|
1190
|
-
console.log(t('menu.goodbye'));
|
|
1191
|
-
this.safeClose();
|
|
1192
|
-
process.exit(0);
|
|
1193
|
-
default:
|
|
1194
|
-
console.log(t('menu.invalidChoice'));
|
|
1195
|
-
await this.showInteractiveMenu();
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Language Menu
|
|
1200
|
-
async showLanguageMenu() {
|
|
1201
|
-
console.log(`\n${t('language.title')}`);
|
|
1202
|
-
console.log(t('language.separator'));
|
|
1203
|
-
console.log(t('language.current', { language: this.ui.getLanguageDisplayName(this.ui.getCurrentLanguage()) }));
|
|
1204
|
-
console.log('\n' + t('language.available'));
|
|
1205
|
-
|
|
1206
|
-
this.ui.availableLanguages.forEach((lang, index) => {
|
|
1207
|
-
const displayName = this.ui.getLanguageDisplayName(lang);
|
|
1208
|
-
const current = lang === this.ui.getCurrentLanguage() ? ' ✓' : '';
|
|
1209
|
-
console.log(t('language.languageOption', { index: index + 1, displayName, current }));
|
|
1210
|
-
});
|
|
1211
|
-
|
|
1212
|
-
console.log(`0. ${t('language.backToMainMenu')}`);
|
|
1213
|
-
|
|
1214
|
-
const choice = await this.prompt('\n' + t('language.prompt'));
|
|
1215
|
-
const choiceNum = parseInt(choice);
|
|
1216
|
-
|
|
1217
|
-
if (choiceNum === 0) {
|
|
1218
|
-
await this.showInteractiveMenu();
|
|
1219
|
-
return;
|
|
1220
|
-
} else if (choiceNum >= 1 && choiceNum <= this.ui.availableLanguages.length) {
|
|
1221
|
-
const selectedLang = this.ui.availableLanguages[choiceNum - 1];
|
|
1222
|
-
await this.ui.changeLanguage(selectedLang);
|
|
1223
|
-
console.log(t('language.changed', { language: this.ui.getLanguageDisplayName(selectedLang) }));
|
|
1224
|
-
|
|
1225
|
-
// Force reload translations for the entire system
|
|
1226
|
-
const { loadTranslations } = require('../utils/i18n-helper');
|
|
1227
|
-
loadTranslations(selectedLang);
|
|
1228
|
-
|
|
1229
|
-
// Return to main menu with new language
|
|
1230
|
-
await this.prompt('\n' + t('language.pressEnterToContinue'));
|
|
1231
|
-
await this.showInteractiveMenu();
|
|
1232
|
-
} else {
|
|
1233
|
-
console.log(t('language.invalid'));
|
|
1234
|
-
await this.prompt('\n' + t('language.pressEnterToContinue'));
|
|
1235
|
-
await this.showLanguageMenu();
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// Debug Tools Menu
|
|
1240
|
-
async showDebugMenu() {
|
|
1241
|
-
// Check for PIN protection
|
|
1242
|
-
const authRequired = await this.adminAuth.isAuthRequiredForScript('debugMenu');
|
|
1243
|
-
if (authRequired) {
|
|
1244
|
-
console.log(`\n${t('adminPin.protectedAccess')}`);
|
|
1245
|
-
const cliHelper = require('../utils/cli-helper');
|
|
1246
|
-
const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
|
|
1247
|
-
const isValid = await this.adminAuth.verifyPin(pin);
|
|
1248
|
-
|
|
1249
|
-
if (!isValid) {
|
|
1250
|
-
console.log(t('adminPin.invalidPin'));
|
|
1251
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1252
|
-
await this.showInteractiveMenu();
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
console.log(t('adminPin.accessGranted'));
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
console.log(`\n${t('debug.title')}`);
|
|
1260
|
-
console.log(t('debug.separator'));
|
|
1261
|
-
console.log(t('debug.mainDebuggerSystemDiagnostics'));
|
|
1262
|
-
console.log(t('debug.debugLogs'));
|
|
1263
|
-
console.log(t('debug.backToMainMenu'));
|
|
1264
|
-
|
|
1265
|
-
const choice = await this.prompt('\n' + t('debug.selectOption'));
|
|
1266
|
-
|
|
1267
|
-
switch (choice.trim()) {
|
|
1268
|
-
case '1':
|
|
1269
|
-
await this.runDebugTool('debugger.js', 'Main Debugger');
|
|
1270
|
-
break;
|
|
1271
|
-
case '2':
|
|
1272
|
-
await this.viewDebugLogs();
|
|
1273
|
-
break;
|
|
1274
|
-
case '0':
|
|
1275
|
-
await this.showInteractiveMenu();
|
|
1276
|
-
return;
|
|
1277
|
-
default:
|
|
1278
|
-
console.log(t('debug.invalidChoiceSelectRange'));
|
|
1279
|
-
await this.showDebugMenu();
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// Run a debug tool
|
|
1284
|
-
async runDebugTool(toolName, displayName) {
|
|
1285
|
-
console.log(t('debug.runningDebugTool', { displayName }));
|
|
1286
|
-
try {
|
|
1287
|
-
const toolPath = path.join(__dirname, '..', 'scripts', 'debug', toolName);
|
|
1288
|
-
if (SecurityUtils.safeExistsSync(toolPath)) {
|
|
1289
|
-
console.log(`Debug tool available: ${toolName}`);
|
|
1290
|
-
console.log(`To run this tool manually: node "${toolPath}"`);
|
|
1291
|
-
console.log(`Working directory: ${path.join(__dirname, '..')}`);
|
|
1292
|
-
} else {
|
|
1293
|
-
console.log(t('debug.debugToolNotFound', { toolName }));
|
|
1294
|
-
}
|
|
1295
|
-
} catch (error) {
|
|
1296
|
-
console.error(t('debug.errorRunningDebugTool', { displayName, error: error.message }));
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
await this.prompt('\n' + t('menu.pressEnterToContinue'));
|
|
1301
|
-
await this.showDebugMenu();
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// View debug logs
|
|
1305
|
-
async viewDebugLogs() {
|
|
1306
|
-
console.log(`\n${t('debug.recentDebugLogs')}`);
|
|
1307
|
-
console.log('============================================================');
|
|
1308
|
-
|
|
1309
|
-
try {
|
|
1310
|
-
const logsDir = path.join(__dirname, '..', 'scripts', 'debug', 'logs');
|
|
1311
|
-
if (SecurityUtils.safeExistsSync(logsDir)) {
|
|
1312
|
-
const files = fs.readdirSync(logsDir)
|
|
1313
|
-
.filter(file => file.endsWith('.log') || file.endsWith('.txt'))
|
|
1314
|
-
.sort((a, b) => {
|
|
1315
|
-
const statA = fs.statSync(path.join(logsDir, a));
|
|
1316
|
-
const statB = fs.statSync(path.join(logsDir, b));
|
|
1317
|
-
return statB.mtime - statA.mtime;
|
|
1318
|
-
})
|
|
1319
|
-
.slice(0, 5);
|
|
1320
|
-
|
|
1321
|
-
if (files.length > 0) {
|
|
1322
|
-
files.forEach((file, index) => {
|
|
1323
|
-
const filePath = path.join(logsDir, file);
|
|
1324
|
-
const stats = fs.statSync(filePath);
|
|
1325
|
-
console.log(`${index + 1}. ${file} (${stats.mtime.toLocaleString()})`);
|
|
1326
|
-
});
|
|
1327
|
-
|
|
1328
|
-
const choice = await this.prompt('\n' + t('debug.selectLogPrompt', { count: files.length }));
|
|
1329
|
-
const fileIndex = parseInt(choice) - 1;
|
|
1330
|
-
|
|
1331
|
-
if (fileIndex >= 0 && fileIndex < files.length) {
|
|
1332
|
-
const logContent = SecurityUtils.safeReadFileSync(path.join(logsDir, files[fileIndex]), logsDir, 'utf8');
|
|
1333
|
-
console.log(`\n${t('debug.contentOf', { filename: files[fileIndex] })}:`);
|
|
1334
|
-
console.log('============================================================');
|
|
1335
|
-
console.log(logContent.slice(-2000)); // Show last 2000 characters
|
|
1336
|
-
console.log('============================================================');
|
|
1337
|
-
}
|
|
1338
|
-
} else {
|
|
1339
|
-
console.log(t('debug.noDebugLogsFound'));
|
|
1340
|
-
}
|
|
1341
|
-
} else {
|
|
1342
|
-
console.log(t('debug.debugLogsDirectoryNotFound'));
|
|
1343
|
-
}
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
console.error(t('errors.errorReadingDebugLogs', { error: error.message }));
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
await this.prompt('\n' + t('menu.pressEnterToContinue'));
|
|
1349
|
-
await this.showInteractiveMenu();
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
// Enhanced delete reports and logs functionality
|
|
1353
|
-
async deleteReports() {
|
|
1354
|
-
// Check for PIN protection
|
|
1355
|
-
const authRequired = await this.adminAuth.isAuthRequiredForScript('deleteReports');
|
|
1356
|
-
if (authRequired) {
|
|
1357
|
-
console.log(`\n${t('adminPin.protectedAccess')}`);
|
|
1358
|
-
const cliHelper = require('../utils/cli-helper');
|
|
1359
|
-
const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
|
|
1360
|
-
const isValid = await this.adminAuth.verifyPin(pin);
|
|
1361
|
-
|
|
1362
|
-
if (!isValid) {
|
|
1363
|
-
console.log(t('adminPin.invalidPin'));
|
|
1364
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1365
|
-
await this.showInteractiveMenu();
|
|
1366
|
-
return;
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
console.log(t('adminPin.accessGranted'));
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
console.log(`\n${t('operations.deleteReportsTitle')}`);
|
|
1373
|
-
console.log('============================================================');
|
|
1374
|
-
|
|
1375
|
-
const targetDirs = [
|
|
1376
|
-
{ path: path.join(process.cwd(), 'i18ntk-reports'), name: 'Reports', type: 'reports' },
|
|
1377
|
-
{ path: path.join(process.cwd(), 'reports'), name: 'Legacy Reports', type: 'reports' },
|
|
1378
|
-
{ path: path.join(process.cwd(), 'reports', 'backups'), name: 'Reports Backups', type: 'backups' },
|
|
1379
|
-
{ path: path.join(process.cwd(), 'scripts', 'debug', 'logs'), name: 'Debug Logs', type: 'logs' },
|
|
1380
|
-
{ path: path.join(process.cwd(), 'scripts', 'debug', 'reports'), name: 'Debug Reports', type: 'reports' },
|
|
1381
|
-
{ path: path.join(process.cwd(), 'settings', 'backups'), name: 'Settings Backups', type: 'backups' },
|
|
1382
|
-
{ path: path.join(process.cwd(), 'utils', 'i18ntk-reports'), name: 'Utils Reports', type: 'reports' }
|
|
1383
|
-
].filter(dir => dir.path && typeof dir.path === 'string');
|
|
1384
|
-
|
|
1385
|
-
try {
|
|
1386
|
-
console.log(t('operations.scanningForFiles'));
|
|
1387
|
-
|
|
1388
|
-
let availableDirs = [];
|
|
1389
|
-
|
|
1390
|
-
// Check which directories exist and have files
|
|
1391
|
-
for (const dir of targetDirs) {
|
|
1392
|
-
if (SecurityUtils.safeExistsSync(dir.path)) {
|
|
1393
|
-
const files = this.getAllReportFiles(dir.path);
|
|
1394
|
-
if (files.length > 0) {
|
|
1395
|
-
availableDirs.push({
|
|
1396
|
-
...dir,
|
|
1397
|
-
files: files.map(file => ({ path: file, dir: dir.path })),
|
|
1398
|
-
count: files.length
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
if (availableDirs.length === 0) {
|
|
1405
|
-
console.log(t('operations.noFilesFoundToDelete'));
|
|
1406
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1407
|
-
await this.showInteractiveMenu();
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// Show available directories
|
|
1412
|
-
console.log(t('operations.availableDirectories'));
|
|
1413
|
-
availableDirs.forEach((dir, index) => {
|
|
1414
|
-
console.log(` ${index + 1}. ${dir.name} (${dir.count} files)`);
|
|
1415
|
-
});
|
|
1416
|
-
console.log(` ${availableDirs.length + 1}. ${t('operations.allDirectories')}`);
|
|
1417
|
-
console.log(` 0. ${t('operations.cancelOption')}`);
|
|
1418
|
-
|
|
1419
|
-
const dirChoice = await this.prompt(`\nSelect directory to clean (0-${availableDirs.length + 1}): `);
|
|
1420
|
-
const dirIndex = parseInt(dirChoice) - 1;
|
|
1421
|
-
|
|
1422
|
-
let selectedDirs = [];
|
|
1423
|
-
|
|
1424
|
-
if (dirChoice.trim() === '0') {
|
|
1425
|
-
console.log(t('operations.cancelled'));
|
|
1426
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1427
|
-
await this.showInteractiveMenu();
|
|
1428
|
-
return;
|
|
1429
|
-
} else if (dirIndex === availableDirs.length) {
|
|
1430
|
-
selectedDirs = availableDirs;
|
|
1431
|
-
} else if (dirIndex >= 0 && dirIndex < availableDirs.length) {
|
|
1432
|
-
selectedDirs = [availableDirs[dirIndex]];
|
|
1433
|
-
} else {
|
|
1434
|
-
console.log(t('operations.invalidSelection'));
|
|
1435
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1436
|
-
await this.showInteractiveMenu();
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
// Collect all files from selected directories
|
|
1441
|
-
let allFiles = [];
|
|
1442
|
-
selectedDirs.forEach(dir => {
|
|
1443
|
-
allFiles.push(...dir.files);
|
|
1444
|
-
});
|
|
1445
|
-
|
|
1446
|
-
console.log(t('operations.foundFilesInSelectedDirectories', { count: allFiles.length }));
|
|
1447
|
-
selectedDirs.forEach(dir => {
|
|
1448
|
-
console.log(` 📁 ${dir.name}: ${dir.count} files`);
|
|
1449
|
-
});
|
|
1450
|
-
|
|
1451
|
-
// Show deletion options
|
|
1452
|
-
console.log(t('operations.deletionOptions'));
|
|
1453
|
-
console.log(` 1. ${t('operations.deleteAllFiles')}`);
|
|
1454
|
-
console.log(` 2. ${t('operations.keepLast3Files')}`);
|
|
1455
|
-
console.log(` 3. ${t('operations.keepLast5Files')}`);
|
|
1456
|
-
console.log(` 0. ${t('operations.cancelReportOption')}`);
|
|
1457
|
-
|
|
1458
|
-
const option = await this.prompt('\nSelect option (0-3): ');
|
|
1459
|
-
|
|
1460
|
-
let filesToDelete = [];
|
|
1461
|
-
|
|
1462
|
-
switch (option.trim()) {
|
|
1463
|
-
case '1':
|
|
1464
|
-
filesToDelete = allFiles;
|
|
1465
|
-
break;
|
|
1466
|
-
case '2':
|
|
1467
|
-
filesToDelete = this.getFilesToDeleteKeepLast(allFiles, 3);
|
|
1468
|
-
break;
|
|
1469
|
-
case '3':
|
|
1470
|
-
filesToDelete = this.getFilesToDeleteKeepLast(allFiles, 5);
|
|
1471
|
-
break;
|
|
1472
|
-
case '0':
|
|
1473
|
-
console.log(t('operations.cancelled'));
|
|
1474
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1475
|
-
await this.showInteractiveMenu();
|
|
1476
|
-
return;
|
|
1477
|
-
default:
|
|
1478
|
-
console.log(t('menu.invalidOption'));
|
|
1479
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1480
|
-
await this.showInteractiveMenu();
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
if (filesToDelete.length === 0) {
|
|
1485
|
-
console.log(t('operations.noFilesToDelete'));
|
|
1486
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1487
|
-
await this.showInteractiveMenu();
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
console.log(t('operations.filesToDeleteCount', { count: filesToDelete.length }));
|
|
1492
|
-
console.log(t('operations.filesToKeepCount', { count: allFiles.length - filesToDelete.length }));
|
|
1493
|
-
|
|
1494
|
-
const confirm = await this.prompt(t('operations.confirmDeletion'));
|
|
1495
|
-
|
|
1496
|
-
if (confirm.toLowerCase() === 'y' || confirm.toLowerCase() === 'yes') {
|
|
1497
|
-
let deletedCount = 0;
|
|
1498
|
-
|
|
1499
|
-
for (const fileInfo of filesToDelete) {
|
|
1500
|
-
try {
|
|
1501
|
-
fs.unlinkSync(fileInfo.path);
|
|
1502
|
-
console.log(t('operations.deletedFile', { filename: path.basename(fileInfo.path) }));
|
|
1503
|
-
deletedCount++;
|
|
1504
|
-
} catch (error) {
|
|
1505
|
-
console.log(t('operations.failedToDeleteFile', { filename: path.basename(fileInfo.path), error: error.message }));
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
console.log(`\n🎉 Successfully deleted ${deletedCount} files!`);
|
|
1510
|
-
} else {
|
|
1511
|
-
console.log(t('operations.cancelled'));
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
} catch (error) {
|
|
1515
|
-
console.error(`❌ Error during deletion process: ${error.message}`);
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1519
|
-
await this.showInteractiveMenu();
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// Helper method to get all report and log files recursively
|
|
1523
|
-
getAllReportFiles(dir) {
|
|
1524
|
-
if (!dir || typeof dir !== 'string') {
|
|
1525
|
-
return [];
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
let files = [];
|
|
1529
|
-
|
|
1530
|
-
try {
|
|
1531
|
-
if (!SecurityUtils.safeExistsSync(dir)) {
|
|
1532
|
-
return [];
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
const items = fs.readdirSync(dir);
|
|
1536
|
-
for (const item of items) {
|
|
1537
|
-
const fullPath = path.join(dir, item);
|
|
1538
|
-
|
|
1539
|
-
try {
|
|
1540
|
-
const stat = fs.statSync(fullPath);
|
|
1541
|
-
|
|
1542
|
-
if (stat.isDirectory()) {
|
|
1543
|
-
files.push(...this.getAllReportFiles(fullPath));
|
|
1544
|
-
} else if (
|
|
1545
|
-
// Common report file extensions
|
|
1546
|
-
item.endsWith('.json') ||
|
|
1547
|
-
item.endsWith('.html') ||
|
|
1548
|
-
item.endsWith('.txt') ||
|
|
1549
|
-
item.endsWith('.log') ||
|
|
1550
|
-
item.endsWith('.csv') ||
|
|
1551
|
-
item.endsWith('.md') ||
|
|
1552
|
-
// Specific report filename patterns
|
|
1553
|
-
item.includes('-report.') ||
|
|
1554
|
-
item.includes('_report.') ||
|
|
1555
|
-
item.includes('report-') ||
|
|
1556
|
-
item.includes('report_') ||
|
|
1557
|
-
item.includes('analysis-') ||
|
|
1558
|
-
item.includes('validation-')
|
|
1559
|
-
) {
|
|
1560
|
-
files.push(fullPath);
|
|
1561
|
-
}
|
|
1562
|
-
} catch (error) {
|
|
1563
|
-
// Skip individual files that can't be accessed
|
|
1564
|
-
continue;
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
} catch (error) {
|
|
1568
|
-
// Silent fail for inaccessible directories
|
|
1569
|
-
console.log(`⚠️ Could not access directory: ${dir}`);
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
return files;
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
// Helper method to determine which files to delete when keeping last N files
|
|
1576
|
-
getFilesToDeleteKeepLast(allFiles, keepCount = 3) {
|
|
1577
|
-
// Sort files by modification time (newest first)
|
|
1578
|
-
const sortedFiles = allFiles.sort((a, b) => {
|
|
1579
|
-
try {
|
|
1580
|
-
const statA = fs.statSync(a.path || a);
|
|
1581
|
-
const statB = fs.statSync(b.path || b);
|
|
1582
|
-
return statB.mtime.getTime() - statA.mtime.getTime();
|
|
1583
|
-
} catch (error) {
|
|
1584
|
-
// If stat fails, sort by filename as fallback
|
|
1585
|
-
const pathA = a.path || a;
|
|
1586
|
-
const pathB = b.path || b;
|
|
1587
|
-
return pathB.localeCompare(pathA);
|
|
1588
|
-
}
|
|
1589
|
-
});
|
|
1590
|
-
|
|
1591
|
-
// Keep the N newest files, delete the rest
|
|
1592
|
-
return sortedFiles.slice(keepCount);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
// Settings Menu
|
|
1596
|
-
async showSettingsMenu() {
|
|
1597
|
-
try {
|
|
1598
|
-
// Check for PIN protection
|
|
1599
|
-
const authRequired = await this.adminAuth.isAuthRequiredForScript('settingsMenu');
|
|
1600
|
-
if (authRequired) {
|
|
1601
|
-
console.log(`\n${t('adminPin.protectedAccess')}`);
|
|
1602
|
-
const cliHelper = require('../utils/cli-helper');
|
|
1603
|
-
const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
|
|
1604
|
-
const isValid = await this.adminAuth.verifyPin(pin);
|
|
1605
|
-
|
|
1606
|
-
if (!isValid) {
|
|
1607
|
-
console.log(t('adminPin.invalidPin'));
|
|
1608
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1609
|
-
await this.showInteractiveMenu();
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
console.log(t('adminPin.accessGranted'));
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
const SettingsCLI = require('../settings/settings-cli');
|
|
1617
|
-
const settingsCLI = new SettingsCLI();
|
|
1618
|
-
await settingsCLI.run();
|
|
1619
|
-
} catch (error) {
|
|
1620
|
-
console.error('❌ Error opening settings:', error.message);
|
|
1621
|
-
await this.prompt(t('menu.pressEnterToContinue'));
|
|
1622
|
-
}
|
|
1623
|
-
await this.showInteractiveMenu();
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
prompt(question) {
|
|
1629
|
-
const cliHelper = require('../utils/cli-helper');
|
|
1630
|
-
// If interactive not available, return empty string to avoid hangs
|
|
1631
|
-
if (!process.stdin.isTTY || process.stdin.destroyed) {
|
|
1632
|
-
console.log('\n⚠️ Interactive input not available, using default response.');
|
|
1633
|
-
return Promise.resolve('');
|
|
1634
|
-
}
|
|
1635
|
-
return cliHelper.prompt(`${question} `);
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
// Safe method to check if we're in non-interactive mode
|
|
1639
|
-
isNonInteractiveMode() {
|
|
1640
|
-
return !process.stdin.isTTY || process.stdin.destroyed || this.isReadlineClosed;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
safeClose() {
|
|
1644
|
-
if (this.rl && !this.isReadlineClosed) {
|
|
1645
|
-
try {
|
|
1646
|
-
this.rl.close();
|
|
1647
|
-
this.isReadlineClosed = true;
|
|
1648
|
-
} catch (error) {
|
|
1649
|
-
// Ignore close errors
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
// Run if called directly
|
|
1656
|
-
if (require.main === module) {
|
|
1657
|
-
// Handle version and help immediately before any initialization
|
|
1658
|
-
const args = process.argv.slice(2);
|
|
1659
|
-
|
|
1660
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
1661
|
-
try {
|
|
1662
|
-
const packageJsonPath = path.resolve(__dirname, '../package.json');
|
|
1663
|
-
const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
|
|
1664
|
-
const versionInfo = packageJson.versionInfo || {};
|
|
1665
|
-
|
|
1666
|
-
console.log(`\n🌍 i18n Toolkit (i18ntk)`);
|
|
1667
|
-
console.log(`Version: ${packageJson.version}`);
|
|
1668
|
-
console.log(`Release Date: ${versionInfo.releaseDate || 'N/A'}`);
|
|
1669
|
-
console.log(`Maintainer: ${versionInfo.maintainer || packageJson.author}`);
|
|
1670
|
-
console.log(`Node.js: ${versionInfo.supportedNodeVersions || packageJson.engines?.node || '>=16.0.0'}`);
|
|
1671
|
-
console.log(`License: ${packageJson.license}`);
|
|
1672
|
-
|
|
1673
|
-
if (versionInfo.majorChanges && versionInfo.majorChanges.length > 0) {
|
|
1674
|
-
console.log(`\n✨ What's New in ${packageJson.version}:`);
|
|
1675
|
-
versionInfo.majorChanges.forEach(change => {
|
|
1676
|
-
console.log(` • ${change}`);
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
console.log(`\n📖 Documentation: ${packageJson.homepage}`);
|
|
1681
|
-
console.log(`🐛 Report Issues: ${packageJson.bugs?.url}`);
|
|
1682
|
-
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
console.log(`\n❌ Version information unavailable`);
|
|
1685
|
-
console.log(`Error: ${error.message}`);
|
|
1686
|
-
}
|
|
1687
|
-
process.exit(0);
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
const manager = new I18nManager();
|
|
1691
|
-
manager.run();
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
module.exports = I18nManager;
|