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.
- package/README.md +83 -50
- package/main/i18ntk-backup-class.js +37 -41
- package/main/i18ntk-backup.js +28 -30
- package/main/i18ntk-doctor.js +7 -6
- package/main/i18ntk-init.js +44 -8
- package/main/i18ntk-sizing.js +7 -8
- package/main/i18ntk-usage.js +17 -5
- package/main/i18ntk-validate.js +72 -22
- package/main/manage/commands/AnalyzeCommand.js +12 -14
- package/main/manage/commands/CommandRouter.js +15 -12
- package/main/manage/commands/FixerCommand.js +92 -36
- package/main/manage/commands/ValidateCommand.js +78 -27
- package/main/manage/index.js +158 -148
- package/main/manage/managers/DebugMenu.js +6 -6
- package/main/manage/managers/InteractiveMenu.js +6 -6
- package/main/manage/managers/LanguageMenu.js +5 -4
- package/main/manage/managers/SettingsMenu.js +6 -6
- package/main/manage/services/AuthenticationService.js +5 -6
- package/main/manage/services/ConfigurationService.js +22 -34
- package/main/manage/services/FileManagementService.js +6 -6
- package/main/manage/services/InitService.js +44 -8
- package/main/manage/services/UsageService.js +17 -5
- package/package.json +6 -6
- package/settings/settings-cli.js +2 -2
- package/settings/settings-manager.js +984 -968
- package/utils/config-helper.js +27 -16
- package/utils/config-manager.js +8 -7
- package/utils/init-helper.js +3 -2
- package/utils/json-output.js +11 -10
- package/utils/logger.js +4 -4
- package/utils/safe-json.js +3 -3
- package/utils/secure-backup.js +8 -7
- package/utils/setup-enforcer.js +63 -98
|
@@ -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;
|