i18ntk 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,969 +1,985 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
- const { I18nError } = require('../utils/i18n-helper');
5
- const SecurityUtils = require('../utils/security');
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { I18nError } = require('../utils/i18n-helper');
5
+ const SecurityUtils = require('../utils/security');
6
+
7
+ class SettingsManager {
8
+ constructor() {
9
+ // Use centralized .i18ntk-settings file as single source of truth
10
+ this.configDir = path.resolve(__dirname, '..');
11
+ this.configFile = path.join(process.cwd(), '.i18ntk-settings');
12
+ this.backupDir = path.join(process.cwd(), 'i18ntk-backups');
13
+ this.saveTimeout = null;
14
+
15
+ this.defaultConfig = {
16
+ "version": "1.10.1",
17
+ "language": "en",
18
+ "uiLanguage": "en",
19
+ "theme": "dark",
20
+ "projectRoot": process.cwd(),
21
+ "sourceDir": "./locales",
22
+ "i18nDir": "./i18n",
23
+ "outputDir": "./i18ntk-reports",
24
+ "setup": {
25
+ "completed": false,
26
+ "completedAt": null,
27
+ "version": null,
28
+ "setupId": null
29
+ },
30
+ "framework": {
31
+ "preference": "auto", // auto | vanilla | react | vue | angular | svelte | i18next | nuxt | next
32
+ "fallback": "vanilla",
33
+ "detect": true,
34
+ "supported": ["react", "vue", "angular", "svelte", "i18next", "nuxt", "next", "vanilla"]
35
+ },
36
+ "scriptDirectories": {
37
+ "main": "./main",
38
+ "utils": "./utils",
39
+ "scripts": "./scripts",
40
+ "settings": "./settings",
41
+ "uiLocales": "./ui-locales"
42
+ },
43
+ "processing": {
44
+ "mode": "extreme",
45
+ "cacheEnabled": true,
46
+ "batchSize": 1000,
47
+ "maxWorkers": 4,
48
+ "timeout": 30000,
49
+ "retryAttempts": 3,
50
+ "parallelProcessing": true,
51
+ "memoryOptimization": true,
52
+ "compression": true
53
+ },
54
+ "reports": {
55
+ "format": "json",
56
+ "includeSource": false,
57
+ "includeStats": true,
58
+ "includeRecommendations": true,
59
+ "includeSecurity": true,
60
+ "includePerformance": true,
61
+ "saveToFile": true,
62
+ "fileName": "i18n-report-[timestamp].json",
63
+ "outputPath": "./i18ntk-reports",
64
+ "compress": true
65
+ },
66
+ "ui": {
67
+ "showProgress": true,
68
+ "showColors": true,
69
+ "showTimestamps": true,
70
+ "showTips": true,
71
+ "showWarnings": true,
72
+ "showErrors": true,
73
+ "interactive": true,
74
+ "confirmActions": true,
75
+ "autoComplete": true,
76
+ "syntaxHighlighting": true
77
+ },
78
+ "behavior": {
79
+ "autoSave": true,
80
+ "autoBackup": false,
81
+ "backupFrequency": "weekly",
82
+ "maxBackups": 1,
83
+ "confirmDestructive": true,
84
+ "validateOnSave": true,
85
+ "formatOnSave": true,
86
+ "lintOnSave": true,
87
+ "autoFix": false,
88
+ "strictMode": false,
89
+ "devMode": false
90
+ },
91
+ "notifications": {
92
+ "enabled": true,
93
+ "desktop": true,
94
+ "sound": false,
95
+ "types": {
96
+ "success": true,
97
+ "warning": true,
98
+ "error": true,
99
+ "info": true,
100
+ "debug": false
101
+ },
102
+ "timeout": 5000,
103
+ "maxNotifications": 5
104
+ },
105
+ "dateTime": {
106
+ "timezone": "auto",
107
+ "format": "YYYY-MM-DD HH:mm:ss",
108
+ "locale": "en-US",
109
+ "dateFormat": "YYYY-MM-DD",
110
+ "timeFormat": "HH:mm:ss",
111
+ "use24Hour": true,
112
+ "showTimezone": false
113
+ },
114
+ "advanced": {
115
+ "debugMode": false,
116
+ "verboseLogging": false,
117
+ "performanceTracking": true,
118
+ "memoryProfiling": false,
119
+ "stackTraces": false,
120
+ "experimentalFeatures": false,
121
+ "customExtractors": [],
122
+ "customValidators": [],
123
+ "customFormatters": []
124
+ },
125
+ "backup": {
126
+ "enabled": false,
127
+ "location": "./i18ntk-backups",
128
+ "frequency": "daily",
129
+ "retention": 7,
130
+ "maxBackups": 1,
131
+ "compression": true,
132
+ "encryption": true,
133
+ "autoCleanup": true,
134
+ "maxSize": "100MB",
135
+ "includeReports": true,
136
+ "includeLogs": true
137
+ },
138
+ "security": {
139
+ "enabled": true,
140
+ "adminPinEnabled": false,
141
+ "sessionTimeout": 1800000,
142
+ "maxFailedAttempts": 3,
143
+ "lockoutDuration": 300000,
144
+ "encryption": {
145
+ "enabled": true,
146
+ "algorithm": "aes-256-gcm",
147
+ "keyDerivation": "pbkdf2",
148
+ "iterations": 100000
149
+ },
150
+ "pinProtection": {
151
+ "enabled": false,
152
+ "pin": null,
153
+ "protectedScripts": {
154
+ "init": false,
155
+ "analyze": false,
156
+ "validate": false,
157
+ "fix": false,
158
+ "manage": true,
159
+ "settings": true,
160
+ "admin": true
161
+ }
162
+ },
163
+ "auditLog": true,
164
+ "sanitizeInput": true,
165
+ "validatePaths": true,
166
+ "restrictAccess": false
167
+ },
168
+ "debug": {
169
+ "enabled": false,
170
+ "logLevel": "info",
171
+ "logFile": "./i18ntk-debug.log",
172
+ "maxFileSize": "10MB",
173
+ "maxFiles": 5,
174
+ "includeStackTrace": false,
175
+ "includeMemoryUsage": false,
176
+ "performanceMetrics": false
177
+ },
178
+ "sizeLimit": null,
179
+ "placeholderStyles": {
180
+ "en": [
181
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
182
+ "%\\\\{[^}]+\\\\}",
183
+ "%[sdif]",
184
+ "\\\\$\\\\{[^}]+\\\\}",
185
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
186
+ "__\\\\w+__",
187
+ "\\\\{\\\\w+\\\\}",
188
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
189
+ "\\\\{\\\\{t\\\\s+['\"][^'\"]*['\"]\\\\}\\\\}",
190
+ "t\\\\(['\"][^'\"]*['\"]\\\\)",
191
+ "i18n\\\\.t\\\\(['\"][^'\"]*['\"]\\\\)"
192
+ ],
193
+ "de": [
194
+ "%\\\\{[^}]+\\\\}",
195
+ "%[sdif]",
196
+ "\\\\$\\\\{[^}]+\\\\}",
197
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
198
+ "__\\\\w+__",
199
+ "\\\\{\\\\w+\\\\}",
200
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
201
+ "\\\\{\\\\{[^}]+\\\\}\\\\}"
202
+ ],
203
+ "es": [
204
+ "%\\\\{[^}]+\\\\}",
205
+ "%[sdif]",
206
+ "\\\\$\\\\{[^}]+\\\\}",
207
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
208
+ "__\\\\w+__",
209
+ "\\\\{\\\\w+\\\\}",
210
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
211
+ "\\\\{\\\\{[^}]+\\\\}\\\\}"
212
+ ],
213
+ "fr": [
214
+ "%\\\\{[^}]+\\\\}",
215
+ "%[sdif]",
216
+ "\\\\$\\\\{[^}]+\\\\}",
217
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
218
+ "__\\\\w+__",
219
+ "\\\\{\\\\w+\\\\}",
220
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
221
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
222
+ "\\\\{\\\\d+\\\\}"
223
+ ],
224
+ "ru": [
225
+ "%\\\\{[^}]+\\\\}",
226
+ "%[sdif]",
227
+ "\\\\$\\\\{[^}]+\\\\}",
228
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
229
+ "__\\\\w+__",
230
+ "\\\\{\\\\w+\\\\}",
231
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
232
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
233
+ "\\\\{\\\\d+\\\\}"
234
+ ],
235
+ "zh": [
236
+ "%\\\\{[^}]+\\\\}",
237
+ "%[sdif]",
238
+ "\\\\$\\\\{[^}]+\\\\}",
239
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
240
+ "__\\\\w+__",
241
+ "\\\\{\\\\w+\\\\}",
242
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
243
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
244
+ "\\\\{\\\\d+\\\\}"
245
+ ],
246
+ "ja": [
247
+ "%\\\\{[^}]+\\\\}",
248
+ "%[sdif]",
249
+ "\\\\$\\\\{[^}]+\\\\}",
250
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
251
+ "__\\\\w+__",
252
+ "\\\\{\\\\w+\\\\}",
253
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
254
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
255
+ "\\\\{\\\\d+\\\\}"
256
+ ],
257
+ "universal": [
258
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
259
+ "%\\\\{[^}]+\\\\}",
260
+ "%[sdif]",
261
+ "\\\\$\\\\{[^}]+\\\\}",
262
+ "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
263
+ "__\\\\w+__",
264
+ "\\\\{\\\\w+\\\\}",
265
+ "\\\\[\\\\[\\\\w+\\\\]\\\\]",
266
+ "\\\\{\\\\d+\\\\}",
267
+ "\\\\{\\\\d*\\\\}"
268
+ ],
269
+ "frameworks": {
270
+ "react": [
271
+ "\\\\{\\\\{[^}]+\\\\}\\\\}",
272
+ "\\\\$\\\\{[^}]+\\\\}",
273
+ "t\\\\(['\"][^'\"]*['\"]",
274
+ "i18n\\\\.t\\\\(['\"][^'\"]*['\"]",
275
+ "useTranslation\\\\s*\\\\([^)]*\\\\)",
276
+ "<Trans[^>]*>.*?</Trans>"
277
+ ],
278
+ "vue": [
279
+ "\\\\$t\\\\(['\"][^'\"]*['\"]",
280
+ "\\\\$tc\\\\(['\"][^'\"]*['\"]",
281
+ "\\\\{\\\\{\\\\$t\\\\([^)]+\\\\)\\\\}\\\\}",
282
+ "v-t=['\"][^'\"]*['\"]"
283
+ ],
284
+ "angular": [
285
+ "'[^']*'\\\\s*\\\\|\\\\s*translate",
286
+ "\\\\{[^}]*'[^']*'\\\\s*\\\\|\\\\s*translate[^}]*\\\\}",
287
+ "translate\\\\s*:\\s*['\"][^'\"]*['\"]"
288
+ ],
289
+ "nextjs": [
290
+ "t\\\\(['\"][^'\"]*['\"]",
291
+ "router\\\\.locale",
292
+ "useTranslation\\\\s*\\\\([^)]*\\\\)",
293
+ "getStaticProps\\\\s*\\\\([^)]*\\\\)",
294
+ "getServerSideProps\\\\s*\\\\([^)]*\\\\)"
295
+ ]
296
+ }
297
+ },
298
+ "framework": {
299
+ "detected": false,
300
+ "preference": "none",
301
+ "prompt": "always",
302
+ "lastPromptedVersion": null
303
+ },
304
+ "setup": {
305
+ "completed": false,
306
+ "completedAt": null,
307
+ "version": null,
308
+ "setupId": null
309
+ }
310
+ };
311
+
312
+ this.settings = this.loadSettings();
313
+ }
314
+
315
+ /**
316
+ * Load settings from file or return default settings
317
+ * @returns {object} Settings object
318
+ */
319
+ loadSettings() {
320
+ try {
321
+ if (SecurityUtils.safeExistsSync(this.configFile)) {
322
+ const content = SecurityUtils.safeReadFileSync(this.configFile, process.cwd(), 'utf8');
323
+ const loadedSettings = JSON.parse(content);
324
+ // Merge with defaults to ensure all properties exist
325
+ this.settings = this.mergeWithDefaults(loadedSettings);
326
+ return this.settings;
327
+ }
328
+ } catch (error) {
329
+ console.error('Error loading settings:', error.message);
330
+ }
331
+ this.settings = { ...this.defaultConfig };
332
+ return this.settings;
333
+ }
334
+
335
+ /**
336
+ * Merge loaded settings with defaults to ensure all properties exist
337
+ * @param {object} loadedSettings - Settings loaded from file
338
+ * @returns {object} Merged settings
339
+ */
340
+ mergeWithDefaults(loadedSettings) {
341
+ const merged = { ...this.defaultConfig, ...loadedSettings };
342
+
343
+ // Ensure nested objects are properly merged
344
+ if (loadedSettings.notifications) {
345
+ merged.notifications = {
346
+ ...this.defaultConfig.notifications,
347
+ ...loadedSettings.notifications,
348
+ types: {
349
+ ...this.defaultConfig.notifications.types,
350
+ ...(loadedSettings.notifications.types || {})
351
+ }
352
+ };
353
+ }
354
+
355
+ if (loadedSettings.processing) {
356
+ merged.processing = { ...this.defaultConfig.processing, ...loadedSettings.processing };
357
+ }
358
+
359
+ if (loadedSettings.advanced) {
360
+ merged.advanced = { ...this.defaultConfig.advanced, ...loadedSettings.advanced };
361
+ }
362
+
363
+ if (loadedSettings.scriptDirectories) {
364
+ merged.scriptDirectories = {
365
+ ...this.defaultConfig.scriptDirectories,
366
+ ...loadedSettings.scriptDirectories
367
+ };
368
+ }
369
+
370
+ if (loadedSettings.security?.pinProtection) {
371
+ merged.security.pinProtection = {
372
+ ...this.defaultConfig.security.pinProtection,
373
+ ...loadedSettings.security.pinProtection,
374
+ protectedScripts: {
375
+ ...this.defaultConfig.security.pinProtection.protectedScripts,
376
+ ...(loadedSettings.security.pinProtection.protectedScripts || {})
377
+ }
378
+ };
379
+ }
380
+
381
+ if (loadedSettings.setup) {
382
+ merged.setup = {
383
+ ...this.defaultConfig.setup,
384
+ ...loadedSettings.setup
385
+ };
386
+ }
387
+
388
+ return merged;
389
+ }
390
+
391
+ /**
392
+ * Save settings to file
393
+ * @param {object} settings - Settings to save
394
+ */
395
+ saveSettings(settings = null) {
396
+ if (settings) {
397
+ this.settings = settings;
398
+ }
399
+
400
+ // Clear any pending save
401
+ if (this.saveTimeout) {
402
+ clearTimeout(this.saveTimeout);
403
+ }
404
+
405
+ // Debounce saves to prevent rapid successive saves
406
+ this.saveTimeout = setTimeout(() => {
407
+ this._saveImmediately();
408
+ }, 100); // 100ms debounce
409
+ }
410
+
411
+ /**
412
+ * Save settings immediately without debounce
413
+ * @param {object} settings - Settings to save
414
+ */
415
+ saveSettingsImmediately(settings = null) {
416
+ if (settings) {
417
+ this.settings = settings;
418
+ }
419
+
420
+ // Clear any pending save
421
+ if (this.saveTimeout) {
422
+ clearTimeout(this.saveTimeout);
423
+ this.saveTimeout = null;
424
+ }
425
+
426
+ this._saveImmediately();
427
+ }
428
+
429
+ /**
430
+ * Internal method to save settings without debounce
431
+ * @private
432
+ */
433
+ _saveImmediately() {
434
+ try {
435
+ if (!SecurityUtils.safeExistsSync(this.configDir)) {
436
+ fs.mkdirSync(this.configDir, { recursive: true });
437
+ }
438
+
439
+ const content = JSON.stringify(this.settings, null, 4);
440
+ SecurityUtils.safeWriteFileSync(this.configFile, content, path.dirname(this.configFile), 'utf8');
441
+
442
+ // Create backup if enabled
443
+ if (this.settings.backup?.enabled) {
444
+ this.createBackup();
445
+ }
446
+
447
+ console.log('Settings saved successfully');
448
+ } catch (error) {
449
+ console.error('Error saving settings:', error.message);
450
+ }
451
+
452
+ this.saveTimeout = null;
453
+ }
454
+
455
+ /**
456
+ * Create backup of current settings
457
+ */
458
+ createBackup() {
459
+ try {
460
+ const configuredBackupLocation = this.settings?.backup?.location || './i18ntk-backups';
461
+ const resolvedBackupDir = path.resolve(process.cwd(), configuredBackupLocation);
462
+ const validatedBackupDir = SecurityUtils.validatePath(resolvedBackupDir, process.cwd());
463
+ if (!validatedBackupDir) {
464
+ console.error('Error creating backup: Invalid backup directory');
465
+ return;
466
+ }
467
+
468
+ if (!SecurityUtils.safeExistsSync(validatedBackupDir, process.cwd())) {
469
+ fs.mkdirSync(validatedBackupDir, { recursive: true });
470
+ }
471
+
472
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
473
+ const backupFile = path.join(validatedBackupDir, `config-${timestamp}.json`);
474
+
475
+ fs.copyFileSync(this.configFile, backupFile);
476
+
477
+ // Clean old backups
478
+ this.cleanupOldBackups(validatedBackupDir);
479
+ } catch (error) {
480
+ console.error('Error creating backup:', error.message);
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Clean old backup files
486
+ */
487
+ cleanupOldBackups(backupDirectory = null) {
488
+ try {
489
+ const activeBackupDir = backupDirectory || this.backupDir;
490
+ if (!SecurityUtils.safeExistsSync(activeBackupDir, process.cwd())) {
491
+ return;
492
+ }
493
+
494
+ const files = fs.readdirSync(activeBackupDir)
495
+ .filter(file => file.startsWith('config-') && file.endsWith('.json'))
496
+ .map(file => ({
497
+ name: file,
498
+ path: path.join(activeBackupDir, file),
499
+ mtime: fs.statSync(path.join(activeBackupDir, file)).mtime
500
+ }))
501
+ .sort((a, b) => b.mtime - a.mtime);
502
+
503
+ const configuredKeep = parseInt(this.settings?.backup?.maxBackups, 10);
504
+ const maxBackups = Number.isInteger(configuredKeep)
505
+ ? Math.min(Math.max(configuredKeep, 1), 3)
506
+ : 1;
507
+ if (files.length > maxBackups) {
508
+ files.slice(maxBackups).forEach(file => {
509
+ fs.unlinkSync(file.path);
510
+ });
511
+ }
512
+ } catch (error) {
513
+ console.error('Error cleaning backups:', error.message);
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Reset settings to defaults
519
+ */
520
+ resetToDefaults() {
521
+ this.settings = { ...this.defaultConfig };
522
+ // Ensure setup section is also reset
523
+ this.settings.setup = {
524
+ "completed": false,
525
+ "completedAt": null,
526
+ "version": null,
527
+ "setupId": null
528
+ };
529
+ // Set projectRoot to "/" for fresh install state
530
+ this.settings.projectRoot = "/";
531
+ this.saveSettings();
532
+ console.log('Settings reset to defaults');
533
+ }
534
+
535
+ /**
536
+ * Complete reset - removes all configuration files and resets to fresh install state
537
+ */
538
+ async completeReset() {
539
+
540
+ try {
541
+ console.log('🧹 Performing complete reset...');
542
+
543
+ // 1. Reset to defaults with projectRoot set to "/" for fresh install
544
+ this.settings = { ...this.defaultConfig };
545
+ this.settings.projectRoot = "/"; // Set to root for fresh install
546
+
547
+ // 2. Remove actual configuration files used by the system
548
+ const packageDir = path.resolve(__dirname, '..');
549
+ const settingsDir = path.join(packageDir, 'settings');
550
+
551
+ // Main configuration file
552
+ const mainConfigPath = path.join(settingsDir, 'i18ntk-config.json');
553
+ if (SecurityUtils.safeExistsSync(mainConfigPath)) {
554
+ fs.unlinkSync(mainConfigPath);
555
+ console.log('✅ Main configuration file removed');
556
+ }
557
+
558
+ // Project configuration file
559
+ const projectConfigPath = path.join(settingsDir, 'project-config.json');
560
+ if (SecurityUtils.safeExistsSync(projectConfigPath)) {
561
+ fs.unlinkSync(projectConfigPath);
562
+ console.log('✅ Project configuration removed');
563
+ }
564
+
565
+ // Setup tracking file
566
+ const setupFile = path.join(settingsDir, 'setup.json');
567
+ if (SecurityUtils.safeExistsSync(setupFile)) {
568
+ fs.unlinkSync(setupFile);
569
+ console.log('✅ Setup tracking cleared');
570
+ }
571
+
572
+ // 3. Clear all backup files from backups directory
573
+ const backupsDir = path.join(packageDir, 'backups');
574
+ if (SecurityUtils.safeExistsSync(backupsDir)) {
575
+ const backupFiles = fs.readdirSync(backupsDir);
576
+ for (const file of backupFiles) {
577
+ if (file.endsWith('.json') || file.endsWith('.bak')) {
578
+ fs.unlinkSync(path.join(backupsDir, file));
579
+ }
580
+ }
581
+ console.log('✅ All backup files cleared');
582
+ }
583
+
584
+ // 4. Clear admin PIN configuration (multiple possible locations including root)
585
+ const adminConfigPaths = [
586
+ path.join(packageDir, '.i18n-admin-config.json'),
587
+ path.join(settingsDir, '.i18n-admin-config.json'),
588
+ path.join(settingsDir, 'admin-config.json'),
589
+ path.join(packageDir, '..', '.i18n-admin-config.json') // Root level
590
+ ];
591
+
592
+ for (const adminConfigPath of adminConfigPaths) {
593
+ if (SecurityUtils.safeExistsSync(adminConfigPath)) {
594
+ fs.unlinkSync(adminConfigPath);
595
+ console.log('✅ Admin PIN configuration cleared');
596
+ }
597
+ }
598
+
599
+ // 5. Clear initialization tracking
600
+ const initFiles = [
601
+ path.join(settingsDir, 'initialization.json'),
602
+ path.join(settingsDir, 'init.json')
603
+ ];
604
+
605
+ for (const initFile of initFiles) {
606
+ if (SecurityUtils.safeExistsSync(initFile)) {
607
+ fs.unlinkSync(initFile);
608
+ console.log('✅ Initialization tracking cleared');
609
+ }
610
+ }
611
+
612
+ // 6. Clear any cached data
613
+ const cacheDirs = [
614
+ path.join(packageDir, '.cache'),
615
+ path.join(settingsDir, '.cache')
616
+ ];
617
+
618
+ for (const cacheDir of cacheDirs) {
619
+ if (SecurityUtils.safeExistsSync(cacheDir)) {
620
+ const cacheFiles = fs.readdirSync(cacheDir);
621
+ for (const file of cacheFiles) {
622
+ fs.unlinkSync(path.join(cacheDir, file));
623
+ }
624
+ console.log('✅ Cache cleared');
625
+ }
626
+ }
627
+
628
+ // 7. Clear temporary files across directories
629
+ const tempFiles = [
630
+ path.join(settingsDir, '.temp-config.json'),
631
+ path.join(settingsDir, '.last-config.json'),
632
+ path.join(settingsDir, '.lock'),
633
+ path.join(settingsDir, 'i18ntk-config.json.tmp'),
634
+ path.join(settingsDir, 'settings.lock'),
635
+ path.join(packageDir, '.env-config.json'),
636
+ path.join(packageDir, '.temp-config.json'),
637
+ path.join(packageDir, 'config.tmp'),
638
+ path.join(packageDir, '.lock')
639
+ ];
640
+
641
+ for (const tempFile of tempFiles) {
642
+ if (SecurityUtils.safeExistsSync(tempFile)) {
643
+ fs.unlinkSync(tempFile);
644
+ }
645
+ }
646
+ console.log('✅ Temporary files cleared');
647
+
648
+ // 8. Clear any additional user data files
649
+ const additionalFiles = [
650
+ path.join(settingsDir, 'user-preferences.json'),
651
+ path.join(settingsDir, 'custom-config.json'),
652
+ path.join(settingsDir, 'local-config.json'),
653
+ path.join(packageDir, 'user-preferences.json'),
654
+ path.join(packageDir, 'custom-config.json'),
655
+ path.join(packageDir, 'local-config.json')
656
+ ];
657
+
658
+ for (const file of additionalFiles) {
659
+ if (SecurityUtils.safeExistsSync(file)) {
660
+ fs.unlinkSync(file);
661
+ console.log(`✅ Removed ${path.basename(file)}`);
662
+ }
663
+ }
664
+
665
+ // 9. Reset all script directory overrides to defaults
666
+ this.settings.scriptDirectories = {
667
+ "main": "./main",
668
+ "utils": "./utils",
669
+ "scripts": "./scripts",
670
+ "settings": "./settings",
671
+ "uiLocales": "./ui-locales"
672
+ };
673
+
674
+ // 10. Reset setup section to ensure clean state
675
+ this.settings.setup = {
676
+ "completed": false,
677
+ "completedAt": null,
678
+ "version": null,
679
+ "setupId": null
680
+ };
681
+
682
+ // 11. Save fresh configuration to the correct location
683
+ this.saveSettings();
684
+ console.log('✅ Fresh configuration saved');
685
+
686
+ return this.settings;
687
+
688
+ } catch (error) {
689
+ throw new Error(`Complete reset failed: ${error.message}`);
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Get current settings
695
+ * @returns {object} Current settings
696
+ */
697
+ getSettings() {
698
+ return this.settings;
699
+ }
700
+
701
+ /**
702
+ * Get default settings
703
+ * @returns {object} Default settings
704
+ */
705
+ getDefaultSettings() {
706
+ return this.defaultConfig;
707
+ }
708
+
709
+ /**
710
+ * Get settings schema structure
711
+ * @returns {object} Simple schema based on default configuration
712
+ */
713
+ getSettingsSchema() {
714
+ return { properties: this.defaultConfig };
715
+ }
716
+
717
+ /**
718
+ * Get enhanced settings schema with validation rules
719
+ * @returns {object} Enhanced schema with validation rules and descriptions
720
+ */
721
+ getEnhancedSettingsSchema() {
722
+ return {
723
+ type: 'object',
724
+ properties: {
725
+ version: {
726
+ type: 'string',
727
+ description: 'Configuration version',
728
+ default: '1.10.1',
729
+ readOnly: true
730
+ },
731
+ language: {
732
+ type: 'string',
733
+ description: 'Default language for translations',
734
+ enum: ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'],
735
+ default: 'en'
736
+ },
737
+ uiLanguage: {
738
+ type: 'string',
739
+ description: 'UI language for toolkit interface',
740
+ enum: ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'],
741
+ default: 'en'
742
+ },
743
+ theme: {
744
+ type: 'string',
745
+ description: 'UI theme preference',
746
+ enum: ['dark', 'light', 'auto'],
747
+ default: 'dark'
748
+ },
749
+ projectRoot: {
750
+ type: 'string',
751
+ description: 'Root directory of the project',
752
+ default: process.cwd()
753
+ },
754
+ sourceDir: {
755
+ type: 'string',
756
+ description: 'Directory containing translation files',
757
+ default: './locales'
758
+ },
759
+ i18nDir: {
760
+ type: 'string',
761
+ description: 'Directory for i18n configuration files',
762
+ default: './i18n'
763
+ },
764
+ outputDir: {
765
+ type: 'string',
766
+ description: 'Directory for generated reports',
767
+ default: './i18ntk-reports'
768
+ },
769
+ framework: {
770
+ type: 'object',
771
+ description: 'Framework preference and detection settings',
772
+ properties: {
773
+ preference: {
774
+ type: 'string',
775
+ description: 'Preferred framework to use. auto tries to detect.',
776
+ enum: ['auto', 'vanilla', 'react', 'vue', 'angular', 'svelte', 'i18next', 'nuxt', 'next'],
777
+ default: 'auto'
778
+ },
779
+ fallback: {
780
+ type: 'string',
781
+ description: 'Framework to use when detection finds nothing',
782
+ enum: ['vanilla', 'react', 'vue', 'angular', 'svelte', 'i18next', 'nuxt', 'next'],
783
+ default: 'vanilla'
784
+ },
785
+ detect: {
786
+ type: 'boolean',
787
+ description: 'Enable automatic framework detection',
788
+ default: true
789
+ },
790
+ supported: {
791
+ type: 'array',
792
+ description: 'List of supported frameworks for hints/UI',
793
+ items: { type: 'string' },
794
+ default: ['react', 'vue', 'angular', 'svelte', 'i18next', 'nuxt', 'next', 'vanilla']
795
+ }
796
+ }
797
+ },
798
+ processing: {
799
+ type: 'object',
800
+ properties: {
801
+ mode: {
802
+ type: 'string',
803
+ description: 'Processing performance mode',
804
+ enum: ['ultra-extreme', 'extreme', 'ultra', 'optimized'],
805
+ default: 'extreme'
806
+ },
807
+ cacheEnabled: {
808
+ type: 'boolean',
809
+ description: 'Enable caching for better performance',
810
+ default: true
811
+ },
812
+ batchSize: {
813
+ type: 'number',
814
+ description: 'Number of items to process in each batch',
815
+ minimum: 100,
816
+ maximum: 10000,
817
+ default: 1000
818
+ },
819
+ maxWorkers: {
820
+ type: 'number',
821
+ description: 'Maximum number of worker processes',
822
+ minimum: 1,
823
+ maximum: 16,
824
+ default: 4
825
+ },
826
+ timeout: {
827
+ type: 'number',
828
+ description: 'Timeout for processing operations in milliseconds',
829
+ minimum: 1000,
830
+ maximum: 300000,
831
+ default: 30000
832
+ },
833
+ retryAttempts: {
834
+ type: 'number',
835
+ description: 'Number of retry attempts for failed operations',
836
+ minimum: 0,
837
+ maximum: 10,
838
+ default: 3
839
+ },
840
+ parallelProcessing: {
841
+ type: 'boolean',
842
+ description: 'Enable parallel processing for better performance',
843
+ default: true
844
+ },
845
+ memoryOptimization: {
846
+ type: 'boolean',
847
+ description: 'Enable memory optimization for large datasets',
848
+ default: true
849
+ },
850
+ compression: {
851
+ type: 'boolean',
852
+ description: 'Enable compression for reports and backups',
853
+ default: true
854
+ }
855
+ }
856
+ },
857
+ security: {
858
+ type: 'object',
859
+ properties: {
860
+ enabled: {
861
+ type: 'boolean',
862
+ description: 'Enable security features',
863
+ default: true
864
+ },
865
+ adminPinEnabled: {
866
+ type: 'boolean',
867
+ description: 'Enable admin PIN protection',
868
+ default: false
869
+ },
870
+ sessionTimeout: {
871
+ type: 'number',
872
+ description: 'Session timeout in milliseconds',
873
+ minimum: 60000,
874
+ maximum: 3600000,
875
+ default: 1800000
876
+ },
877
+ maxFailedAttempts: {
878
+ type: 'number',
879
+ description: 'Maximum failed login attempts',
880
+ minimum: 1,
881
+ maximum: 10,
882
+ default: 3
883
+ },
884
+ sanitizeInput: {
885
+ type: 'boolean',
886
+ description: 'Enable input sanitization',
887
+ default: true
888
+ },
889
+ validatePaths: {
890
+ type: 'boolean',
891
+ description: 'Enable path validation',
892
+ default: true
893
+ }
894
+ }
895
+ }
896
+ }
897
+ };
898
+ }
899
+
900
+ /**
901
+ * Update specific setting
902
+ * @param {string} key - Setting key (dot notation supported)
903
+ * @param {*} value - New value
904
+ * @param {boolean} immediateSave - Whether to save immediately (default: true)
905
+ */
906
+ updateSetting(key, value, immediateSave = true) {
907
+ const keys = key.split('.');
908
+ let current = this.settings;
909
+
910
+ for (let i = 0; i < keys.length - 1; i++) {
911
+ if (!current[keys[i]]) {
912
+ current[keys[i]] = {};
913
+ }
914
+ current = current[keys[i]];
915
+ }
916
+
917
+ current[keys[keys.length - 1]] = value;
918
+ if (immediateSave) {
919
+ this.saveSettings();
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Update multiple settings at once
925
+ * @param {Object} settings - Object with key-value pairs to update
926
+ */
927
+ updateSettings(settings) {
928
+ for (const [key, value] of Object.entries(settings)) {
929
+ const keys = key.split('.');
930
+ let current = this.settings;
931
+
932
+ for (let i = 0; i < keys.length - 1; i++) {
933
+ if (!current[keys[i]]) {
934
+ current[keys[i]] = {};
935
+ }
936
+ current = current[keys[i]];
937
+ }
938
+
939
+ current[keys[keys.length - 1]] = value;
940
+ }
941
+
942
+ // Save once after all updates (use immediate save for batch operations)
943
+ this.saveSettingsImmediately();
944
+ }
945
+
946
+ /**
947
+ * Get specific setting
948
+ * @param {string} key - Setting key (dot notation supported)
949
+ * @param {*} defaultValue - Default value if key doesn't exist
950
+ * @returns {*} Setting value
951
+ */
952
+ getSetting(key, defaultValue = undefined) {
953
+ const keys = key.split('.');
954
+ let current = this.settings;
955
+
956
+ for (const k of keys) {
957
+ if (current && typeof current === 'object' && k in current) {
958
+ current = current[k];
959
+ } else {
960
+ return defaultValue;
961
+ }
962
+ }
963
+
964
+ return current;
965
+ }
966
+
967
+ /**
968
+ * Get available languages
969
+ * @returns {Array} Array of language objects with code and name
970
+ */
971
+ getAvailableLanguages() {
972
+ return [
973
+ { code: 'en', name: 'English' },
974
+ { code: 'de', name: 'Deutsch' },
975
+ { code: 'es', name: 'Español' },
976
+ { code: 'fr', name: 'Français' },
977
+ { code: 'ru', name: 'Русский' },
978
+ { code: 'ja', name: '日本語' },
979
+ { code: 'zh', name: '中文' }
980
+ ];
981
+ }
982
+ }
983
+
984
+ module.exports = SettingsManager;
6
985
 
7
- class SettingsManager {
8
- constructor() {
9
- // Use centralized .i18ntk-settings file as single source of truth
10
- this.configDir = path.resolve(__dirname, '..');
11
- this.configFile = path.join(process.cwd(), '.i18ntk-settings');
12
- this.backupDir = path.join(process.cwd(), 'backups');
13
- this.saveTimeout = null;
14
-
15
- this.defaultConfig = {
16
- "version": "1.10.1",
17
- "language": "en",
18
- "uiLanguage": "en",
19
- "theme": "dark",
20
- "projectRoot": process.cwd(),
21
- "sourceDir": "./locales",
22
- "i18nDir": "./i18n",
23
- "outputDir": "./i18ntk-reports",
24
- "setup": {
25
- "completed": false,
26
- "completedAt": null,
27
- "version": null,
28
- "setupId": null
29
- },
30
- "framework": {
31
- "preference": "auto", // auto | vanilla | react | vue | angular | svelte | i18next | nuxt | next
32
- "fallback": "vanilla",
33
- "detect": true,
34
- "supported": ["react", "vue", "angular", "svelte", "i18next", "nuxt", "next", "vanilla"]
35
- },
36
- "scriptDirectories": {
37
- "main": "./main",
38
- "utils": "./utils",
39
- "scripts": "./scripts",
40
- "settings": "./settings",
41
- "uiLocales": "./ui-locales"
42
- },
43
- "processing": {
44
- "mode": "extreme",
45
- "cacheEnabled": true,
46
- "batchSize": 1000,
47
- "maxWorkers": 4,
48
- "timeout": 30000,
49
- "retryAttempts": 3,
50
- "parallelProcessing": true,
51
- "memoryOptimization": true,
52
- "compression": true
53
- },
54
- "reports": {
55
- "format": "json",
56
- "includeSource": false,
57
- "includeStats": true,
58
- "includeRecommendations": true,
59
- "includeSecurity": true,
60
- "includePerformance": true,
61
- "saveToFile": true,
62
- "fileName": "i18n-report-[timestamp].json",
63
- "outputPath": "./i18ntk-reports",
64
- "compress": true
65
- },
66
- "ui": {
67
- "showProgress": true,
68
- "showColors": true,
69
- "showTimestamps": true,
70
- "showTips": true,
71
- "showWarnings": true,
72
- "showErrors": true,
73
- "interactive": true,
74
- "confirmActions": true,
75
- "autoComplete": true,
76
- "syntaxHighlighting": true
77
- },
78
- "behavior": {
79
- "autoSave": true,
80
- "autoBackup": true,
81
- "backupFrequency": "weekly",
82
- "maxBackups": 10,
83
- "confirmDestructive": true,
84
- "validateOnSave": true,
85
- "formatOnSave": true,
86
- "lintOnSave": true,
87
- "autoFix": false,
88
- "strictMode": false,
89
- "devMode": false
90
- },
91
- "notifications": {
92
- "enabled": true,
93
- "desktop": true,
94
- "sound": false,
95
- "types": {
96
- "success": true,
97
- "warning": true,
98
- "error": true,
99
- "info": true,
100
- "debug": false
101
- },
102
- "timeout": 5000,
103
- "maxNotifications": 5
104
- },
105
- "dateTime": {
106
- "timezone": "auto",
107
- "format": "YYYY-MM-DD HH:mm:ss",
108
- "locale": "en-US",
109
- "dateFormat": "YYYY-MM-DD",
110
- "timeFormat": "HH:mm:ss",
111
- "use24Hour": true,
112
- "showTimezone": false
113
- },
114
- "advanced": {
115
- "debugMode": false,
116
- "verboseLogging": false,
117
- "performanceTracking": true,
118
- "memoryProfiling": false,
119
- "stackTraces": false,
120
- "experimentalFeatures": false,
121
- "customExtractors": [],
122
- "customValidators": [],
123
- "customFormatters": []
124
- },
125
- "backup": {
126
- "enabled": true,
127
- "location": "./backups",
128
- "frequency": "daily",
129
- "retention": 7,
130
- "compression": true,
131
- "encryption": true,
132
- "autoCleanup": true,
133
- "maxSize": "100MB",
134
- "includeReports": true,
135
- "includeLogs": true
136
- },
137
- "security": {
138
- "enabled": true,
139
- "adminPinEnabled": false,
140
- "sessionTimeout": 1800000,
141
- "maxFailedAttempts": 3,
142
- "lockoutDuration": 300000,
143
- "encryption": {
144
- "enabled": true,
145
- "algorithm": "aes-256-gcm",
146
- "keyDerivation": "pbkdf2",
147
- "iterations": 100000
148
- },
149
- "pinProtection": {
150
- "enabled": false,
151
- "pin": null,
152
- "protectedScripts": {
153
- "init": false,
154
- "analyze": false,
155
- "validate": false,
156
- "fix": false,
157
- "manage": true,
158
- "settings": true,
159
- "admin": true
160
- }
161
- },
162
- "auditLog": true,
163
- "sanitizeInput": true,
164
- "validatePaths": true,
165
- "restrictAccess": false
166
- },
167
- "debug": {
168
- "enabled": false,
169
- "logLevel": "info",
170
- "logFile": "./i18ntk-debug.log",
171
- "maxFileSize": "10MB",
172
- "maxFiles": 5,
173
- "includeStackTrace": false,
174
- "includeMemoryUsage": false,
175
- "performanceMetrics": false
176
- },
177
- "sizeLimit": null,
178
- "placeholderStyles": {
179
- "en": [
180
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
181
- "%\\\\{[^}]+\\\\}",
182
- "%[sdif]",
183
- "\\\\$\\\\{[^}]+\\\\}",
184
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
185
- "__\\\\w+__",
186
- "\\\\{\\\\w+\\\\}",
187
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
188
- "\\\\{\\\\{t\\\\s+['\"][^'\"]*['\"]\\\\}\\\\}",
189
- "t\\\\(['\"][^'\"]*['\"]",
190
- "i18n\\\\.t\\\\(['\"][^'\"]*['\"]"
191
- ],
192
- "de": [
193
- "%\\\\{[^}]+\\\\}",
194
- "%[sdif]",
195
- "\\\\$\\\\{[^}]+\\\\}",
196
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
197
- "__\\\\w+__",
198
- "\\\\{\\\\w+\\\\}",
199
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
200
- "\\\\{\\\\{[^}]+\\\\}\\\\}"
201
- ],
202
- "es": [
203
- "%\\\\{[^}]+\\\\}",
204
- "%[sdif]",
205
- "\\\\$\\\\{[^}]+\\\\}",
206
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
207
- "__\\\\w+__",
208
- "\\\\{\\\\w+\\\\}",
209
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
210
- "\\\\{\\\\{[^}]+\\\\}\\\\}"
211
- ],
212
- "fr": [
213
- "%\\\\{[^}]+\\\\}",
214
- "%[sdif]",
215
- "\\\\$\\\\{[^}]+\\\\}",
216
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
217
- "__\\\\w+__",
218
- "\\\\{\\\\w+\\\\}",
219
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
220
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
221
- "\\\\{\\\\d+\\\\}"
222
- ],
223
- "ru": [
224
- "%\\\\{[^}]+\\\\}",
225
- "%[sdif]",
226
- "\\\\$\\\\{[^}]+\\\\}",
227
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
228
- "__\\\\w+__",
229
- "\\\\{\\\\w+\\\\}",
230
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
231
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
232
- "\\\\{\\\\d+\\\\}"
233
- ],
234
- "zh": [
235
- "%\\\\{[^}]+\\\\}",
236
- "%[sdif]",
237
- "\\\\$\\\\{[^}]+\\\\}",
238
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
239
- "__\\\\w+__",
240
- "\\\\{\\\\w+\\\\}",
241
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
242
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
243
- "\\\\{\\\\d+\\\\}"
244
- ],
245
- "ja": [
246
- "%\\\\{[^}]+\\\\}",
247
- "%[sdif]",
248
- "\\\\$\\\\{[^}]+\\\\}",
249
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
250
- "__\\\\w+__",
251
- "\\\\{\\\\w+\\\\}",
252
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
253
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
254
- "\\\\{\\\\d+\\\\}"
255
- ],
256
- "universal": [
257
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
258
- "%\\\\{[^}]+\\\\}",
259
- "%[sdif]",
260
- "\\\\$\\\\{[^}]+\\\\}",
261
- "\\\\$[a-zA-Z_][a-zA-Z0-9_]*",
262
- "__\\\\w+__",
263
- "\\\\{\\\\w+\\\\}",
264
- "\\\\[\\\\[\\\\w+\\\\]\\\\]",
265
- "\\\\{\\\\d+\\\\}",
266
- "\\\\{\\\\d*\\\\}"
267
- ],
268
- "frameworks": {
269
- "react": [
270
- "\\\\{\\\\{[^}]+\\\\}\\\\}",
271
- "\\\\$\\\\{[^}]+\\\\}",
272
- "t\\\\(['\"][^'\"]*['\"]",
273
- "i18n\\\\.t\\\\(['\"][^'\"]*['\"]",
274
- "useTranslation\\\\s*\\\\([^)]*\\\\)",
275
- "<Trans[^>]*>.*?</Trans>"
276
- ],
277
- "vue": [
278
- "\\\\$t\\\\(['\"][^'\"]*['\"]",
279
- "\\\\$tc\\\\(['\"][^'\"]*['\"]",
280
- "\\\\{\\\\{\\\\$t\\\\([^)]+\\\\)\\\\}\\\\}",
281
- "v-t=['\"][^'\"]*['\"]"
282
- ],
283
- "angular": [
284
- "'[^']*'\\\\s*\\\\|\\\\s*translate",
285
- "\\\\{[^}]*'[^']*'\\\\s*\\\\|\\\\s*translate[^}]*\\\\}",
286
- "translate\\\\s*:\\s*['\"][^'\"]*['\"]"
287
- ],
288
- "nextjs": [
289
- "t\\\\(['\"][^'\"]*['\"]",
290
- "router\\\\.locale",
291
- "useTranslation\\\\s*\\\\([^)]*\\\\)",
292
- "getStaticProps\\\\s*\\\\([^)]*\\\\)",
293
- "getServerSideProps\\\\s*\\\\([^)]*\\\\)"
294
- ]
295
- }
296
- },
297
- "framework": {
298
- "detected": false,
299
- "preference": "none",
300
- "prompt": "always",
301
- "lastPromptedVersion": null
302
- },
303
- "setup": {
304
- "completed": false,
305
- "completedAt": null,
306
- "version": null,
307
- "setupId": null
308
- }
309
- };
310
-
311
- this.settings = this.loadSettings();
312
- }
313
-
314
- /**
315
- * Load settings from file or return default settings
316
- * @returns {object} Settings object
317
- */
318
- loadSettings() {
319
- try {
320
- if (SecurityUtils.safeExistsSync(this.configFile)) {
321
- const content = SecurityUtils.safeReadFileSync(this.configFile, process.cwd(), 'utf8');
322
- const loadedSettings = JSON.parse(content);
323
- // Merge with defaults to ensure all properties exist
324
- this.settings = this.mergeWithDefaults(loadedSettings);
325
- return this.settings;
326
- }
327
- } catch (error) {
328
- console.error('Error loading settings:', error.message);
329
- }
330
- this.settings = { ...this.defaultConfig };
331
- return this.settings;
332
- }
333
-
334
- /**
335
- * Merge loaded settings with defaults to ensure all properties exist
336
- * @param {object} loadedSettings - Settings loaded from file
337
- * @returns {object} Merged settings
338
- */
339
- mergeWithDefaults(loadedSettings) {
340
- const merged = { ...this.defaultConfig, ...loadedSettings };
341
-
342
- // Ensure nested objects are properly merged
343
- if (loadedSettings.notifications) {
344
- merged.notifications = {
345
- ...this.defaultConfig.notifications,
346
- ...loadedSettings.notifications,
347
- types: {
348
- ...this.defaultConfig.notifications.types,
349
- ...(loadedSettings.notifications.types || {})
350
- }
351
- };
352
- }
353
-
354
- if (loadedSettings.processing) {
355
- merged.processing = { ...this.defaultConfig.processing, ...loadedSettings.processing };
356
- }
357
-
358
- if (loadedSettings.advanced) {
359
- merged.advanced = { ...this.defaultConfig.advanced, ...loadedSettings.advanced };
360
- }
361
-
362
- if (loadedSettings.scriptDirectories) {
363
- merged.scriptDirectories = {
364
- ...this.defaultConfig.scriptDirectories,
365
- ...loadedSettings.scriptDirectories
366
- };
367
- }
368
-
369
- if (loadedSettings.security?.pinProtection) {
370
- merged.security.pinProtection = {
371
- ...this.defaultConfig.security.pinProtection,
372
- ...loadedSettings.security.pinProtection,
373
- protectedScripts: {
374
- ...this.defaultConfig.security.pinProtection.protectedScripts,
375
- ...(loadedSettings.security.pinProtection.protectedScripts || {})
376
- }
377
- };
378
- }
379
-
380
- if (loadedSettings.setup) {
381
- merged.setup = {
382
- ...this.defaultConfig.setup,
383
- ...loadedSettings.setup
384
- };
385
- }
386
-
387
- return merged;
388
- }
389
-
390
- /**
391
- * Save settings to file
392
- * @param {object} settings - Settings to save
393
- */
394
- saveSettings(settings = null) {
395
- if (settings) {
396
- this.settings = settings;
397
- }
398
-
399
- // Clear any pending save
400
- if (this.saveTimeout) {
401
- clearTimeout(this.saveTimeout);
402
- }
403
-
404
- // Debounce saves to prevent rapid successive saves
405
- this.saveTimeout = setTimeout(() => {
406
- this._saveImmediately();
407
- }, 100); // 100ms debounce
408
- }
409
-
410
- /**
411
- * Save settings immediately without debounce
412
- * @param {object} settings - Settings to save
413
- */
414
- saveSettingsImmediately(settings = null) {
415
- if (settings) {
416
- this.settings = settings;
417
- }
418
-
419
- // Clear any pending save
420
- if (this.saveTimeout) {
421
- clearTimeout(this.saveTimeout);
422
- this.saveTimeout = null;
423
- }
424
-
425
- this._saveImmediately();
426
- }
427
-
428
- /**
429
- * Internal method to save settings without debounce
430
- * @private
431
- */
432
- _saveImmediately() {
433
- try {
434
- if (!SecurityUtils.safeExistsSync(this.configDir)) {
435
- fs.mkdirSync(this.configDir, { recursive: true });
436
- }
437
-
438
- const content = JSON.stringify(this.settings, null, 4);
439
- SecurityUtils.safeWriteFileSync(this.configFile, content, path.dirname(this.configFile), 'utf8');
440
-
441
- // Create backup if enabled
442
- if (this.settings.backup?.enabled) {
443
- this.createBackup();
444
- }
445
-
446
- console.log('Settings saved successfully');
447
- } catch (error) {
448
- console.error('Error saving settings:', error.message);
449
- }
450
-
451
- this.saveTimeout = null;
452
- }
453
-
454
- /**
455
- * Create backup of current settings
456
- */
457
- createBackup() {
458
- try {
459
- if (!SecurityUtils.safeExistsSync(this.backupDir)) {
460
- fs.mkdirSync(this.backupDir, { recursive: true });
461
- }
462
-
463
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
464
- const backupFile = path.join(this.backupDir, `config-${timestamp}.json`);
465
-
466
- fs.copyFileSync(this.configFile, backupFile);
467
-
468
- // Clean old backups
469
- this.cleanupOldBackups();
470
- } catch (error) {
471
- console.error('Error creating backup:', error.message);
472
- }
473
- }
474
-
475
- /**
476
- * Clean old backup files
477
- */
478
- cleanupOldBackups() {
479
- try {
480
- const files = fs.readdirSync(this.backupDir)
481
- .filter(file => file.startsWith('config-') && file.endsWith('.json'))
482
- .map(file => ({
483
- name: file,
484
- path: path.join(this.backupDir, file),
485
- mtime: fs.statSync(path.join(this.backupDir, file)).mtime
486
- }))
487
- .sort((a, b) => b.mtime - a.mtime);
488
-
489
- const maxBackups = this.settings.backup?.maxBackups || 10;
490
- if (files.length > maxBackups) {
491
- files.slice(maxBackups).forEach(file => {
492
- fs.unlinkSync(file.path);
493
- });
494
- }
495
- } catch (error) {
496
- console.error('Error cleaning backups:', error.message);
497
- }
498
- }
499
-
500
- /**
501
- * Reset settings to defaults
502
- */
503
- resetToDefaults() {
504
- this.settings = { ...this.defaultConfig };
505
- // Ensure setup section is also reset
506
- this.settings.setup = {
507
- "completed": false,
508
- "completedAt": null,
509
- "version": null,
510
- "setupId": null
511
- };
512
- // Set projectRoot to "/" for fresh install state
513
- this.settings.projectRoot = "/";
514
- this.saveSettings();
515
- console.log('Settings reset to defaults');
516
- }
517
-
518
- /**
519
- * Complete reset - removes all configuration files and resets to fresh install state
520
- */
521
- async completeReset() {
522
- const fs = require('fs');
523
- const path = require('path');
524
-
525
- try {
526
- console.log('🧹 Performing complete reset...');
527
-
528
- // 1. Reset to defaults with projectRoot set to "/" for fresh install
529
- this.settings = { ...this.defaultConfig };
530
- this.settings.projectRoot = "/"; // Set to root for fresh install
531
-
532
- // 2. Remove actual configuration files used by the system
533
- const packageDir = path.resolve(__dirname, '..');
534
- const settingsDir = path.join(packageDir, 'settings');
535
-
536
- // Main configuration file
537
- const mainConfigPath = path.join(settingsDir, 'i18ntk-config.json');
538
- if (SecurityUtils.safeExistsSync(mainConfigPath)) {
539
- fs.unlinkSync(mainConfigPath);
540
- console.log('✅ Main configuration file removed');
541
- }
542
-
543
- // Project configuration file
544
- const projectConfigPath = path.join(settingsDir, 'project-config.json');
545
- if (SecurityUtils.safeExistsSync(projectConfigPath)) {
546
- fs.unlinkSync(projectConfigPath);
547
- console.log('✅ Project configuration removed');
548
- }
549
-
550
- // Setup tracking file
551
- const setupFile = path.join(settingsDir, 'setup.json');
552
- if (SecurityUtils.safeExistsSync(setupFile)) {
553
- fs.unlinkSync(setupFile);
554
- console.log('✅ Setup tracking cleared');
555
- }
556
-
557
- // 3. Clear all backup files from backups directory
558
- const backupsDir = path.join(packageDir, 'backups');
559
- if (SecurityUtils.safeExistsSync(backupsDir)) {
560
- const backupFiles = fs.readdirSync(backupsDir);
561
- for (const file of backupFiles) {
562
- if (file.endsWith('.json') || file.endsWith('.bak')) {
563
- fs.unlinkSync(path.join(backupsDir, file));
564
- }
565
- }
566
- console.log('✅ All backup files cleared');
567
- }
568
-
569
- // 4. Clear admin PIN configuration (multiple possible locations including root)
570
- const adminConfigPaths = [
571
- path.join(packageDir, '.i18n-admin-config.json'),
572
- path.join(settingsDir, '.i18n-admin-config.json'),
573
- path.join(settingsDir, 'admin-config.json'),
574
- path.join(packageDir, '..', '.i18n-admin-config.json') // Root level
575
- ];
576
-
577
- for (const adminConfigPath of adminConfigPaths) {
578
- if (SecurityUtils.safeExistsSync(adminConfigPath)) {
579
- fs.unlinkSync(adminConfigPath);
580
- console.log('✅ Admin PIN configuration cleared');
581
- }
582
- }
583
-
584
- // 5. Clear initialization tracking
585
- const initFiles = [
586
- path.join(settingsDir, 'initialization.json'),
587
- path.join(settingsDir, 'init.json')
588
- ];
589
-
590
- for (const initFile of initFiles) {
591
- if (SecurityUtils.safeExistsSync(initFile)) {
592
- fs.unlinkSync(initFile);
593
- console.log('✅ Initialization tracking cleared');
594
- }
595
- }
596
-
597
- // 6. Clear any cached data
598
- const cacheDirs = [
599
- path.join(packageDir, '.cache'),
600
- path.join(settingsDir, '.cache')
601
- ];
602
-
603
- for (const cacheDir of cacheDirs) {
604
- if (SecurityUtils.safeExistsSync(cacheDir)) {
605
- const cacheFiles = fs.readdirSync(cacheDir);
606
- for (const file of cacheFiles) {
607
- fs.unlinkSync(path.join(cacheDir, file));
608
- }
609
- console.log('✅ Cache cleared');
610
- }
611
- }
612
-
613
- // 7. Clear temporary files across directories
614
- const tempFiles = [
615
- path.join(settingsDir, '.temp-config.json'),
616
- path.join(settingsDir, '.last-config.json'),
617
- path.join(settingsDir, '.lock'),
618
- path.join(settingsDir, 'i18ntk-config.json.tmp'),
619
- path.join(settingsDir, 'settings.lock'),
620
- path.join(packageDir, '.env-config.json'),
621
- path.join(packageDir, '.temp-config.json'),
622
- path.join(packageDir, 'config.tmp'),
623
- path.join(packageDir, '.lock')
624
- ];
625
-
626
- for (const tempFile of tempFiles) {
627
- if (SecurityUtils.safeExistsSync(tempFile)) {
628
- fs.unlinkSync(tempFile);
629
- }
630
- }
631
- console.log('✅ Temporary files cleared');
632
-
633
- // 8. Clear any additional user data files
634
- const additionalFiles = [
635
- path.join(settingsDir, 'user-preferences.json'),
636
- path.join(settingsDir, 'custom-config.json'),
637
- path.join(settingsDir, 'local-config.json'),
638
- path.join(packageDir, 'user-preferences.json'),
639
- path.join(packageDir, 'custom-config.json'),
640
- path.join(packageDir, 'local-config.json')
641
- ];
642
-
643
- for (const file of additionalFiles) {
644
- if (SecurityUtils.safeExistsSync(file)) {
645
- fs.unlinkSync(file);
646
- console.log(`✅ Removed ${path.basename(file)}`);
647
- }
648
- }
649
-
650
- // 9. Reset all script directory overrides to defaults
651
- this.settings.scriptDirectories = {
652
- "main": "./main",
653
- "utils": "./utils",
654
- "scripts": "./scripts",
655
- "settings": "./settings",
656
- "uiLocales": "./ui-locales"
657
- };
658
-
659
- // 10. Reset setup section to ensure clean state
660
- this.settings.setup = {
661
- "completed": false,
662
- "completedAt": null,
663
- "version": null,
664
- "setupId": null
665
- };
666
-
667
- // 11. Save fresh configuration to the correct location
668
- this.saveSettings();
669
- console.log('✅ Fresh configuration saved');
670
-
671
- return this.settings;
672
-
673
- } catch (error) {
674
- throw new Error(`Complete reset failed: ${error.message}`);
675
- }
676
- }
677
-
678
- /**
679
- * Get current settings
680
- * @returns {object} Current settings
681
- */
682
- getSettings() {
683
- return this.settings;
684
- }
685
-
686
- /**
687
- * Get default settings
688
- * @returns {object} Default settings
689
- */
690
- getDefaultSettings() {
691
- return this.defaultConfig;
692
- }
693
-
694
- /**
695
- * Get settings schema structure
696
- * @returns {object} Simple schema based on default configuration
697
- */
698
- getSettingsSchema() {
699
- return { properties: this.defaultConfig };
700
- }
701
-
702
- /**
703
- * Get enhanced settings schema with validation rules
704
- * @returns {object} Enhanced schema with validation rules and descriptions
705
- */
706
- getEnhancedSettingsSchema() {
707
- return {
708
- type: 'object',
709
- properties: {
710
- version: {
711
- type: 'string',
712
- description: 'Configuration version',
713
- default: '1.10.1',
714
- readOnly: true
715
- },
716
- language: {
717
- type: 'string',
718
- description: 'Default language for translations',
719
- enum: ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'],
720
- default: 'en'
721
- },
722
- uiLanguage: {
723
- type: 'string',
724
- description: 'UI language for toolkit interface',
725
- enum: ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'],
726
- default: 'en'
727
- },
728
- theme: {
729
- type: 'string',
730
- description: 'UI theme preference',
731
- enum: ['dark', 'light', 'auto'],
732
- default: 'dark'
733
- },
734
- projectRoot: {
735
- type: 'string',
736
- description: 'Root directory of the project',
737
- default: process.cwd()
738
- },
739
- sourceDir: {
740
- type: 'string',
741
- description: 'Directory containing translation files',
742
- default: './locales'
743
- },
744
- i18nDir: {
745
- type: 'string',
746
- description: 'Directory for i18n configuration files',
747
- default: './i18n'
748
- },
749
- outputDir: {
750
- type: 'string',
751
- description: 'Directory for generated reports',
752
- default: './i18ntk-reports'
753
- },
754
- framework: {
755
- type: 'object',
756
- description: 'Framework preference and detection settings',
757
- properties: {
758
- preference: {
759
- type: 'string',
760
- description: 'Preferred framework to use. auto tries to detect.',
761
- enum: ['auto', 'vanilla', 'react', 'vue', 'angular', 'svelte', 'i18next', 'nuxt', 'next'],
762
- default: 'auto'
763
- },
764
- fallback: {
765
- type: 'string',
766
- description: 'Framework to use when detection finds nothing',
767
- enum: ['vanilla', 'react', 'vue', 'angular', 'svelte', 'i18next', 'nuxt', 'next'],
768
- default: 'vanilla'
769
- },
770
- detect: {
771
- type: 'boolean',
772
- description: 'Enable automatic framework detection',
773
- default: true
774
- },
775
- supported: {
776
- type: 'array',
777
- description: 'List of supported frameworks for hints/UI',
778
- items: { type: 'string' },
779
- default: ['react', 'vue', 'angular', 'svelte', 'i18next', 'nuxt', 'next', 'vanilla']
780
- }
781
- }
782
- },
783
- processing: {
784
- type: 'object',
785
- properties: {
786
- mode: {
787
- type: 'string',
788
- description: 'Processing performance mode',
789
- enum: ['ultra-extreme', 'extreme', 'ultra', 'optimized'],
790
- default: 'extreme'
791
- },
792
- cacheEnabled: {
793
- type: 'boolean',
794
- description: 'Enable caching for better performance',
795
- default: true
796
- },
797
- batchSize: {
798
- type: 'number',
799
- description: 'Number of items to process in each batch',
800
- minimum: 100,
801
- maximum: 10000,
802
- default: 1000
803
- },
804
- maxWorkers: {
805
- type: 'number',
806
- description: 'Maximum number of worker processes',
807
- minimum: 1,
808
- maximum: 16,
809
- default: 4
810
- },
811
- timeout: {
812
- type: 'number',
813
- description: 'Timeout for processing operations in milliseconds',
814
- minimum: 1000,
815
- maximum: 300000,
816
- default: 30000
817
- },
818
- retryAttempts: {
819
- type: 'number',
820
- description: 'Number of retry attempts for failed operations',
821
- minimum: 0,
822
- maximum: 10,
823
- default: 3
824
- },
825
- parallelProcessing: {
826
- type: 'boolean',
827
- description: 'Enable parallel processing for better performance',
828
- default: true
829
- },
830
- memoryOptimization: {
831
- type: 'boolean',
832
- description: 'Enable memory optimization for large datasets',
833
- default: true
834
- },
835
- compression: {
836
- type: 'boolean',
837
- description: 'Enable compression for reports and backups',
838
- default: true
839
- }
840
- }
841
- },
842
- security: {
843
- type: 'object',
844
- properties: {
845
- enabled: {
846
- type: 'boolean',
847
- description: 'Enable security features',
848
- default: true
849
- },
850
- adminPinEnabled: {
851
- type: 'boolean',
852
- description: 'Enable admin PIN protection',
853
- default: false
854
- },
855
- sessionTimeout: {
856
- type: 'number',
857
- description: 'Session timeout in milliseconds',
858
- minimum: 60000,
859
- maximum: 3600000,
860
- default: 1800000
861
- },
862
- maxFailedAttempts: {
863
- type: 'number',
864
- description: 'Maximum failed login attempts',
865
- minimum: 1,
866
- maximum: 10,
867
- default: 3
868
- },
869
- sanitizeInput: {
870
- type: 'boolean',
871
- description: 'Enable input sanitization',
872
- default: true
873
- },
874
- validatePaths: {
875
- type: 'boolean',
876
- description: 'Enable path validation',
877
- default: true
878
- }
879
- }
880
- }
881
- }
882
- };
883
- }
884
-
885
- /**
886
- * Update specific setting
887
- * @param {string} key - Setting key (dot notation supported)
888
- * @param {*} value - New value
889
- * @param {boolean} immediateSave - Whether to save immediately (default: true)
890
- */
891
- updateSetting(key, value, immediateSave = true) {
892
- const keys = key.split('.');
893
- let current = this.settings;
894
-
895
- for (let i = 0; i < keys.length - 1; i++) {
896
- if (!current[keys[i]]) {
897
- current[keys[i]] = {};
898
- }
899
- current = current[keys[i]];
900
- }
901
-
902
- current[keys[keys.length - 1]] = value;
903
- if (immediateSave) {
904
- this.saveSettings();
905
- }
906
- }
907
-
908
- /**
909
- * Update multiple settings at once
910
- * @param {Object} settings - Object with key-value pairs to update
911
- */
912
- updateSettings(settings) {
913
- for (const [key, value] of Object.entries(settings)) {
914
- const keys = key.split('.');
915
- let current = this.settings;
916
-
917
- for (let i = 0; i < keys.length - 1; i++) {
918
- if (!current[keys[i]]) {
919
- current[keys[i]] = {};
920
- }
921
- current = current[keys[i]];
922
- }
923
-
924
- current[keys[keys.length - 1]] = value;
925
- }
926
-
927
- // Save once after all updates (use immediate save for batch operations)
928
- this.saveSettingsImmediately();
929
- }
930
-
931
- /**
932
- * Get specific setting
933
- * @param {string} key - Setting key (dot notation supported)
934
- * @param {*} defaultValue - Default value if key doesn't exist
935
- * @returns {*} Setting value
936
- */
937
- getSetting(key, defaultValue = undefined) {
938
- const keys = key.split('.');
939
- let current = this.settings;
940
-
941
- for (const k of keys) {
942
- if (current && typeof current === 'object' && k in current) {
943
- current = current[k];
944
- } else {
945
- return defaultValue;
946
- }
947
- }
948
-
949
- return current;
950
- }
951
-
952
- /**
953
- * Get available languages
954
- * @returns {Array} Array of language objects with code and name
955
- */
956
- getAvailableLanguages() {
957
- return [
958
- { code: 'en', name: 'English' },
959
- { code: 'de', name: 'Deutsch' },
960
- { code: 'es', name: 'Español' },
961
- { code: 'fr', name: 'Français' },
962
- { code: 'ru', name: 'Русский' },
963
- { code: 'ja', name: '日本語' },
964
- { code: 'zh', name: '中文' }
965
- ];
966
- }
967
- }
968
-
969
- module.exports = SettingsManager;