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.
Files changed (86) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/LICENSE +21 -0
  3. package/README.md +507 -0
  4. package/dev/README.md +37 -0
  5. package/dev/debug/README.md +30 -0
  6. package/dev/debug/complete-console-translations.js +295 -0
  7. package/dev/debug/console-key-checker.js +408 -0
  8. package/dev/debug/console-translations.js +335 -0
  9. package/dev/debug/debugger.js +408 -0
  10. package/dev/debug/export-missing-keys.js +432 -0
  11. package/dev/debug/final-normalize.js +236 -0
  12. package/dev/debug/find-extra-keys.js +68 -0
  13. package/dev/debug/normalize-locales.js +153 -0
  14. package/dev/debug/refactor-locales.js +240 -0
  15. package/dev/debug/reorder-locales.js +85 -0
  16. package/dev/debug/replace-hardcoded-console.js +378 -0
  17. package/docs/INSTALLATION.md +449 -0
  18. package/docs/README.md +222 -0
  19. package/docs/TODO_ROADMAP.md +279 -0
  20. package/docs/api/API_REFERENCE.md +377 -0
  21. package/docs/api/COMPONENTS.md +492 -0
  22. package/docs/api/CONFIGURATION.md +651 -0
  23. package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
  24. package/docs/debug/DEBUG_README.md +30 -0
  25. package/docs/debug/DEBUG_TOOLS.md +494 -0
  26. package/docs/development/AGENTS.md +351 -0
  27. package/docs/development/DEVELOPMENT_RULES.md +165 -0
  28. package/docs/development/DEV_README.md +37 -0
  29. package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
  30. package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
  31. package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
  32. package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
  33. package/docs/reports/ANALYSIS_README.md +17 -0
  34. package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
  35. package/docs/reports/SIZING_README.md +18 -0
  36. package/docs/reports/SUMMARY_README.md +18 -0
  37. package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
  38. package/docs/reports/USAGE_README.md +18 -0
  39. package/docs/reports/VALIDATION_README.md +18 -0
  40. package/locales/de/auth.json +3 -0
  41. package/locales/de/common.json +16 -0
  42. package/locales/de/pagination.json +6 -0
  43. package/locales/en/auth.json +3 -0
  44. package/locales/en/common.json +16 -0
  45. package/locales/en/pagination.json +6 -0
  46. package/locales/es/auth.json +3 -0
  47. package/locales/es/common.json +16 -0
  48. package/locales/es/pagination.json +6 -0
  49. package/locales/fr/auth.json +3 -0
  50. package/locales/fr/common.json +16 -0
  51. package/locales/fr/pagination.json +6 -0
  52. package/locales/ru/auth.json +3 -0
  53. package/locales/ru/common.json +16 -0
  54. package/locales/ru/pagination.json +6 -0
  55. package/main/i18ntk-analyze.js +625 -0
  56. package/main/i18ntk-autorun.js +461 -0
  57. package/main/i18ntk-complete.js +494 -0
  58. package/main/i18ntk-init.js +686 -0
  59. package/main/i18ntk-manage.js +848 -0
  60. package/main/i18ntk-sizing.js +557 -0
  61. package/main/i18ntk-summary.js +671 -0
  62. package/main/i18ntk-usage.js +1282 -0
  63. package/main/i18ntk-validate.js +762 -0
  64. package/main/ui-i18n.js +332 -0
  65. package/package.json +152 -0
  66. package/scripts/fix-missing-translation-keys.js +214 -0
  67. package/scripts/verify-package.js +168 -0
  68. package/ui-locales/de.json +637 -0
  69. package/ui-locales/en.json +688 -0
  70. package/ui-locales/es.json +637 -0
  71. package/ui-locales/fr.json +637 -0
  72. package/ui-locales/ja.json +637 -0
  73. package/ui-locales/ru.json +637 -0
  74. package/ui-locales/zh.json +637 -0
  75. package/utils/admin-auth.js +317 -0
  76. package/utils/admin-cli.js +353 -0
  77. package/utils/admin-pin.js +409 -0
  78. package/utils/detect-language-mismatches.js +454 -0
  79. package/utils/i18n-helper.js +128 -0
  80. package/utils/maintain-language-purity.js +433 -0
  81. package/utils/native-translations.js +478 -0
  82. package/utils/security.js +384 -0
  83. package/utils/test-complete-system.js +356 -0
  84. package/utils/test-console-i18n.js +402 -0
  85. package/utils/translate-mismatches.js +571 -0
  86. 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;