i18ntk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +401 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dev/README.md +37 -0
- package/dev/debug/README.md +30 -0
- package/dev/debug/complete-console-translations.js +295 -0
- package/dev/debug/console-key-checker.js +408 -0
- package/dev/debug/console-translations.js +335 -0
- package/dev/debug/debugger.js +408 -0
- package/dev/debug/export-missing-keys.js +432 -0
- package/dev/debug/final-normalize.js +236 -0
- package/dev/debug/find-extra-keys.js +68 -0
- package/dev/debug/normalize-locales.js +153 -0
- package/dev/debug/refactor-locales.js +240 -0
- package/dev/debug/reorder-locales.js +85 -0
- package/dev/debug/replace-hardcoded-console.js +378 -0
- package/docs/INSTALLATION.md +449 -0
- package/docs/README.md +222 -0
- package/docs/TODO_ROADMAP.md +279 -0
- package/docs/api/API_REFERENCE.md +377 -0
- package/docs/api/COMPONENTS.md +492 -0
- package/docs/api/CONFIGURATION.md +651 -0
- package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
- package/docs/debug/DEBUG_README.md +30 -0
- package/docs/debug/DEBUG_TOOLS.md +494 -0
- package/docs/development/AGENTS.md +351 -0
- package/docs/development/DEVELOPMENT_RULES.md +165 -0
- package/docs/development/DEV_README.md +37 -0
- package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
- package/docs/reports/ANALYSIS_README.md +17 -0
- package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
- package/docs/reports/SIZING_README.md +18 -0
- package/docs/reports/SUMMARY_README.md +18 -0
- package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
- package/docs/reports/USAGE_README.md +18 -0
- package/docs/reports/VALIDATION_README.md +18 -0
- package/locales/de/auth.json +3 -0
- package/locales/de/common.json +16 -0
- package/locales/de/pagination.json +6 -0
- package/locales/en/auth.json +3 -0
- package/locales/en/common.json +16 -0
- package/locales/en/pagination.json +6 -0
- package/locales/es/auth.json +3 -0
- package/locales/es/common.json +16 -0
- package/locales/es/pagination.json +6 -0
- package/locales/fr/auth.json +3 -0
- package/locales/fr/common.json +16 -0
- package/locales/fr/pagination.json +6 -0
- package/locales/ru/auth.json +3 -0
- package/locales/ru/common.json +16 -0
- package/locales/ru/pagination.json +6 -0
- package/main/i18ntk-analyze.js +625 -0
- package/main/i18ntk-autorun.js +461 -0
- package/main/i18ntk-complete.js +494 -0
- package/main/i18ntk-init.js +686 -0
- package/main/i18ntk-manage.js +848 -0
- package/main/i18ntk-sizing.js +557 -0
- package/main/i18ntk-summary.js +671 -0
- package/main/i18ntk-usage.js +1282 -0
- package/main/i18ntk-validate.js +762 -0
- package/main/ui-i18n.js +332 -0
- package/package.json +152 -0
- package/scripts/fix-missing-translation-keys.js +214 -0
- package/scripts/verify-package.js +168 -0
- package/ui-locales/de.json +637 -0
- package/ui-locales/en.json +688 -0
- package/ui-locales/es.json +637 -0
- package/ui-locales/fr.json +637 -0
- package/ui-locales/ja.json +637 -0
- package/ui-locales/ru.json +637 -0
- package/ui-locales/zh.json +637 -0
- package/utils/admin-auth.js +317 -0
- package/utils/admin-cli.js +353 -0
- package/utils/admin-pin.js +409 -0
- package/utils/detect-language-mismatches.js +454 -0
- package/utils/i18n-helper.js +128 -0
- package/utils/maintain-language-purity.js +433 -0
- package/utils/native-translations.js +478 -0
- package/utils/security.js +384 -0
- package/utils/test-complete-system.js +356 -0
- package/utils/test-console-i18n.js +402 -0
- package/utils/translate-mismatches.js +571 -0
- package/utils/validate-language-purity.js +531 -0
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* I18N 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 fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const readline = require('readline');
|
|
24
|
+
const UIi18n = require('./ui-i18n');
|
|
25
|
+
const AdminAuth = require('../utils/admin-auth');
|
|
26
|
+
const SecurityUtils = require('../utils/security');
|
|
27
|
+
const AdminCLI = require('../utils/admin-cli');
|
|
28
|
+
const settingsManager = require('../settings/settings-manager');
|
|
29
|
+
const I18nInitializer = require('./i18ntk-init');
|
|
30
|
+
const I18nAnalyzer = require('./i18ntk-analyze');
|
|
31
|
+
const I18nValidator = require('./i18ntk-validate');
|
|
32
|
+
const I18nUsageAnalyzer = require('./i18ntk-usage');
|
|
33
|
+
const I18nSizingAnalyzer = require('./i18ntk-sizing');
|
|
34
|
+
const SettingsCLI = require('../settings/settings-cli');
|
|
35
|
+
const I18nDebugger = require('../dev/debug/debugger');
|
|
36
|
+
|
|
37
|
+
// Enhanced default configuration with multiple path detection
|
|
38
|
+
const DEFAULT_CONFIG = {
|
|
39
|
+
sourceDir: './locales',
|
|
40
|
+
sourceLanguage: 'en',
|
|
41
|
+
defaultLanguages: ['de', 'es', 'fr', 'ru'],
|
|
42
|
+
outputDir: './i18ntk-reports',
|
|
43
|
+
// Multiple possible i18n locations to check
|
|
44
|
+
possibleI18nPaths: [
|
|
45
|
+
'./locales',
|
|
46
|
+
'./src/locales',
|
|
47
|
+
'./src/i18n',
|
|
48
|
+
'./src/i18n/locales',
|
|
49
|
+
'./app/locales',
|
|
50
|
+
'./app/i18n',
|
|
51
|
+
'./public/locales',
|
|
52
|
+
'./assets/locales',
|
|
53
|
+
'./translations',
|
|
54
|
+
'./lang'
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
class I18nManager {
|
|
59
|
+
constructor(config = {}) {
|
|
60
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
61
|
+
this.rl = null;
|
|
62
|
+
this.isReadlineClosed = false;
|
|
63
|
+
this.isAuthenticated = false;
|
|
64
|
+
this.settingsManager = settingsManager;
|
|
65
|
+
|
|
66
|
+
// Initialize UI localization system
|
|
67
|
+
this.ui = new UIi18n();
|
|
68
|
+
|
|
69
|
+
// Initialize admin authentication
|
|
70
|
+
this.adminAuth = new AdminAuth();
|
|
71
|
+
|
|
72
|
+
// Auto-detect i18n directory on initialization
|
|
73
|
+
this.detectI18nDirectory();
|
|
74
|
+
|
|
75
|
+
// Initialize readline interface
|
|
76
|
+
this.initializeReadline();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
initializeReadline() {
|
|
80
|
+
if (!this.rl || this.isReadlineClosed) {
|
|
81
|
+
this.rl = readline.createInterface({
|
|
82
|
+
input: process.stdin,
|
|
83
|
+
output: process.stdout,
|
|
84
|
+
terminal: true,
|
|
85
|
+
historySize: 0
|
|
86
|
+
});
|
|
87
|
+
this.isReadlineClosed = false;
|
|
88
|
+
|
|
89
|
+
// Handle readline close events
|
|
90
|
+
this.rl.on('close', () => {
|
|
91
|
+
this.isReadlineClosed = true;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Make readline interface globally available
|
|
95
|
+
global.activeReadlineInterface = this.rl;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Auto-detect i18n directory from common locations
|
|
100
|
+
detectI18nDirectory() {
|
|
101
|
+
for (const possiblePath of this.config.possibleI18nPaths) {
|
|
102
|
+
const resolvedPath = path.resolve(possiblePath);
|
|
103
|
+
if (fs.existsSync(resolvedPath)) {
|
|
104
|
+
// Check if it contains language directories
|
|
105
|
+
try {
|
|
106
|
+
const items = fs.readdirSync(resolvedPath);
|
|
107
|
+
const hasLanguageDirs = items.some(item => {
|
|
108
|
+
const itemPath = path.join(resolvedPath, item);
|
|
109
|
+
return fs.statSync(itemPath).isDirectory() &&
|
|
110
|
+
['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(item);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (hasLanguageDirs) {
|
|
114
|
+
this.config.sourceDir = possiblePath;
|
|
115
|
+
console.log(this.ui.t('hardcodedTexts.autoDetectedI18nDirectory', { path: possiblePath }));
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// Continue checking other paths
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check if i18n framework is installed
|
|
126
|
+
async checkI18nDependencies() {
|
|
127
|
+
const packageJsonPath = path.resolve('./package.json');
|
|
128
|
+
|
|
129
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
130
|
+
console.log(this.ui.t('init.warnings.noPackageJson'));
|
|
131
|
+
return await this.promptContinueWithoutI18n();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
136
|
+
// Include peerDependencies in the check
|
|
137
|
+
const dependencies = {
|
|
138
|
+
...packageJson.dependencies,
|
|
139
|
+
...packageJson.devDependencies,
|
|
140
|
+
...packageJson.peerDependencies
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const i18nFrameworks = [
|
|
144
|
+
'react-i18next',
|
|
145
|
+
'vue-i18n',
|
|
146
|
+
'angular-i18n',
|
|
147
|
+
'i18next',
|
|
148
|
+
'next-i18next',
|
|
149
|
+
'svelte-i18n',
|
|
150
|
+
'@nuxtjs/i18n'
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const installedFrameworks = i18nFrameworks.filter(framework => dependencies[framework]);
|
|
154
|
+
|
|
155
|
+
if (installedFrameworks.length > 0) {
|
|
156
|
+
console.log(`✅ Detected i18n framework(s): ${installedFrameworks.join(', ')}`);
|
|
157
|
+
return true;
|
|
158
|
+
} else {
|
|
159
|
+
console.log(this.ui.t('init.suggestions.noFramework'));
|
|
160
|
+
console.log(this.ui.t('init.frameworks.react'));
|
|
161
|
+
console.log(this.ui.t('init.frameworks.vue'));
|
|
162
|
+
console.log(this.ui.t('init.frameworks.i18next'));
|
|
163
|
+
console.log(this.ui.t('init.frameworks.nuxt'));
|
|
164
|
+
console.log(this.ui.t('init.frameworks.svelte'));
|
|
165
|
+
return await this.promptContinueWithoutI18n();
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log(this.ui.t('init.errors.packageJsonRead'));
|
|
169
|
+
return await this.promptContinueWithoutI18n();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Prompt user to continue without i18n framework
|
|
175
|
+
*/
|
|
176
|
+
async promptContinueWithoutI18n() {
|
|
177
|
+
const answer = await this.prompt('\n🤔 Continue without i18n framework? (y/N): ');
|
|
178
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add this run method after the checkI18nDependencies method
|
|
182
|
+
async run() {
|
|
183
|
+
try {
|
|
184
|
+
console.log(this.ui.t('menu.title'));
|
|
185
|
+
console.log('=' .repeat(40));
|
|
186
|
+
|
|
187
|
+
// Check dependencies and exit if user chooses not to continue
|
|
188
|
+
const shouldContinue = await this.checkI18nDependencies();
|
|
189
|
+
if (!shouldContinue) {
|
|
190
|
+
console.log(this.ui.t('init.errors.noFramework'));
|
|
191
|
+
console.log(this.ui.t('init.suggestions.installFramework'));
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Parse command line arguments
|
|
196
|
+
const args = process.argv.slice(2);
|
|
197
|
+
|
|
198
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
199
|
+
this.showHelp();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle direct commands
|
|
204
|
+
const commandArg = args.find(arg => arg.startsWith('--command='));
|
|
205
|
+
if (commandArg) {
|
|
206
|
+
const command = commandArg.split('=')[1];
|
|
207
|
+
await this.executeCommand(command);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Interactive mode
|
|
212
|
+
await this.showInteractiveMenu();
|
|
213
|
+
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('❌ Error:', error.message);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
} finally {
|
|
218
|
+
this.safeClose();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
showHelp() {
|
|
223
|
+
console.log(this.ui.t('help.usage'));
|
|
224
|
+
console.log(this.ui.t('help.interactiveMode'));
|
|
225
|
+
console.log(this.ui.t('help.initProject'));
|
|
226
|
+
console.log(this.ui.t('help.analyzeTranslations'));
|
|
227
|
+
console.log(this.ui.t('help.validateTranslations'));
|
|
228
|
+
console.log(this.ui.t('help.checkUsage'));
|
|
229
|
+
console.log(this.ui.t('help.showHelp'));
|
|
230
|
+
console.log(this.ui.t('help.availableCommands'));
|
|
231
|
+
console.log(this.ui.t('help.initCommand'));
|
|
232
|
+
console.log(this.ui.t('help.analyzeCommand'));
|
|
233
|
+
console.log(this.ui.t('help.validateCommand'));
|
|
234
|
+
console.log(this.ui.t('help.usageCommand'));
|
|
235
|
+
console.log(this.ui.t('help.sizingCommand'));
|
|
236
|
+
console.log(this.ui.t('help.completeCommand'));
|
|
237
|
+
console.log(this.ui.t('help.summaryCommand'));
|
|
238
|
+
console.log(this.ui.t('help.debugCommand'));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async executeCommand(command) {
|
|
242
|
+
console.log(this.ui.t('hardcodedTexts.executingCommand', { command }));
|
|
243
|
+
|
|
244
|
+
// Check admin authentication for sensitive commands
|
|
245
|
+
const sensitiveCommands = ['init', 'validate', 'complete', 'sizing', 'debug'];
|
|
246
|
+
if (sensitiveCommands.includes(command)) {
|
|
247
|
+
const authPassed = await this.checkAdminAuth();
|
|
248
|
+
if (!authPassed) {
|
|
249
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
250
|
+
await this.showInteractiveMenu();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
switch (command) {
|
|
257
|
+
case 'init':
|
|
258
|
+
const initializer = new I18nInitializer();
|
|
259
|
+
await initializer.run();
|
|
260
|
+
break;
|
|
261
|
+
case 'analyze':
|
|
262
|
+
const analyzer = new I18nAnalyzer();
|
|
263
|
+
await analyzer.run();
|
|
264
|
+
break;
|
|
265
|
+
case 'validate':
|
|
266
|
+
const validator = new I18nValidator();
|
|
267
|
+
await validator.run();
|
|
268
|
+
break;
|
|
269
|
+
case 'usage':
|
|
270
|
+
const usageAnalyzer = new I18nUsageAnalyzer();
|
|
271
|
+
await usageAnalyzer.run();
|
|
272
|
+
break;
|
|
273
|
+
case 'sizing':
|
|
274
|
+
const sizingAnalyzer = new I18nSizingAnalyzer();
|
|
275
|
+
await sizingAnalyzer.run();
|
|
276
|
+
break;
|
|
277
|
+
case 'complete':
|
|
278
|
+
const completeTool = require('./i18ntk-complete');
|
|
279
|
+
const tool = new completeTool();
|
|
280
|
+
await tool.run();
|
|
281
|
+
break;
|
|
282
|
+
case 'workflow':
|
|
283
|
+
console.log(this.ui.t('workflow.starting'));
|
|
284
|
+
const AutoRunner = require('./i18ntk-autorun');
|
|
285
|
+
const runner = new AutoRunner();
|
|
286
|
+
await runner.runAll(true); // Pass true for quiet mode
|
|
287
|
+
|
|
288
|
+
// Show workflow completion message and return to menu
|
|
289
|
+
console.log(this.ui.t('workflow.completed'));
|
|
290
|
+
console.log(this.ui.t('workflow.checkReports'));
|
|
291
|
+
|
|
292
|
+
// Check if stdin is available before prompting
|
|
293
|
+
if (process.stdin.isTTY && !process.stdin.destroyed) {
|
|
294
|
+
try {
|
|
295
|
+
await this.prompt('\n📝 Press Enter to return to main menu...');
|
|
296
|
+
await this.showInteractiveMenu();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
// If readline fails, just exit gracefully
|
|
299
|
+
console.log(this.ui.t('menu.returning'));
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
// If no TTY or stdin is closed, exit gracefully
|
|
304
|
+
console.log(this.ui.t('workflow.exitingCompleted'));
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
case 'debug':
|
|
309
|
+
const debuggerTool = new I18nDebugger();
|
|
310
|
+
await debuggerTool.run();
|
|
311
|
+
break;
|
|
312
|
+
default:
|
|
313
|
+
console.log(this.ui.t('hardcodedTexts.unknownCommand', { command }));
|
|
314
|
+
this.showHelp();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Add session continuity
|
|
319
|
+
console.log(this.ui.t('operations.completed'));
|
|
320
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
321
|
+
await this.showInteractiveMenu();
|
|
322
|
+
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(this.ui.t('hardcodedTexts.errorExecutingCommand', { error: error.message }));
|
|
325
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
326
|
+
await this.showInteractiveMenu();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Add admin authentication check
|
|
331
|
+
async checkAdminAuth() {
|
|
332
|
+
const isRequired = await this.adminAuth.isAuthRequired();
|
|
333
|
+
if (!isRequired) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
console.log(this.ui.t('admin.authRequired'));
|
|
338
|
+
const pin = await this.prompt('Enter admin PIN: ');
|
|
339
|
+
const isValid = await this.adminAuth.verifyPin(pin);
|
|
340
|
+
|
|
341
|
+
if (!isValid) {
|
|
342
|
+
console.log(this.ui.t('admin.invalidPin'));
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log(this.ui.t('admin.authSuccess'));
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async showInteractiveMenu() {
|
|
351
|
+
console.log(`\n${this.ui.t('menu.title')}`);
|
|
352
|
+
console.log(this.ui.t('menu.separator'));
|
|
353
|
+
console.log(`1. ${this.ui.t('menu.options.init')}`);
|
|
354
|
+
console.log(`2. ${this.ui.t('menu.options.analyze')}`);
|
|
355
|
+
console.log(`3. ${this.ui.t('menu.options.validate')}`);
|
|
356
|
+
console.log(`4. ${this.ui.t('menu.options.usage')}`);
|
|
357
|
+
console.log(`5. ${this.ui.t('menu.options.complete')}`);
|
|
358
|
+
console.log(`6. ${this.ui.t('menu.options.sizing')}`);
|
|
359
|
+
console.log(`7. ${this.ui.t('menu.options.workflow')}`);
|
|
360
|
+
console.log(`8. ${this.ui.t('menu.options.status')}`);
|
|
361
|
+
console.log(`9. ${this.ui.t('menu.options.delete')}`);
|
|
362
|
+
console.log(`10. ${this.ui.t('menu.options.language')}`);
|
|
363
|
+
console.log(`11. ${this.ui.t('menu.options.settings')}`);
|
|
364
|
+
console.log(`12. ${this.ui.t('menu.options.help')}`);
|
|
365
|
+
console.log(`13. ${this.ui.t('menu.options.debug')}`);
|
|
366
|
+
console.log(`0. ${this.ui.t('menu.options.exit')}`);
|
|
367
|
+
|
|
368
|
+
const choice = await this.prompt('\n' + this.ui.t('hardcodedTexts.selectOptionPrompt'));
|
|
369
|
+
|
|
370
|
+
switch (choice.trim()) {
|
|
371
|
+
case '1':
|
|
372
|
+
await this.executeCommand('init');
|
|
373
|
+
break;
|
|
374
|
+
case '2':
|
|
375
|
+
await this.executeCommand('analyze');
|
|
376
|
+
break;
|
|
377
|
+
case '3':
|
|
378
|
+
await this.executeCommand('validate');
|
|
379
|
+
break;
|
|
380
|
+
case '4':
|
|
381
|
+
await this.executeCommand('usage');
|
|
382
|
+
break;
|
|
383
|
+
case '5':
|
|
384
|
+
await this.executeCommand('complete', {fromMenu: true});
|
|
385
|
+
break;
|
|
386
|
+
case '6':
|
|
387
|
+
await this.executeCommand('sizing');
|
|
388
|
+
break;
|
|
389
|
+
case '7':
|
|
390
|
+
await this.executeCommand('workflow');
|
|
391
|
+
break;
|
|
392
|
+
case '8':
|
|
393
|
+
console.log(this.ui.t('status.generating'));
|
|
394
|
+
try {
|
|
395
|
+
const summaryTool = require('./i18ntk-summary');
|
|
396
|
+
const summary = new summaryTool();
|
|
397
|
+
await summary.run();
|
|
398
|
+
console.log(this.ui.t('status.completed'));
|
|
399
|
+
|
|
400
|
+
// Check if stdin is available before prompting
|
|
401
|
+
if (process.stdin.isTTY && !process.stdin.destroyed) {
|
|
402
|
+
try {
|
|
403
|
+
await this.prompt('\n' + this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
404
|
+
await this.showInteractiveMenu();
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.log(this.ui.t('menu.returning'));
|
|
407
|
+
process.exit(0);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
console.log(this.ui.t('status.exitingCompleted'));
|
|
411
|
+
process.exit(0);
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error(this.ui.t('hardcodedTexts.errorGeneratingStatusSummary', { error: error.message }));
|
|
415
|
+
|
|
416
|
+
// Check if stdin is available before prompting
|
|
417
|
+
if (process.stdin.isTTY && !process.stdin.destroyed) {
|
|
418
|
+
try {
|
|
419
|
+
await this.prompt('\n' + this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
420
|
+
await this.showInteractiveMenu();
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.log(this.ui.t('menu.returning'));
|
|
423
|
+
process.exit(0);
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
console.log(this.ui.t('common.errorExiting'));
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
case '9':
|
|
432
|
+
await this.deleteReports();
|
|
433
|
+
break;
|
|
434
|
+
case '10':
|
|
435
|
+
await this.changeLanguage();
|
|
436
|
+
break;
|
|
437
|
+
case '11':
|
|
438
|
+
await this.showSettingsMenu();
|
|
439
|
+
break;
|
|
440
|
+
case '12':
|
|
441
|
+
this.showHelp();
|
|
442
|
+
await this.showInteractiveMenu();
|
|
443
|
+
break;
|
|
444
|
+
case '13':
|
|
445
|
+
await this.showDebugMenu();
|
|
446
|
+
break;
|
|
447
|
+
case '0':
|
|
448
|
+
console.log(this.ui.t('menu.goodbye'));
|
|
449
|
+
this.safeClose();
|
|
450
|
+
process.exit(0);
|
|
451
|
+
default:
|
|
452
|
+
console.log(this.ui.t('menu.invalidChoice'));
|
|
453
|
+
await this.showInteractiveMenu();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Debug Tools Menu
|
|
458
|
+
async showDebugMenu() {
|
|
459
|
+
console.log(`\n${this.ui.t('menu.options.debug')}`);
|
|
460
|
+
console.log(this.ui.t('menu.separator'));
|
|
461
|
+
console.log(`1. ${this.ui.t('hardcodedTexts.mainDebuggerSystemDiagnostics')}`);
|
|
462
|
+
console.log(`2. ${this.ui.t('hardcodedTexts.consoleTranslationsCheck')}`);
|
|
463
|
+
console.log(`3. ${this.ui.t('hardcodedTexts.exportMissingKeys')}`);
|
|
464
|
+
console.log(`4. ${this.ui.t('hardcodedTexts.replaceHardcodedConsole')}`);
|
|
465
|
+
console.log(`5. ${this.ui.t('hardcodedTexts.consoleKeyChecker')}`);
|
|
466
|
+
console.log(`6. ${this.ui.t('hardcodedTexts.debugLogs')}`);
|
|
467
|
+
console.log(`0. ${this.ui.t('hardcodedTexts.backToMainMenu')}`);
|
|
468
|
+
|
|
469
|
+
const choice = await this.prompt('\n' + this.ui.t('hardcodedTexts.selectDebugToolPrompt'));
|
|
470
|
+
|
|
471
|
+
switch (choice.trim()) {
|
|
472
|
+
case '1':
|
|
473
|
+
await this.runDebugTool('debugger.js', 'Main Debugger');
|
|
474
|
+
break;
|
|
475
|
+
case '2':
|
|
476
|
+
await this.runDebugTool('console-translations.js', 'Console Translations');
|
|
477
|
+
break;
|
|
478
|
+
case '3':
|
|
479
|
+
await this.runDebugTool('export-missing-keys.js', 'Export Missing Keys');
|
|
480
|
+
break;
|
|
481
|
+
case '4':
|
|
482
|
+
await this.runDebugTool('replace-hardcoded-console.js', 'Replace Hardcoded Console');
|
|
483
|
+
break;
|
|
484
|
+
case '5':
|
|
485
|
+
await this.runDebugTool('console-key-checker.js', 'Console Key Checker');
|
|
486
|
+
break;
|
|
487
|
+
case '6':
|
|
488
|
+
await this.viewDebugLogs();
|
|
489
|
+
break;
|
|
490
|
+
case '0':
|
|
491
|
+
await this.showInteractiveMenu();
|
|
492
|
+
return;
|
|
493
|
+
default:
|
|
494
|
+
console.log(this.ui.t('hardcodedTexts.invalidChoiceSelectRange'));
|
|
495
|
+
await this.showDebugMenu();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Run a debug tool
|
|
500
|
+
async runDebugTool(toolName, displayName) {
|
|
501
|
+
console.log(this.ui.t('hardcodedTexts.runningDebugTool', { displayName }));
|
|
502
|
+
try {
|
|
503
|
+
const toolPath = path.join(__dirname, '..', 'dev', 'debug', toolName);
|
|
504
|
+
if (fs.existsSync(toolPath)) {
|
|
505
|
+
const { execSync } = require('child_process');
|
|
506
|
+
const output = execSync(`node "${toolPath}"`, {
|
|
507
|
+
encoding: 'utf8',
|
|
508
|
+
cwd: path.join(__dirname, '..'),
|
|
509
|
+
timeout: 30000
|
|
510
|
+
});
|
|
511
|
+
console.log(output);
|
|
512
|
+
} else {
|
|
513
|
+
console.log(this.ui.t('hardcodedTexts.debugToolNotFound', { toolName }));
|
|
514
|
+
}
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error(this.ui.t('hardcodedTexts.errorRunningDebugTool', { displayName, error: error.message }));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
await this.prompt('\n' + this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
520
|
+
await this.showDebugMenu();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// View debug logs
|
|
524
|
+
async viewDebugLogs() {
|
|
525
|
+
console.log(`\n${this.ui.t('hardcodedTexts.recentDebugLogs')}`);
|
|
526
|
+
console.log('============================================================');
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
const logsDir = path.join(__dirname, '..', 'dev', 'debug', 'logs');
|
|
530
|
+
if (fs.existsSync(logsDir)) {
|
|
531
|
+
const files = fs.readdirSync(logsDir)
|
|
532
|
+
.filter(file => file.endsWith('.log') || file.endsWith('.txt'))
|
|
533
|
+
.sort((a, b) => {
|
|
534
|
+
const statA = fs.statSync(path.join(logsDir, a));
|
|
535
|
+
const statB = fs.statSync(path.join(logsDir, b));
|
|
536
|
+
return statB.mtime - statA.mtime;
|
|
537
|
+
})
|
|
538
|
+
.slice(0, 5);
|
|
539
|
+
|
|
540
|
+
if (files.length > 0) {
|
|
541
|
+
files.forEach((file, index) => {
|
|
542
|
+
const filePath = path.join(logsDir, file);
|
|
543
|
+
const stats = fs.statSync(filePath);
|
|
544
|
+
console.log(`${index + 1}. ${file} (${stats.mtime.toLocaleString()})`);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const choice = await this.prompt('\n' + this.ui.t('hardcodedTexts.selectLogPrompt', { count: files.length }));
|
|
548
|
+
const fileIndex = parseInt(choice) - 1;
|
|
549
|
+
|
|
550
|
+
if (fileIndex >= 0 && fileIndex < files.length) {
|
|
551
|
+
const logContent = fs.readFileSync(path.join(logsDir, files[fileIndex]), 'utf8');
|
|
552
|
+
console.log(`\n📄 Content of ${files[fileIndex]}:`);
|
|
553
|
+
console.log('============================================================');
|
|
554
|
+
console.log(logContent.slice(-2000)); // Show last 2000 characters
|
|
555
|
+
console.log('============================================================');
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
console.log(this.ui.t('hardcodedTexts.noDebugLogsFound'));
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
console.log(this.ui.t('hardcodedTexts.debugLogsDirectoryNotFound'));
|
|
562
|
+
}
|
|
563
|
+
} catch (error) {
|
|
564
|
+
console.error(this.ui.t('hardcodedTexts.errorReadingDebugLogs', { error: error.message }));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
await this.prompt('\n' + this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
568
|
+
await this.showInteractiveMenu();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Enhanced delete reports and logs functionality
|
|
572
|
+
async deleteReports() {
|
|
573
|
+
console.log(`\n🗑️ Delete Reports & Logs`);
|
|
574
|
+
console.log('============================================================');
|
|
575
|
+
|
|
576
|
+
const targetDirs = [
|
|
577
|
+
{ path: path.join(process.cwd(), 'i18ntk-reports'), name: 'Reports', type: 'reports' },
|
|
578
|
+
{ path: path.join(process.cwd(), 'reports'), name: 'Legacy Reports', type: 'reports' },
|
|
579
|
+
{ path: path.join(process.cwd(), 'dev', 'debug', 'logs'), name: 'Debug Logs', type: 'logs' },
|
|
580
|
+
{ path: path.join(process.cwd(), 'dev', 'debug', 'reports'), name: 'Debug Reports', type: 'reports' },
|
|
581
|
+
{ path: path.join(process.cwd(), 'settings', 'backups'), name: 'Backups', type: 'backups' }
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
console.log('🔍 Scanning for files to delete...');
|
|
586
|
+
|
|
587
|
+
let availableDirs = [];
|
|
588
|
+
|
|
589
|
+
// Check which directories exist and have files
|
|
590
|
+
for (const dir of targetDirs) {
|
|
591
|
+
if (fs.existsSync(dir.path)) {
|
|
592
|
+
const files = this.getAllReportFiles(dir.path);
|
|
593
|
+
if (files.length > 0) {
|
|
594
|
+
availableDirs.push({
|
|
595
|
+
...dir,
|
|
596
|
+
files: files.map(file => ({ path: file, dir: dir.path })),
|
|
597
|
+
count: files.length
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (availableDirs.length === 0) {
|
|
604
|
+
console.log('✅ No files found to delete.');
|
|
605
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
606
|
+
await this.showInteractiveMenu();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Show available directories
|
|
611
|
+
console.log('\n📁 Available directories:');
|
|
612
|
+
availableDirs.forEach((dir, index) => {
|
|
613
|
+
console.log(` ${index + 1}. ${dir.name} (${dir.count} files)`);
|
|
614
|
+
});
|
|
615
|
+
console.log(` ${availableDirs.length + 1}. All directories`);
|
|
616
|
+
console.log(' 0. Cancel');
|
|
617
|
+
|
|
618
|
+
const dirChoice = await this.prompt(`\nSelect directory to clean (0-${availableDirs.length + 1}): `);
|
|
619
|
+
const dirIndex = parseInt(dirChoice) - 1;
|
|
620
|
+
|
|
621
|
+
let selectedDirs = [];
|
|
622
|
+
|
|
623
|
+
if (dirChoice.trim() === '0') {
|
|
624
|
+
console.log('❌ Operation cancelled.');
|
|
625
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
626
|
+
await this.showInteractiveMenu();
|
|
627
|
+
return;
|
|
628
|
+
} else if (dirIndex === availableDirs.length) {
|
|
629
|
+
selectedDirs = availableDirs;
|
|
630
|
+
} else if (dirIndex >= 0 && dirIndex < availableDirs.length) {
|
|
631
|
+
selectedDirs = [availableDirs[dirIndex]];
|
|
632
|
+
} else {
|
|
633
|
+
console.log('❌ Invalid selection.');
|
|
634
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
635
|
+
await this.showInteractiveMenu();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Collect all files from selected directories
|
|
640
|
+
let allFiles = [];
|
|
641
|
+
selectedDirs.forEach(dir => {
|
|
642
|
+
allFiles.push(...dir.files);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
console.log(`\n📊 Found ${allFiles.length} files in selected directories:`);
|
|
646
|
+
selectedDirs.forEach(dir => {
|
|
647
|
+
console.log(` 📁 ${dir.name}: ${dir.count} files`);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Show deletion options
|
|
651
|
+
console.log('\n🗑️ Deletion Options:');
|
|
652
|
+
console.log(' 1. Delete all files');
|
|
653
|
+
console.log(' 2. Keep last 3 files (by date)');
|
|
654
|
+
console.log(' 3. Keep last 5 files (by date)');
|
|
655
|
+
console.log(' 0. Cancel');
|
|
656
|
+
|
|
657
|
+
const option = await this.prompt('\nSelect option (0-3): ');
|
|
658
|
+
|
|
659
|
+
let filesToDelete = [];
|
|
660
|
+
|
|
661
|
+
switch (option.trim()) {
|
|
662
|
+
case '1':
|
|
663
|
+
filesToDelete = allFiles;
|
|
664
|
+
break;
|
|
665
|
+
case '2':
|
|
666
|
+
filesToDelete = this.getFilesToDeleteKeepLast(allFiles, 3);
|
|
667
|
+
break;
|
|
668
|
+
case '3':
|
|
669
|
+
filesToDelete = this.getFilesToDeleteKeepLast(allFiles, 5);
|
|
670
|
+
break;
|
|
671
|
+
case '0':
|
|
672
|
+
console.log('❌ Operation cancelled.');
|
|
673
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
674
|
+
await this.showInteractiveMenu();
|
|
675
|
+
return;
|
|
676
|
+
default:
|
|
677
|
+
console.log('❌ Invalid option.');
|
|
678
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
679
|
+
await this.showInteractiveMenu();
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (filesToDelete.length === 0) {
|
|
684
|
+
console.log('✅ No files to delete.');
|
|
685
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
686
|
+
await this.showInteractiveMenu();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
console.log(`\n📋 Files to delete: ${filesToDelete.length}`);
|
|
691
|
+
console.log(`📋 Files to keep: ${allFiles.length - filesToDelete.length}`);
|
|
692
|
+
|
|
693
|
+
const confirm = await this.prompt('\n⚠️ Are you sure you want to delete these files? (y/N): ');
|
|
694
|
+
|
|
695
|
+
if (confirm.toLowerCase() === 'y' || confirm.toLowerCase() === 'yes') {
|
|
696
|
+
let deletedCount = 0;
|
|
697
|
+
|
|
698
|
+
for (const fileInfo of filesToDelete) {
|
|
699
|
+
try {
|
|
700
|
+
fs.unlinkSync(fileInfo.path);
|
|
701
|
+
console.log(`✅ Deleted: ${path.basename(fileInfo.path)}`);
|
|
702
|
+
deletedCount++;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
console.log(`❌ Failed to delete ${path.basename(fileInfo.path)}: ${error.message}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
console.log(`\n🎉 Successfully deleted ${deletedCount} files!`);
|
|
709
|
+
} else {
|
|
710
|
+
console.log('❌ Operation cancelled.');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error(`❌ Error during deletion process: ${error.message}`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
718
|
+
await this.showInteractiveMenu();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Helper method to get all report and log files recursively
|
|
722
|
+
getAllReportFiles(dir) {
|
|
723
|
+
let files = [];
|
|
724
|
+
|
|
725
|
+
const items = fs.readdirSync(dir);
|
|
726
|
+
for (const item of items) {
|
|
727
|
+
const fullPath = path.join(dir, item);
|
|
728
|
+
const stat = fs.statSync(fullPath);
|
|
729
|
+
|
|
730
|
+
if (stat.isDirectory()) {
|
|
731
|
+
files.push(...this.getAllReportFiles(fullPath));
|
|
732
|
+
} else if (item.endsWith('.json') || item.endsWith('.html') || item.endsWith('.txt') || item.endsWith('.log')) {
|
|
733
|
+
files.push(fullPath);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return files;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Helper method to determine which files to delete when keeping last N files
|
|
741
|
+
getFilesToDeleteKeepLast(allFiles, keepCount = 3) {
|
|
742
|
+
// Sort files by modification time (newest first)
|
|
743
|
+
const sortedFiles = allFiles.sort((a, b) => {
|
|
744
|
+
const statA = fs.statSync(a.path);
|
|
745
|
+
const statB = fs.statSync(b.path);
|
|
746
|
+
return statB.mtime.getTime() - statA.mtime.getTime();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// Keep the N newest files, delete the rest
|
|
750
|
+
return sortedFiles.slice(keepCount);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Settings Menu
|
|
754
|
+
async showSettingsMenu() {
|
|
755
|
+
try {
|
|
756
|
+
const SettingsCLI = require('../settings/settings-cli');
|
|
757
|
+
const settingsCLI = new SettingsCLI();
|
|
758
|
+
await settingsCLI.run();
|
|
759
|
+
} catch (error) {
|
|
760
|
+
console.error('❌ Error opening settings:', error.message);
|
|
761
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
762
|
+
}
|
|
763
|
+
await this.showInteractiveMenu();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Add language change functionality
|
|
767
|
+
async changeLanguage() {
|
|
768
|
+
console.log(this.ui.t('language.changeTitle'));
|
|
769
|
+
console.log('=' .repeat(50));
|
|
770
|
+
|
|
771
|
+
const languages = {
|
|
772
|
+
'en': '🇺🇸 English',
|
|
773
|
+
'de': '🇩🇪 Deutsch',
|
|
774
|
+
'es': '🇪🇸 Español',
|
|
775
|
+
'fr': '🇫🇷 Français',
|
|
776
|
+
'ru': '🇷🇺 Русский',
|
|
777
|
+
'ja': '🇯🇵 日本語',
|
|
778
|
+
'zh': '🇨🇳 中文'
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
console.log(this.ui.t('language.available'));
|
|
782
|
+
Object.entries(languages).forEach(([code, name], index) => {
|
|
783
|
+
console.log(` ${index + 1}. ${code} - ${name}`);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const choice = await this.prompt('\nSelect language (1-7): ');
|
|
787
|
+
const languageCodes = Object.keys(languages);
|
|
788
|
+
const selectedIndex = parseInt(choice) - 1;
|
|
789
|
+
|
|
790
|
+
if (selectedIndex >= 0 && selectedIndex < languageCodes.length) {
|
|
791
|
+
const selectedLang = languageCodes[selectedIndex];
|
|
792
|
+
this.ui.loadLanguage(selectedLang);
|
|
793
|
+
|
|
794
|
+
// Save to settings
|
|
795
|
+
const settingsManager = require('../settings/settings-manager');
|
|
796
|
+
settingsManager.setLanguage(selectedLang);
|
|
797
|
+
|
|
798
|
+
console.log(`✅ Language changed to ${languages[selectedLang]}`);
|
|
799
|
+
} else {
|
|
800
|
+
console.log(this.ui.t('language.invalidSelection'));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
804
|
+
await this.showInteractiveMenu();
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
prompt(question) {
|
|
808
|
+
return new Promise((resolve) => {
|
|
809
|
+
// Check if readline is available and not closed
|
|
810
|
+
if (!this.rl || this.isReadlineClosed) {
|
|
811
|
+
this.initializeReadline();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Double check if stdin is available
|
|
815
|
+
if (!process.stdin.isTTY || process.stdin.destroyed) {
|
|
816
|
+
console.log('\n⚠️ Interactive input not available, using default response.');
|
|
817
|
+
resolve('');
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
try {
|
|
822
|
+
this.rl.question(question, resolve);
|
|
823
|
+
} catch (error) {
|
|
824
|
+
console.log('\n⚠️ Readline error, using default response.');
|
|
825
|
+
resolve('');
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
safeClose() {
|
|
831
|
+
if (this.rl && !this.isReadlineClosed) {
|
|
832
|
+
try {
|
|
833
|
+
this.rl.close();
|
|
834
|
+
this.isReadlineClosed = true;
|
|
835
|
+
} catch (error) {
|
|
836
|
+
// Ignore close errors
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Run if called directly
|
|
843
|
+
if (require.main === module) {
|
|
844
|
+
const manager = new I18nManager();
|
|
845
|
+
manager.run();
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
module.exports = I18nManager;
|