i18ntk 1.0.0 → 1.0.2

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.
@@ -0,0 +1,1021 @@
1
+ /**
2
+ * Settings CLI Interface
3
+ * Interactive terminal-based settings management for i18n toolkit
4
+ * No external dependencies - uses Node.js built-in readline
5
+ */
6
+
7
+ const readline = require('readline');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const settingsManager = require('./settings-manager');
11
+ const UIi18n = require('../main/ui-i18n');
12
+ const AdminPinManager = require('../utils/admin-pin');
13
+ const uiI18n = new UIi18n();
14
+
15
+ // ANSI color codes for terminal output
16
+ const colors = {
17
+ reset: '\x1b[0m',
18
+ bright: '\x1b[1m',
19
+ dim: '\x1b[2m',
20
+ red: '\x1b[31m',
21
+ green: '\x1b[32m',
22
+ yellow: '\x1b[33m',
23
+ blue: '\x1b[34m',
24
+ magenta: '\x1b[35m',
25
+ cyan: '\x1b[36m',
26
+ white: '\x1b[37m',
27
+ bgRed: '\x1b[41m',
28
+ bgGreen: '\x1b[42m',
29
+ bgYellow: '\x1b[43m'
30
+ };
31
+
32
+ class SettingsCLI {
33
+ constructor() {
34
+ // Check if there's already an active readline interface
35
+ if (global.activeReadlineInterface) {
36
+ this.rl = global.activeReadlineInterface;
37
+ this.shouldCloseRL = false;
38
+ } else {
39
+ this.rl = readline.createInterface({
40
+ input: process.stdin,
41
+ output: process.stdout,
42
+ terminal: true,
43
+ historySize: 0
44
+ });
45
+ this.shouldCloseRL = true;
46
+ global.activeReadlineInterface = this.rl;
47
+ }
48
+ this.settings = null;
49
+ this.schema = null;
50
+ this.modified = false;
51
+ this.adminPin = new AdminPinManager();
52
+ this.adminAuthenticated = false;
53
+ }
54
+
55
+ /**
56
+ * Initialize the CLI interface
57
+ */
58
+ async init() {
59
+ try {
60
+ this.settings = settingsManager.getSettings();
61
+ this.schema = settingsManager.getSettingsSchema();
62
+ return true;
63
+ } catch (error) {
64
+ this.error(`Failed to initialize settings: ${error.message}`);
65
+ return false;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Start the interactive settings interface
71
+ */
72
+ async start() {
73
+ if (!(await this.init())) {
74
+ process.exit(1);
75
+ }
76
+
77
+ this.clearScreen();
78
+ this.showHeader();
79
+ await this.showMainMenu();
80
+ }
81
+
82
+ /**
83
+ * Run the settings interface (alias for start)
84
+ */
85
+ async run() {
86
+ await this.start();
87
+ }
88
+
89
+ /**
90
+ * Clear the terminal screen
91
+ */
92
+ clearScreen() {
93
+ process.stdout.write('\x1b[2J\x1b[0f');
94
+ }
95
+
96
+ /**
97
+ * Show the main header
98
+ */
99
+ showHeader() {
100
+ const title = uiI18n.t('operations.settings.title') || 'Settings Management';
101
+ const separator = uiI18n.t('operations.settings.separator') || '============================================================';
102
+
103
+ console.log(`${colors.cyan}${colors.bright}`);
104
+ console.log(`${title}`);
105
+ console.log(separator);
106
+ console.log(colors.reset);
107
+
108
+ if (this.modified) {
109
+ console.log(`${colors.yellow}⚠️ You have unsaved changes${colors.reset}\n`);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Show the main menu
115
+ */
116
+ async showMainMenu() {
117
+ const options = [
118
+ { key: '1', label: 'UI Settings', description: 'Language, theme, and interface options' },
119
+ { key: '2', label: 'Directory Settings', description: 'Source and output directory paths' },
120
+ { key: '3', label: 'Processing Settings', description: 'Batch size, concurrency, and performance' },
121
+ { key: '4', label: 'Advanced Settings', description: 'Validation, logging, and expert options' },
122
+ { key: '5', label: 'View All Settings', description: 'Display current configuration' },
123
+ { key: '6', label: 'Import/Export', description: 'Backup and restore settings' },
124
+ { key: '7', label: 'Reset to Defaults', description: 'Restore factory settings' },
125
+ { key: '8', label: 'Report Bug', description: 'Submit an issue report on GitHub' },
126
+ { key: 's', label: 'Save Changes', description: 'Save current settings to file' },
127
+ { key: 'h', label: 'Help', description: 'Show detailed help information' },
128
+ { key: 'q', label: 'Quit', description: 'Exit settings (with save prompt if needed)' }
129
+ ];
130
+
131
+ console.log(`${colors.bright}Main Menu:${colors.reset}\n`);
132
+
133
+ options.forEach(option => {
134
+ const keyColor = option.key.match(/[0-9]/) ? colors.cyan : colors.yellow;
135
+ console.log(` ${keyColor}${option.key}${colors.reset}) ${colors.bright}${option.label}${colors.reset}`);
136
+ console.log(` ${colors.dim}${option.description}${colors.reset}`);
137
+ });
138
+
139
+ console.log();
140
+ const choice = await this.prompt('Select an option: ');
141
+ await this.handleMainMenuChoice(choice.toLowerCase());
142
+ }
143
+
144
+ /**
145
+ * Handle main menu choice
146
+ */
147
+ async handleMainMenuChoice(choice) {
148
+ switch (choice) {
149
+ case '1':
150
+ await this.showUISettings();
151
+ break;
152
+ case '2':
153
+ await this.showDirectorySettings();
154
+ break;
155
+ case '3':
156
+ await this.showProcessingSettings();
157
+ break;
158
+ case '4':
159
+ await this.showAdvancedSettings();
160
+ break;
161
+ case '5':
162
+ await this.showAllSettings();
163
+ break;
164
+ case '6':
165
+ await this.showImportExport();
166
+ break;
167
+ case '7':
168
+ await this.resetToDefaults();
169
+ break;
170
+ case '8':
171
+ await this.reportBug();
172
+ break;
173
+ case 's':
174
+ await this.saveSettings();
175
+ break;
176
+ case 'h':
177
+ await this.showHelp();
178
+ break;
179
+ case 'q':
180
+ await this.quit();
181
+ return;
182
+ default:
183
+ this.error('Invalid option. Please try again.');
184
+ await this.pause();
185
+ break;
186
+ }
187
+
188
+ this.clearScreen();
189
+ this.showHeader();
190
+ await this.showMainMenu();
191
+ }
192
+
193
+ /**
194
+ * Show UI settings menu
195
+ */
196
+ async showUISettings() {
197
+ this.clearScreen();
198
+ this.showHeader();
199
+ console.log(`${colors.bright}UI Settings${colors.reset}\n`);
200
+
201
+ const uiSettings = {
202
+ 'language': 'Interface Language', // Fix: use root language instead of ui.language
203
+ 'theme': 'Color Theme',
204
+ 'dateFormat': 'Date Format',
205
+ 'notifications.enabled': 'Show Notifications'
206
+ };
207
+
208
+ await this.showSettingsCategory(uiSettings);
209
+ }
210
+
211
+ /**
212
+ * Show directory settings menu
213
+ */
214
+ async showDirectorySettings() {
215
+ this.clearScreen();
216
+ this.showHeader();
217
+ console.log(`${colors.bright}Directory Settings${colors.reset}\n`);
218
+
219
+ const dirSettings = {
220
+ 'directories.locales': 'Locales Directory',
221
+ 'directories.reports': 'Reports Directory',
222
+ 'directories.backup': 'Backup Directory'
223
+ };
224
+
225
+ await this.showSettingsCategory(dirSettings);
226
+ }
227
+
228
+ /**
229
+ * Show processing settings menu
230
+ */
231
+ async showProcessingSettings() {
232
+ this.clearScreen();
233
+ this.showHeader();
234
+ console.log(`${colors.bright}Processing Settings${colors.reset}\n`);
235
+
236
+ const processSettings = {
237
+ 'processing.batchSize': 'Batch Size',
238
+ 'processing.concurrentFiles': 'Concurrent Files',
239
+ 'processing.sizingThreshold': 'Sizing Threshold (KB)',
240
+ 'processing.autoSave': 'Auto-save Interval (minutes)'
241
+ };
242
+
243
+ await this.showSettingsCategory(processSettings);
244
+ }
245
+
246
+ /**
247
+ * Show advanced settings menu
248
+ */
249
+ async showAdvancedSettings() {
250
+ this.clearScreen();
251
+ this.showHeader();
252
+ console.log(`${colors.bright}Advanced Settings${colors.reset}\n`);
253
+
254
+ const advancedSettings = {
255
+ 'advanced.strictMode': 'Strict Validation Mode',
256
+ 'debug.enabled': 'Debug Mode',
257
+ 'debug.verboseLogging': 'Verbose Logging',
258
+ 'security.adminPinEnabled': 'Admin PIN Protection',
259
+ 'security.sessionTimeout': 'Session Timeout (minutes)',
260
+ 'advanced.backupBeforeChanges': 'Auto Backup',
261
+ '_setupPin': 'Setup/Change Admin PIN'
262
+ };
263
+
264
+ await this.showSettingsCategory(advancedSettings);
265
+ }
266
+
267
+ /**
268
+ * Show settings category with edit options
269
+ */
270
+ async showSettingsCategory(categorySettings) {
271
+ const keys = Object.keys(categorySettings);
272
+
273
+ // Display current values
274
+ keys.forEach((key, index) => {
275
+ const value = this.getNestedValue(this.settings, key);
276
+ const displayValue = this.formatValue(value);
277
+ console.log(` ${colors.cyan}${index + 1}${colors.reset}) ${categorySettings[key]}`);
278
+ console.log(` ${colors.dim}Current: ${colors.reset}${displayValue}`);
279
+ });
280
+
281
+ console.log(`\n ${colors.yellow}b${colors.reset}) Back to main menu`);
282
+ console.log();
283
+
284
+ const choice = await this.prompt('Select setting to edit (or b for back): ');
285
+
286
+ if (choice.toLowerCase() === 'b') {
287
+ return;
288
+ }
289
+
290
+ const index = parseInt(choice) - 1;
291
+ if (index >= 0 && index < keys.length) {
292
+ const key = keys[index];
293
+ await this.editSetting(key, categorySettings[key]);
294
+ await this.showSettingsCategory(categorySettings);
295
+ } else {
296
+ this.error('Invalid option.');
297
+ await this.pause();
298
+ await this.showSettingsCategory(categorySettings);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Check if setting requires admin authentication
304
+ */
305
+ requiresAdminAuth(key) {
306
+ const adminProtectedSettings = [
307
+ 'security.adminPinEnabled',
308
+ 'security.sessionTimeout',
309
+ 'advanced.strictMode',
310
+ 'debug.enabled',
311
+ 'debug.verboseLogging',
312
+ 'advanced.backupBeforeChanges'
313
+ ];
314
+ return adminProtectedSettings.includes(key);
315
+ }
316
+
317
+ /**
318
+ * Get helper text for a setting
319
+ */
320
+ getHelperText(key) {
321
+ const helperTexts = {
322
+ 'language': 'Select the interface language for the toolkit. Changes take effect immediately.',
323
+ 'theme': 'Choose the color theme for the interface. Options: light, dark, auto.',
324
+ 'dateFormat': 'Set the date format for reports and logs. Examples: YYYY-MM-DD, DD/MM/YYYY, MM-DD-YYYY.',
325
+ 'notifications.enabled': 'Enable or disable system notifications. Enter: true or false.',
326
+ 'directories.locales': 'Path to the directory containing translation files.',
327
+ 'directories.reports': 'Path where analysis reports will be saved.',
328
+ 'directories.backup': 'Path for backup files. Leave empty for default.',
329
+ 'processing.batchSize': 'Number of files to process simultaneously. Range: 1-100.',
330
+ 'processing.concurrentFiles': 'Maximum concurrent file operations. Range: 1-20.',
331
+ 'processing.sizingThreshold': 'File size threshold in KB for warnings. Range: 1-10000.',
332
+ 'processing.autoSave': 'Auto-save interval in minutes. 0 to disable. Range: 0-60.',
333
+ 'advanced.strictMode': '🔒 Enable strict validation mode. Requires admin PIN.',
334
+ 'debug.enabled': '🔒 Enable debug mode for troubleshooting. Requires admin PIN.',
335
+ 'debug.verboseLogging': '🔒 Enable detailed logging. Requires admin PIN.',
336
+ 'security.adminPinEnabled': '🔒 Enable admin PIN protection. Requires admin PIN.',
337
+ 'security.sessionTimeout': '🔒 Session timeout in minutes. Requires admin PIN.',
338
+ 'advanced.backupBeforeChanges': '🔒 Auto-backup before changes. Requires admin PIN.'
339
+ };
340
+ return helperTexts[key] || 'No additional information available.';
341
+ }
342
+
343
+ /**
344
+ * Get valid options for a setting
345
+ */
346
+ getValidOptions(key, schema) {
347
+ if (schema && schema.enum) {
348
+ return schema.enum;
349
+ }
350
+
351
+ const validOptions = {
352
+ 'language': ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'],
353
+ 'theme': ['light', 'dark', 'auto'],
354
+ 'dateFormat': ['YYYY-MM-DD', 'DD/MM/YYYY', 'MM-DD-YYYY'],
355
+ 'notifications.enabled': ['true', 'false'],
356
+ 'advanced.strictMode': ['true', 'false'],
357
+ 'debug.enabled': ['true', 'false'],
358
+ 'debug.verboseLogging': ['true', 'false'],
359
+ 'security.adminPinEnabled': ['true', 'false'],
360
+ 'advanced.backupBeforeChanges': ['true', 'false']
361
+ };
362
+
363
+ return validOptions[key] || null;
364
+ }
365
+
366
+ /**
367
+ * Validate input value
368
+ */
369
+ validateInput(value, key, schema) {
370
+ const validOptions = this.getValidOptions(key, schema);
371
+
372
+ if (validOptions) {
373
+ if (!validOptions.includes(value.toLowerCase())) {
374
+ return { valid: false, message: `Invalid option. Valid options: ${validOptions.join(', ')}` };
375
+ }
376
+ }
377
+
378
+ // Numeric validations
379
+ if (key.includes('batchSize')) {
380
+ const num = parseInt(value);
381
+ if (isNaN(num) || num < 1 || num > 100) {
382
+ return { valid: false, message: 'Batch size must be between 1 and 100.' };
383
+ }
384
+ }
385
+
386
+ if (key.includes('concurrentFiles')) {
387
+ const num = parseInt(value);
388
+ if (isNaN(num) || num < 1 || num > 20) {
389
+ return { valid: false, message: 'Concurrent files must be between 1 and 20.' };
390
+ }
391
+ }
392
+
393
+ if (key.includes('sizingThreshold')) {
394
+ const num = parseInt(value);
395
+ if (isNaN(num) || num < 1 || num > 10000) {
396
+ return { valid: false, message: 'Sizing threshold must be between 1 and 10000 KB.' };
397
+ }
398
+ }
399
+
400
+ if (key.includes('autoSave') || key.includes('sessionTimeout')) {
401
+ const num = parseInt(value);
402
+ if (isNaN(num) || num < 0 || num > 60) {
403
+ return { valid: false, message: 'Value must be between 0 and 60 minutes.' };
404
+ }
405
+ }
406
+
407
+ return { valid: true };
408
+ }
409
+
410
+ /**
411
+ * Edit a specific setting
412
+ */
413
+ async editSetting(key, label) {
414
+ // Special handling for PIN setup
415
+ if (key === '_setupPin') {
416
+ await this.handlePinSetup();
417
+ return;
418
+ }
419
+
420
+ // Check if admin authentication is required
421
+ if (this.requiresAdminAuth(key) && !this.adminAuthenticated) {
422
+ console.log(`\n🔒 Admin authentication required for: ${label}`);
423
+ const authenticated = await this.adminPin.verifyPin();
424
+ if (!authenticated) {
425
+ console.log('❌ Access denied. Returning to menu.');
426
+ await this.pause();
427
+ return;
428
+ }
429
+ this.adminAuthenticated = true;
430
+ }
431
+
432
+ const currentValue = this.getNestedValue(this.settings, key);
433
+ const schema = this.getSettingSchema(key);
434
+
435
+ console.log(`\n${colors.bright}Editing: ${label}${colors.reset}`);
436
+
437
+ // Show helper text
438
+ const helperText = this.getHelperText(key);
439
+ console.log(`${colors.dim}${helperText}${colors.reset}\n`);
440
+
441
+ // Show current value with special handling for admin PIN
442
+ if (key === 'security.adminPinEnabled') {
443
+ const pinDisplay = this.adminPin.getPinDisplay();
444
+ console.log(`Current value: ${this.formatValue(currentValue)} (PIN: ${pinDisplay})`);
445
+ } else {
446
+ console.log(`Current value: ${this.formatValue(currentValue)}`);
447
+ }
448
+
449
+ // Show valid options
450
+ const validOptions = this.getValidOptions(key, schema);
451
+ if (validOptions) {
452
+ console.log(`\n${colors.cyan}Valid options:${colors.reset}`);
453
+ validOptions.forEach((option, index) => {
454
+ const marker = option.toLowerCase() === String(currentValue).toLowerCase() ? ' ← current' : '';
455
+ console.log(` ${index + 1}) ${option}${colors.dim}${marker}${colors.reset}`);
456
+ });
457
+ }
458
+
459
+ console.log();
460
+ const newValue = await this.prompt(`Enter new value (or press Enter to keep current): `);
461
+
462
+ if (newValue.trim() === '') {
463
+ return;
464
+ }
465
+
466
+ // Validate input
467
+ const validation = this.validateInput(newValue.trim(), key, schema);
468
+ if (!validation.valid) {
469
+ this.error(validation.message);
470
+ await this.pause();
471
+ return;
472
+ }
473
+
474
+ // Convert the value
475
+ const convertedValue = this.convertValue(newValue.trim(), schema);
476
+ if (convertedValue === null) {
477
+ this.error('Invalid value format.');
478
+ await this.pause();
479
+ return;
480
+ }
481
+
482
+ // Special handling for admin PIN setup
483
+ if (key === 'security.adminPinEnabled' && convertedValue === true && !this.adminPin.isPinSet()) {
484
+ console.log('\n🔐 Setting up admin PIN...');
485
+ const pinSetup = await this.adminPin.setupPin();
486
+ if (!pinSetup) {
487
+ console.log('❌ Failed to set up admin PIN. Setting not changed.');
488
+ await this.pause();
489
+ return;
490
+ }
491
+ }
492
+
493
+ // Update the setting
494
+ this.setNestedValue(this.settings, key, convertedValue);
495
+ this.modified = true;
496
+
497
+ // Special handling for language changes
498
+ if (key === 'language') {
499
+ // Refresh UI language immediately
500
+ if (typeof uiI18n.refreshLanguageFromSettings === 'function') {
501
+ uiI18n.refreshLanguageFromSettings();
502
+ }
503
+ }
504
+
505
+ this.success(`${label} updated successfully.`);
506
+ await this.pause();
507
+ }
508
+
509
+ /**
510
+ * Handle PIN setup/change
511
+ */
512
+ async handlePinSetup() {
513
+ this.clearScreen();
514
+ this.showHeader();
515
+ console.log(`${colors.bright}Admin PIN Setup${colors.reset}\n`);
516
+
517
+ if (this.adminPin.isPinSet()) {
518
+ console.log('📌 Admin PIN is currently configured.');
519
+ console.log('\nOptions:');
520
+ console.log(' 1) Change existing PIN');
521
+ console.log(' 2) Remove PIN protection');
522
+ console.log(' 3) Cancel');
523
+ console.log();
524
+
525
+ const choice = await this.prompt('Select option: ');
526
+
527
+ switch (choice) {
528
+ case '1':
529
+ console.log('\n🔐 Verify current PIN first:');
530
+ const verified = await this.adminPin.verifyPin();
531
+ if (verified) {
532
+ console.log('\n🔄 Setting up new PIN...');
533
+ const success = await this.adminPin.setupPin();
534
+ if (success) {
535
+ this.success('Admin PIN updated successfully!');
536
+ } else {
537
+ this.error('Failed to update admin PIN.');
538
+ }
539
+ }
540
+ break;
541
+ case '2':
542
+ console.log('\n🔐 Verify current PIN to remove protection:');
543
+ const verifiedForRemoval = await this.adminPin.verifyPin();
544
+ if (verifiedForRemoval) {
545
+ // Remove PIN file
546
+ const fs = require('fs');
547
+ const pinFile = this.adminPin.pinFile;
548
+ if (fs.existsSync(pinFile)) {
549
+ fs.unlinkSync(pinFile);
550
+ this.success('Admin PIN protection removed.');
551
+ }
552
+ }
553
+ break;
554
+ case '3':
555
+ console.log('Operation cancelled.');
556
+ break;
557
+ default:
558
+ this.error('Invalid option.');
559
+ }
560
+ } else {
561
+ console.log('🔓 No admin PIN is currently configured.');
562
+ console.log('\nSetting up admin PIN will add security for:');
563
+ console.log(' • Changing security settings');
564
+ console.log(' • Modifying advanced configurations');
565
+ console.log(' • Accessing debug tools');
566
+ console.log(' • Resetting settings');
567
+ console.log();
568
+
569
+ const response = await this.prompt('Would you like to set up an admin PIN? (y/N): ');
570
+
571
+ if (response.toLowerCase() === 'y' || response.toLowerCase() === 'yes') {
572
+ const success = await this.adminPin.setupPin();
573
+ if (success) {
574
+ this.success('Admin PIN configured successfully!');
575
+ // Enable admin PIN protection in settings
576
+ this.setNestedValue(this.settings, 'security.adminPinEnabled', true);
577
+ this.modified = true;
578
+ } else {
579
+ this.error('Failed to configure admin PIN.');
580
+ }
581
+ } else {
582
+ console.log('⏭️ Admin PIN setup cancelled.');
583
+ }
584
+ }
585
+
586
+ await this.pause();
587
+ }
588
+
589
+ /**
590
+ * Show all current settings
591
+ */
592
+ async showAllSettings() {
593
+ this.clearScreen();
594
+ this.showHeader();
595
+ console.log(`${colors.bright}Current Settings${colors.reset}\n`);
596
+
597
+ this.displaySettingsTree(this.settings, '');
598
+
599
+ console.log(`\nPress Enter to continue...`);
600
+ await this.prompt('');
601
+ }
602
+
603
+ /**
604
+ * Display settings in a tree format
605
+ */
606
+ displaySettingsTree(obj, prefix) {
607
+ Object.keys(obj).forEach(key => {
608
+ const value = obj[key];
609
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
610
+ console.log(`${prefix}${colors.bright}${key}:${colors.reset}`);
611
+ this.displaySettingsTree(value, prefix + ' ');
612
+ } else {
613
+ console.log(`${prefix}${colors.cyan}${key}:${colors.reset} ${this.formatValue(value)}`);
614
+ }
615
+ });
616
+ }
617
+
618
+ /**
619
+ * Show import/export options
620
+ */
621
+ async showImportExport() {
622
+ this.clearScreen();
623
+ this.showHeader();
624
+ console.log(`${colors.bright}Import/Export Settings${colors.reset}\n`);
625
+
626
+ console.log(` ${colors.cyan}1${colors.reset}) Export current settings`);
627
+ console.log(` ${colors.cyan}2${colors.reset}) Import settings from file`);
628
+ console.log(` ${colors.cyan}3${colors.reset}) Create backup`);
629
+ console.log(` ${colors.yellow}b${colors.reset}) Back to main menu\n`);
630
+
631
+ const choice = await this.prompt('Select option: ');
632
+
633
+ switch (choice) {
634
+ case '1':
635
+ await this.exportSettings();
636
+ break;
637
+ case '2':
638
+ await this.importSettings();
639
+ break;
640
+ case '3':
641
+ await this.createBackup();
642
+ break;
643
+ case 'b':
644
+ return;
645
+ default:
646
+ this.error('Invalid option.');
647
+ await this.pause();
648
+ await this.showImportExport();
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Export settings to a file
654
+ */
655
+ async exportSettings() {
656
+ const filename = await this.prompt('Enter filename (or press Enter for default): ');
657
+ const exportFile = filename.trim() || `i18n-settings-${new Date().toISOString().split('T')[0]}.json`;
658
+
659
+ try {
660
+ fs.writeFileSync(exportFile, JSON.stringify(this.settings, null, 2));
661
+ this.success(`Settings exported to ${exportFile}`);
662
+ } catch (error) {
663
+ this.error(`Failed to export settings: ${error.message}`);
664
+ }
665
+
666
+ await this.pause();
667
+ }
668
+
669
+ /**
670
+ * Import settings from a file
671
+ */
672
+ async importSettings() {
673
+ const filename = await this.prompt('Enter filename to import: ');
674
+
675
+ if (!filename.trim()) {
676
+ return;
677
+ }
678
+
679
+ try {
680
+ if (!fs.existsSync(filename)) {
681
+ this.error('File not found.');
682
+ await this.pause();
683
+ return;
684
+ }
685
+
686
+ const importedSettings = JSON.parse(fs.readFileSync(filename, 'utf8'));
687
+
688
+ // Validate imported settings
689
+ if (!settingsManager.validateSettings(importedSettings)) {
690
+ this.error('Invalid settings file format.');
691
+ await this.pause();
692
+ return;
693
+ }
694
+
695
+ const confirm = await this.prompt('This will replace all current settings. Continue? (y/N): ');
696
+ if (confirm.toLowerCase() === 'y') {
697
+ this.settings = importedSettings;
698
+ this.modified = true;
699
+ this.success('Settings imported successfully.');
700
+ }
701
+ } catch (error) {
702
+ this.error(`Failed to import settings: ${error.message}`);
703
+ }
704
+
705
+ await this.pause();
706
+ }
707
+
708
+ /**
709
+ * Create a backup of current settings
710
+ */
711
+ async createBackup() {
712
+ try {
713
+ const backupFile = settingsManager.createBackup();
714
+ this.success(`Backup created: ${backupFile}`);
715
+ } catch (error) {
716
+ this.error(`Failed to create backup: ${error.message}`);
717
+ }
718
+
719
+ await this.pause();
720
+ }
721
+
722
+ /**
723
+ * Reset settings to defaults
724
+ */
725
+ async resetToDefaults() {
726
+ this.clearScreen();
727
+ this.showHeader();
728
+ console.log(`${colors.bright}Reset to Defaults${colors.reset}\n`);
729
+
730
+ console.log(`${colors.yellow}⚠️ This will reset ALL settings to their default values.${colors.reset}`);
731
+ console.log(`${colors.yellow}⚠️ Any unsaved changes will be lost.${colors.reset}\n`);
732
+
733
+ const confirm = await this.prompt('Are you sure you want to continue? (y/N): ');
734
+
735
+ if (confirm.toLowerCase() === 'y') {
736
+ try {
737
+ settingsManager.resetToDefaults();
738
+ this.settings = settingsManager.getSettings();
739
+ this.modified = false;
740
+ this.success('Settings reset to defaults successfully.');
741
+ } catch (error) {
742
+ this.error(`Failed to reset settings: ${error.message}`);
743
+ }
744
+ }
745
+
746
+ await this.pause();
747
+ }
748
+
749
+ /**
750
+ * Save current settings
751
+ */
752
+ async saveSettings() {
753
+ try {
754
+ const success = settingsManager.saveSettings(this.settings);
755
+ if (success) {
756
+ this.modified = false;
757
+ this.success('Settings saved successfully.');
758
+ } else {
759
+ this.error('Failed to save settings.');
760
+ }
761
+ } catch (error) {
762
+ this.error(`Failed to save settings: ${error.message}`);
763
+ }
764
+
765
+ await this.pause();
766
+ }
767
+
768
+ /**
769
+ * Show help information
770
+ */
771
+ async showHelp() {
772
+ this.clearScreen();
773
+ this.showHeader();
774
+ console.log(`${colors.bright}Help Information${colors.reset}\n`);
775
+
776
+ console.log(`${colors.cyan}Navigation:${colors.reset}`);
777
+ console.log(` • Use number keys to select menu options`);
778
+ console.log(` • Use 'b' to go back to previous menu`);
779
+ console.log(` • Use 'q' to quit (with save prompt if needed)`);
780
+ console.log(` • Use 's' to save changes at any time\n`);
781
+
782
+ console.log(`${colors.cyan}Settings Categories:${colors.reset}`);
783
+ console.log(` • UI Settings: Interface language, theme, and display options`);
784
+ console.log(` • Directory Settings: Paths for locales, reports, and backups`);
785
+ console.log(` • Processing Settings: Performance and batch processing options`);
786
+ console.log(` • Advanced Settings: Validation, logging, and expert features\n`);
787
+
788
+ console.log(`${colors.cyan}Environment Variables:${colors.reset}`);
789
+ console.log(` • I18N_CONFIG_FILE: Custom config file path`);
790
+ console.log(` • I18N_LOCALE_DIR: Override locales directory`);
791
+ console.log(` • I18N_REPORTS_DIR: Override reports directory\n`);
792
+
793
+ console.log(`${colors.cyan}Command Line Usage:${colors.reset}`);
794
+ console.log(` • i18ntk manage --command=settings`);
795
+ console.log(` • node settings-cli.js (direct access)`);
796
+ console.log(` • All scripts support --config flag for custom config files\n`);
797
+
798
+ console.log(`Press Enter to continue...`);
799
+ await this.prompt('');
800
+ }
801
+
802
+ /**
803
+ * Report a bug - opens GitHub issues page
804
+ */
805
+ async reportBug() {
806
+ this.clearScreen();
807
+ this.showHeader();
808
+ console.log(`${colors.bright}Report a Bug${colors.reset}\n`);
809
+
810
+ console.log(`${colors.cyan}GitHub Issues Page:${colors.reset}`);
811
+ console.log(`https://github.com/vladnoskv/i18n-management-toolkit-main/issues\n`);
812
+
813
+ console.log(`${colors.yellow}Before reporting a bug, please:${colors.reset}`);
814
+ console.log(` • Check if the issue already exists`);
815
+ console.log(` • Include steps to reproduce the problem`);
816
+ console.log(` • Provide error messages and logs`);
817
+ console.log(` • Mention your operating system and Node.js version\n`);
818
+
819
+ console.log(`${colors.cyan}Opening GitHub issues page...${colors.reset}`);
820
+
821
+ try {
822
+ const { exec } = require('child_process');
823
+ const url = 'https://github.com/vladnoskv/i18n-management-toolkit-main/issues';
824
+
825
+ // Try to open the URL in the default browser
826
+ let command;
827
+ switch (process.platform) {
828
+ case 'darwin': // macOS
829
+ command = `open "${url}"`;
830
+ break;
831
+ case 'win32': // Windows
832
+ command = `start "" "${url}"`;
833
+ break;
834
+ default: // Linux and others
835
+ command = `xdg-open "${url}"`;
836
+ break;
837
+ }
838
+
839
+ exec(command, (error) => {
840
+ if (error) {
841
+ console.log(`${colors.yellow}Could not automatically open browser.${colors.reset}`);
842
+ console.log(`Please manually visit: ${url}`);
843
+ } else {
844
+ console.log(`${colors.green}✅ Browser opened successfully!${colors.reset}`);
845
+ }
846
+ });
847
+ } catch (error) {
848
+ console.log(`${colors.yellow}Could not automatically open browser.${colors.reset}`);
849
+ console.log(`Please manually visit: https://github.com/vladnoskv/i18n-management-toolkit-main/issues`);
850
+ }
851
+
852
+ await this.pause();
853
+ }
854
+
855
+ /**
856
+ * Quit the application
857
+ */
858
+ async quit() {
859
+ if (this.modified) {
860
+ console.log(`\n${colors.yellow}⚠️ You have unsaved changes.${colors.reset}`);
861
+ const save = await this.prompt('Save before quitting? (Y/n): ');
862
+
863
+ if (save.toLowerCase() !== 'n') {
864
+ await this.saveSettings();
865
+ }
866
+ }
867
+
868
+ console.log(`\n${colors.green}Thank you for using the i18n settings manager!${colors.reset}`);
869
+ this.rl.close();
870
+ process.exit(0);
871
+ }
872
+
873
+ // Utility methods
874
+
875
+ /**
876
+ * Prompt user for input
877
+ */
878
+ prompt(question) {
879
+ return new Promise(resolve => {
880
+ this.rl.question(question, resolve);
881
+ });
882
+ }
883
+
884
+ /**
885
+ * Pause and wait for user input
886
+ */
887
+ async pause() {
888
+ await this.prompt('Press Enter to continue...');
889
+ }
890
+
891
+ /**
892
+ * Display success message
893
+ */
894
+ success(message) {
895
+ console.log(`${colors.green}✅ ${message}${colors.reset}`);
896
+ }
897
+
898
+ /**
899
+ * Display error message
900
+ */
901
+ error(message) {
902
+ console.log(`${colors.red}❌ ${message}${colors.reset}`);
903
+ }
904
+
905
+ /**
906
+ * Display warning message
907
+ */
908
+ warning(message) {
909
+ console.log(`${colors.yellow}⚠️ ${message}${colors.reset}`);
910
+ }
911
+
912
+ /**
913
+ * Get nested value from object using dot notation
914
+ */
915
+ getNestedValue(obj, path) {
916
+ return path.split('.').reduce((current, key) => current && current[key], obj);
917
+ }
918
+
919
+ /**
920
+ * Set nested value in object using dot notation
921
+ */
922
+ setNestedValue(obj, path, value) {
923
+ const keys = path.split('.');
924
+ const lastKey = keys.pop();
925
+ const target = keys.reduce((current, key) => {
926
+ if (!current[key] || typeof current[key] !== 'object') {
927
+ current[key] = {};
928
+ }
929
+ return current[key];
930
+ }, obj);
931
+ target[lastKey] = value;
932
+ }
933
+
934
+ /**
935
+ * Get schema for a specific setting
936
+ */
937
+ getSettingSchema(path) {
938
+ // This would need to be implemented based on your schema structure
939
+ return this.schema && this.schema.properties ?
940
+ this.getNestedValue(this.schema.properties, path) : null;
941
+ }
942
+
943
+ /**
944
+ * Format value for display
945
+ */
946
+ formatValue(value) {
947
+ if (value === null || value === undefined) {
948
+ return `${colors.dim}(not set)${colors.reset}`;
949
+ }
950
+ if (typeof value === 'boolean') {
951
+ return value ? `${colors.green}enabled${colors.reset}` : `${colors.red}disabled${colors.reset}`;
952
+ }
953
+ if (Array.isArray(value)) {
954
+ return `[${value.join(', ')}]`;
955
+ }
956
+ return String(value);
957
+ }
958
+
959
+ /**
960
+ * Convert string input to appropriate type
961
+ */
962
+ convertValue(input, schema) {
963
+ if (!schema) {
964
+ return input;
965
+ }
966
+
967
+ switch (schema.type) {
968
+ case 'boolean':
969
+ const lower = input.toLowerCase();
970
+ if (['true', 'yes', 'y', '1', 'on', 'enabled'].includes(lower)) {
971
+ return true;
972
+ }
973
+ if (['false', 'no', 'n', '0', 'off', 'disabled'].includes(lower)) {
974
+ return false;
975
+ }
976
+ return null;
977
+
978
+ case 'number':
979
+ case 'integer':
980
+ const num = Number(input);
981
+ return isNaN(num) ? null : num;
982
+
983
+ case 'array':
984
+ try {
985
+ return input.split(',').map(s => s.trim());
986
+ } catch {
987
+ return null;
988
+ }
989
+
990
+ default:
991
+ return input;
992
+ }
993
+ }
994
+
995
+ /**
996
+ * Run method for compatibility with manager
997
+ */
998
+ async run() {
999
+ try {
1000
+ console.log('🎛️ Starting Settings CLI...');
1001
+ await this.start();
1002
+ } catch (error) {
1003
+ console.error('❌ Settings CLI Error:', error.message);
1004
+ console.error('Stack trace:', error.stack);
1005
+ throw error;
1006
+ }
1007
+ }
1008
+ }
1009
+
1010
+ // Export the class
1011
+ module.exports = SettingsCLI;
1012
+
1013
+ // If run directly, start the CLI
1014
+ if (require.main === module) {
1015
+ const cli = new SettingsCLI();
1016
+ cli.start().catch(error => {
1017
+ console.error('❌ Failed to start settings CLI:', error.message);
1018
+ console.error('Stack trace:', error.stack);
1019
+ process.exit(1);
1020
+ });
1021
+ }