i18ntk 2.0.3 → 2.1.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.
@@ -124,106 +124,34 @@ class CliHelper {
124
124
  }
125
125
 
126
126
  // Utility: Show framework suggestion warning only once per process
127
- let frameworkWarningShown = false;
128
- function showFrameworkWarningOnce(ui) {
129
- if (frameworkWarningShown) return;
130
- frameworkWarningShown = true;
131
-
132
- // Try to use the proper translation system first
133
- let t;
134
- if (ui && typeof ui.t === 'function') {
135
- t = ui.t.bind(ui);
136
- } else {
137
- // Fallback to loading the UI i18n system properly
138
- try {
139
- const UIi18n = require('../main/i18ntk-ui');
140
- const fallbackUI = new UIi18n();
141
- fallbackUI.loadLanguage('en'); // Load English as fallback
142
- t = fallbackUI.t.bind(fallbackUI);
143
- } catch (error) {
144
- // Last resort: use locale files directly
145
- try {
146
- const path = require('path');
147
- const fs = require('fs');
148
- const localePath = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales', 'en.json');
149
- if (SecurityUtils.safeExistsSync(localePath)) {
150
- const translations = JSON.parse(SecurityUtils.safeReadFileSync(localePath, path.dirname(localePath), 'utf8'));
151
- t = (key) => {
152
- const keys = key.split('.');
153
- let result = translations;
154
- for (const k of keys) {
155
- result = result && result[k];
156
- }
157
- return result || key;
158
- };
159
- } else {
160
- throw new Error('Locale file not found');
161
- }
162
- } catch (fallbackError) {
163
- // Final fallback: load from current UI locale or English
164
- try {
165
- const path = require('path');
166
- const fs = require('fs');
167
-
168
- // Try to determine current language from settings
169
- const settingsManager = require('../settings/settings-manager');
170
- const settings = settingsManager.loadSettings();
171
- const currentLang = settings.uiLanguage || 'en';
172
-
173
- const localePath = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales', `${currentLang}.json`);
174
- if (SecurityUtils.safeExistsSync(localePath)) {
175
- const translations = JSON.parse(SecurityUtils.safeReadFileSync(localePath, path.dirname(localePath), 'utf8'));
176
- t = (key) => {
177
- const keys = key.split('.');
178
- let result = translations;
179
- for (const k of keys) {
180
- result = result && result[k];
181
- }
182
- return result || key;
183
- };
184
- } else {
185
- // Fallback to English
186
- const enLocalePath = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales', 'en.json');
187
- if (SecurityUtils.safeExistsSync(enLocalePath)) {
188
- const translations = JSON.parse(SecurityUtils.safeReadFileSync(enLocalePath, path.dirname(enLocalePath), 'utf8'));
189
- t = (key) => {
190
- const keys = key.split('.');
191
- let result = translations;
192
- for (const k of keys) {
193
- result = result && result[k];
194
- }
195
- return result || key;
196
- };
197
- } else {
198
- throw new Error('No locale files found');
199
- }
200
- }
201
- } catch (finalError) {
202
- // Absolute last resort: basic hardcoded fallback
203
- const messages = {
204
- 'init.suggestions.noFramework': 'No i18n framework detected. Consider using one of the following:',
205
- 'init.frameworks.react': ' - React i18next (react-i18next)',
206
- 'init.frameworks.vue': ' - Vue i18n (vue-i18next)',
207
- 'init.frameworks.i18next': ' - i18next (i18next)',
208
- 'init.frameworks.nuxt': ' - Nuxt i18n (@nuxtjs/i18next)',
209
- 'init.frameworks.svelte': ' - Svelte i18n (svelte-i18next)'
210
- };
211
- t = (key) => messages[key] || key;
212
- }
213
- }
214
- }
215
- }
216
-
217
- console.log(t('init.suggestions.noFramework'));
218
- console.log(t('init.frameworks.react'));
219
- console.log(t('init.frameworks.vue'));
220
- console.log(t('init.frameworks.i18next'));
221
- console.log(t('init.frameworks.nuxt'));
222
- console.log(t('init.frameworks.svelte'));
223
- }
127
+ let frameworkWarningShown = false;
128
+ function showFrameworkWarningOnce(ui) {
129
+ if (frameworkWarningShown) return;
130
+ frameworkWarningShown = true;
131
+
132
+ const translate = ui && typeof ui.t === 'function'
133
+ ? ui.t.bind(ui)
134
+ : require('./i18n-helper').t;
135
+
136
+ const fallback = {
137
+ noFramework: 'No i18n framework detected. Consider using one of the following:',
138
+ react: ' - React i18next (react-i18next)',
139
+ vue: ' - Vue i18n (vue-i18next)',
140
+ i18next: ' - i18next (i18next)',
141
+ nuxt: ' - Nuxt i18n (@nuxtjs/i18n)',
142
+ svelte: ' - Svelte i18n (svelte-i18n)'
143
+ };
144
+
145
+ console.log(translate('init.suggestions.noFramework') || fallback.noFramework);
146
+ console.log(translate('init.frameworks.react') || fallback.react);
147
+ console.log(translate('init.frameworks.vue') || fallback.vue);
148
+ console.log(translate('init.frameworks.i18next') || fallback.i18next);
149
+ console.log(translate('init.frameworks.nuxt') || fallback.nuxt);
150
+ console.log(translate('init.frameworks.svelte') || fallback.svelte);
151
+ }
224
152
 
225
153
  // Export a singleton instance and attach utility for backward compatibility
226
154
  const cliHelper = new CliHelper();
227
155
  cliHelper.showFrameworkWarningOnce = showFrameworkWarningOnce;
228
156
 
229
- module.exports = cliHelper;
157
+ module.exports = cliHelper;
@@ -1,21 +1,48 @@
1
- function extract(content, patterns = []) {
2
- const keys = new Set();
3
- if (!Array.isArray(patterns)) return [];
4
- if (content === null || content === undefined) return [];
1
+ function normalizeKeyCandidate(rawKey) {
2
+ if (rawKey === null || rawKey === undefined) return null;
3
+
4
+ let key = String(rawKey).trim();
5
+ if (!key) return null;
6
+
7
+ // Normalize dynamic template segments to wildcard form.
8
+ key = key.replace(/\$\{[^}]+\}/g, '*');
9
+
10
+ // Reject multiline keys and obvious code fragments.
11
+ if (/[\r\n\t]/.test(key)) return null;
12
+ if (/\s/.test(key)) return null;
13
+ if (/(=>|\|\||&&|function\b|return\b|includes\()/i.test(key)) return null;
14
+
15
+ // Allow typical i18n key character sets only.
16
+ if (!/^[A-Za-z0-9_.:*-]+$/.test(key)) return null;
17
+
18
+ // Reject malformed key shapes.
19
+ if (key.startsWith('.') || key.endsWith('.')) return null;
20
+ if (key.includes('..')) return null;
21
+
22
+ return key;
23
+ }
24
+
25
+ function extract(content, patterns = []) {
26
+ const keys = new Set();
27
+ if (!Array.isArray(patterns)) return [];
28
+ if (content === null || content === undefined) return [];
5
29
  const contentStr = String(content);
6
30
  for (const pattern of patterns) {
7
31
  try {
8
- let regex = pattern instanceof RegExp ? new RegExp(pattern.source, 'g') : new RegExp(pattern, 'g');
9
- let match;
10
- while ((match = regex.exec(contentStr)) !== null) {
11
- if (match[1]) keys.add(match[1]);
12
- if (regex.lastIndex === 0) break;
13
- }
14
- } catch (e) {
32
+ let regex = pattern instanceof RegExp ? new RegExp(pattern.source, 'g') : new RegExp(pattern, 'g');
33
+ let match;
34
+ while ((match = regex.exec(contentStr)) !== null) {
35
+ if (match[1]) {
36
+ const normalized = normalizeKeyCandidate(match[1]);
37
+ if (normalized) keys.add(normalized);
38
+ }
39
+ if (regex.lastIndex === 0) break;
40
+ }
41
+ } catch (e) {
15
42
  // skip invalid patterns
16
43
  }
17
44
  }
18
45
  return Array.from(keys);
19
46
  }
20
47
 
21
- module.exports = { extract };
48
+ module.exports = { extract };
@@ -1,38 +1,39 @@
1
1
  // utils/i18n-helper.js
2
- const path = require('path');
3
- const fs = require('fs');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
4
 
5
5
  // Lazy load SecurityUtils to prevent circular dependencies
6
6
  let securityUtils;
7
- function getSecurityUtils() {
8
- if (!securityUtils) {
9
- try {
10
- securityUtils = require('./security');
11
- } catch (error) {
12
- // Fallback: use basic fs operations if SecurityUtils is not available
13
- return {
14
- safeExistsSync: (path) => {
15
- try {
16
- return require('fs').existsSync(path);
17
- } catch {
18
- return false;
19
- }
20
- },
21
- safeWriteFileSync: (path, data, encoding) => {
22
- try {
23
- return require('fs').writeFileSync(path, data, encoding);
24
- } catch {
25
- return null;
26
- }
27
- },
28
- safeReadFileSync: (path, encoding) => {
29
- try {
30
- return require('fs').readFileSync(path, encoding);
31
- } catch {
32
- return null;
33
- }
34
- }
35
- };
7
+ function getSecurityUtils() {
8
+ if (!securityUtils) {
9
+ try {
10
+ securityUtils = require('./security');
11
+ } catch (error) {
12
+ // Fallback: use basic fs operations if SecurityUtils is not available
13
+ return {
14
+ safeExistsSync: (targetPath) => {
15
+ try {
16
+ require('fs').accessSync(targetPath);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ },
22
+ safeWriteFileSync: (targetPath, data, _basePath, encoding = 'utf8') => {
23
+ try {
24
+ return require('fs').writeFileSync(targetPath, data, encoding);
25
+ } catch {
26
+ return null;
27
+ }
28
+ },
29
+ safeReadFileSync: (targetPath, _basePath, encoding = 'utf8') => {
30
+ try {
31
+ return require('fs').readFileSync(targetPath, encoding);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ };
36
37
  }
37
38
  }
38
39
  return securityUtils;
@@ -49,54 +50,105 @@ function safeRequireConfig() {
49
50
  try { return require('./config-manager'); } catch { return null; }
50
51
  }
51
52
 
52
- function stripBOMAndComments(s) {
53
- if (s.charCodeAt(0) === 0xFEFF) s = s.slice(1);
54
- s = s.replace(/\/\*[\s\S]*?\*\//g, '');
55
- s = s.replace(/^\s*\/\/.*$/mg, '');
56
- return s;
57
- }
58
-
59
- function readJsonSafe(file) {
60
- const SecurityUtils = getSecurityUtils();
61
- const raw = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
62
- return JSON.parse(stripBOMAndComments(raw));
63
- }
53
+ function stripBOMAndComments(s) {
54
+ if (s.charCodeAt(0) === 0xFEFF) s = s.slice(1);
55
+ s = s.replace(/\/\*[\s\S]*?\*\//g, '');
56
+ s = s.replace(/^\s*\/\/.*$/mg, '');
57
+ return s;
58
+ }
59
+
60
+ function getValidationBase(targetPath) {
61
+ const fallbackBase = path.resolve(__dirname, '..');
62
+ if (!targetPath || typeof targetPath !== 'string') {
63
+ return fallbackBase;
64
+ }
65
+
66
+ let current = path.resolve(path.dirname(targetPath));
67
+ while (true) {
68
+ try {
69
+ if (fs.statSync(current).isDirectory()) {
70
+ return current;
71
+ }
72
+ } catch {
73
+ // Continue upward until we find an existing directory.
74
+ }
75
+
76
+ const parent = path.dirname(current);
77
+ if (parent === current) {
78
+ break;
79
+ }
80
+ current = parent;
81
+ }
82
+
83
+ return fallbackBase;
84
+ }
85
+
86
+ function safeReadFile(targetPath, encoding = 'utf8') {
87
+ const SecurityUtils = getSecurityUtils();
88
+ return SecurityUtils.safeReadFileSync(targetPath, getValidationBase(targetPath), encoding);
89
+ }
90
+
91
+ function readJsonSafe(file) {
92
+ const raw = safeReadFile(file, 'utf8');
93
+ if (raw === null) {
94
+ throw new Error(`Unable to read JSON file: ${file}`);
95
+ }
96
+ return JSON.parse(stripBOMAndComments(raw));
97
+ }
64
98
 
65
99
  function pkgUiLocalesDirViaThisFile() {
66
100
  return path.resolve(__dirname, '..', 'ui-locales');
67
101
  }
68
102
 
69
- function pkgUiLocalesDirViaResolve() {
70
- try {
71
- const enJson = require.resolve('i18ntk/ui-locales/en.json');
72
- return path.dirname(enJson);
73
- } catch { return null; }
74
- }
75
-
76
- function resolveLocalesDirs() {
77
- const dirs = [];
78
- const addDir = (dir) => {
79
- if (typeof dir === 'string' && dir.trim()) {
80
- try {
81
- const normalized = path.normalize(path.resolve(dir.trim()));
82
-
83
- const SecurityUtils = getSecurityUtils();
84
- if (SecurityUtils.safeExistsSync(normalized) && fs.statSync(normalized).isDirectory()) {
85
- dirs.push(normalized);
86
- }
87
- } catch {
88
- // Silently ignore invalid paths
89
- }
90
- }
91
- };
92
-
93
- const pkgA = pkgUiLocalesDirViaThisFile();
94
- addDir(pkgA);
95
-
96
- const pkgB = pkgUiLocalesDirViaResolve();
97
- if (pkgB && pkgB !== pkgA) {
98
- addDir(pkgB);
99
- }
103
+ function pkgUiLocalesDirViaResolve() {
104
+ try {
105
+ // Try the new correct path first (ui-locales/en.json)
106
+ const enJson = require.resolve('i18ntk/ui-locales/en.json');
107
+ return path.dirname(enJson);
108
+ } catch {
109
+ try {
110
+ // Fallback to the old incorrect path for backward compatibility
111
+ const enJson = require.resolve('i18ntk/resources/i18n/ui-locales/en.json');
112
+ return path.dirname(enJson);
113
+ } catch { return null; }
114
+ }
115
+ }
116
+
117
+ function legacyResourcesUiLocalesDir() {
118
+ return path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales');
119
+ }
120
+
121
+ function resolveLocalesDirs(preferredDir) {
122
+ const SecurityUtils = getSecurityUtils();
123
+ const dirs = [];
124
+ const addDir = (dir) => {
125
+ if (typeof dir === 'string' && dir.trim()) {
126
+ try {
127
+ const normalized = path.normalize(path.resolve(dir.trim()));
128
+ const stats = SecurityUtils.safeStatSync(normalized, getValidationBase(normalized));
129
+ if (stats && stats.isDirectory()) {
130
+ dirs.push(normalized);
131
+ }
132
+ } catch {
133
+ // Silently ignore invalid paths
134
+ }
135
+ }
136
+ };
137
+
138
+ addDir(preferredDir);
139
+
140
+ const pkgA = pkgUiLocalesDirViaThisFile();
141
+ addDir(pkgA);
142
+
143
+ const pkgB = pkgUiLocalesDirViaResolve();
144
+ if (pkgB && pkgB !== pkgA) {
145
+ addDir(pkgB);
146
+ }
147
+
148
+ const legacyDir = legacyResourcesUiLocalesDir();
149
+ if (legacyDir !== pkgA && legacyDir !== pkgB) {
150
+ addDir(legacyDir);
151
+ }
100
152
 
101
153
  // Deduplicate while preserving order
102
154
  const seen = new Set();
@@ -114,8 +166,9 @@ function candidatesForLang(dir, lang) {
114
166
  ];
115
167
  }
116
168
 
117
- function findLocaleFilesAllDirs(lang) {
118
- const dirs = resolveLocalesDirs();
169
+ function findLocaleFilesAllDirs(lang, preferredDir) {
170
+ const SecurityUtils = getSecurityUtils();
171
+ const dirs = resolveLocalesDirs(preferredDir);
119
172
 
120
173
  if (process.env.I18NTK_DEBUG_LOCALES === '1') {
121
174
  console.log('🔎 i18ntk locale search dirs:', dirs);
@@ -124,32 +177,29 @@ function findLocaleFilesAllDirs(lang) {
124
177
  const files = [];
125
178
  const errors = [];
126
179
 
127
- for (const dir of dirs) {
128
- for (const candidate of candidatesForLang(dir, lang)) {
129
- try {
130
- const SecurityUtils = getSecurityUtils();
131
- if (SecurityUtils.safeExistsSync(candidate)) {
132
- const stats = fs.statSync(candidate);
133
- if (stats.isFile() && stats.size > 0) {
134
- // Validate file is readable and parseable
135
- fs.accessSync(candidate, fs.constants.R_OK);
136
- // Quick JSON validation
137
- const content = SecurityUtils.safeReadFileSync(candidate, path.dirname(candidate), 'utf8');
138
- if (content) {
139
- if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
140
- files.push(candidate);
141
- } else {
142
- errors.push({ file: candidate, error: 'Invalid JSON format' });
143
- }
144
- } else {
145
- errors.push({ file: candidate, error: 'Empty or unreadable file' });
146
- }
147
- }
148
- }
149
- } catch (error) {
150
- errors.push({ file: candidate, error: error.message });
151
- }
152
- }
180
+ for (const dir of dirs) {
181
+ for (const candidate of candidatesForLang(dir, lang)) {
182
+ try {
183
+ const stats = SecurityUtils.safeStatSync(candidate, getValidationBase(candidate));
184
+ if (stats && stats.isFile() && stats.size > 0) {
185
+ // Validate file is readable and parseable
186
+ fs.accessSync(candidate, fs.constants.R_OK);
187
+ // Quick JSON validation
188
+ const content = safeReadFile(candidate, 'utf8');
189
+ if (content) {
190
+ if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
191
+ files.push(candidate);
192
+ } else {
193
+ errors.push({ file: candidate, error: 'Invalid JSON format' });
194
+ }
195
+ } else {
196
+ errors.push({ file: candidate, error: 'Empty or unreadable file' });
197
+ }
198
+ }
199
+ } catch (error) {
200
+ errors.push({ file: candidate, error: error.message });
201
+ }
202
+ }
153
203
  }
154
204
 
155
205
  if (process.env.I18NTK_DEBUG_LOCALES === '1' && errors.length > 0) {
@@ -165,19 +215,21 @@ let isInitialized = false;
165
215
  const missingWarned = new Set();
166
216
 
167
217
  function loadTranslations(language) {
168
- const cfg = safeRequireConfig();
169
- const settings = cfg?.getConfig?.() || {};
170
- const configuredLanguage = settings.uiLanguage || settings.language;
171
-
172
- // Prioritize CLI argument, then UI language, then language fallback
173
- const requested = (language || configuredLanguage || 'en').toString();
218
+ const cfg = safeRequireConfig();
219
+ const settings = cfg?.getConfig?.() || {};
220
+ const configuredLanguage = settings.uiLanguage || settings.language;
221
+
222
+ // Prioritize CLI argument, then UI language, then language fallback
223
+ const requested = (language || configuredLanguage || 'en').toString();
174
224
  const short = requested.split('-')[0].toLowerCase();
175
225
  const tryOrder = [requested, short, 'en'];
176
226
 
177
227
  const loadErrors = [];
178
228
 
179
- for (const lang of tryOrder) {
180
- const files = findLocaleFilesAllDirs(lang);
229
+ const preferredDir = arguments.length > 1 ? arguments[1] : null;
230
+
231
+ for (const lang of tryOrder) {
232
+ const files = findLocaleFilesAllDirs(lang, preferredDir);
181
233
 
182
234
  // Prioritize bundled locales over project ones
183
235
  const prioritizedFiles = files.sort((a, b) => Number(isBundledPath(b)) - Number(isBundledPath(a)));
@@ -350,19 +402,20 @@ function getCurrentLanguage() {
350
402
  function getAvailableLanguages() {
351
403
  const dirs = resolveLocalesDirs();
352
404
  const langs = new Set();
353
- for (const d of dirs) {
354
- try {
355
- const SecurityUtils = getSecurityUtils();
356
- if (!SecurityUtils.safeExistsSync(d)) continue;
357
- for (const f of fs.readdirSync(d)) {
358
- if (f.endsWith('.json')) langs.add(path.basename(f, '.json'));
359
- }
360
- for (const f of fs.readdirSync(d, { withFileTypes: true })) {
361
- if (f.isDirectory() && SecurityUtils.safeExistsSync(path.join(d, f.name, `${f.name}.json`))) {
362
- langs.add(f.name);
363
- }
364
- }
365
- } catch {}
405
+ for (const d of dirs) {
406
+ try {
407
+ const SecurityUtils = getSecurityUtils();
408
+ if (!SecurityUtils.safeExistsSync(d, getValidationBase(d))) continue;
409
+ for (const f of fs.readdirSync(d)) {
410
+ if (f.endsWith('.json')) langs.add(path.basename(f, '.json'));
411
+ }
412
+ for (const f of fs.readdirSync(d, { withFileTypes: true })) {
413
+ const nestedPath = path.join(d, f.name, `${f.name}.json`);
414
+ if (f.isDirectory() && SecurityUtils.safeExistsSync(nestedPath, getValidationBase(nestedPath))) {
415
+ langs.add(f.name);
416
+ }
417
+ }
418
+ } catch {}
366
419
  }
367
420
  return Array.from(langs.size ? langs : new Set(['en']));
368
421
  }
@@ -391,10 +444,10 @@ function deepMerge(target, source) {
391
444
  * Refresh language from settings manager
392
445
  * This ensures translations stay in sync with settings changes
393
446
  */
394
- function refreshLanguageFromSettings() {
395
- const cfg = safeRequireConfig();
396
- const settings = cfg?.getConfig?.() || {};
397
- const configuredLanguage = settings.uiLanguage || settings.language || 'en';
447
+ function refreshLanguageFromSettings() {
448
+ const cfg = safeRequireConfig();
449
+ const settings = cfg?.getConfig?.() || {};
450
+ const configuredLanguage = settings.uiLanguage || settings.language || 'en';
398
451
 
399
452
  if (configuredLanguage !== currentLanguage) {
400
453
  isInitialized = false;
@@ -417,4 +470,4 @@ module.exports = {
417
470
  deepMerge,
418
471
  refreshTranslations,
419
472
  refreshLanguageFromSettings
420
- };
473
+ };
@@ -125,14 +125,12 @@ class SecurityChecker {
125
125
  }
126
126
  }
127
127
 
128
- // Check for dangerous patterns (excluding the overly broad require pattern)
129
- const dangerousPatterns = [
130
- /fs\.readFileSync\s*\(/g,
131
- /fs\.writeFileSync\s*\(/g,
132
- /fs\.existsSync\s*\(/g,
133
- /eval\s*\(/g,
134
- /Function\s*\(/g
135
- ];
128
+ // Check for dangerous code execution patterns in SecurityUtils itself.
129
+ // Direct fs usage is expected here because this module provides vetted wrappers.
130
+ const dangerousPatterns = [
131
+ /eval\s*\(/g,
132
+ /Function\s*\(/g
133
+ ];
136
134
 
137
135
  for (const pattern of dangerousPatterns) {
138
136
  const matches = content.match(pattern);
@@ -251,11 +249,16 @@ class SecurityChecker {
251
249
  // No action needed for legitimate package requires
252
250
  }
253
251
 
254
- async checkFilePermissions() {
255
- this.log('Checking file permissions...');
256
-
257
- const criticalFiles = [
258
- 'utils/security.js',
252
+ async checkFilePermissions() {
253
+ this.log('Checking file permissions...');
254
+
255
+ // POSIX permission checks are noisy/non-actionable on Windows.
256
+ if (process.platform === 'win32') {
257
+ return;
258
+ }
259
+
260
+ const criticalFiles = [
261
+ 'utils/security.js',
259
262
  'tests/security.test.js',
260
263
  'package.json'
261
264
  ];
@@ -464,10 +464,12 @@ class SecurityUtils {
464
464
  'theme', 'ui', 'setup', 'reports', 'display', 'interface',
465
465
  // Security and settings
466
466
  'security', 'settings', 'preferences', 'config', 'configuration',
467
- // Additional common properties
468
- 'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
469
- 'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled'
470
- ]);
467
+ // Additional common properties
468
+ 'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
469
+ 'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled',
470
+ // Date and reporting options used by existing settings
471
+ 'dateFormat', 'timeFormat', 'timezone', 'reportLanguage', 'dateTime'
472
+ ]);
471
473
 
472
474
  // Remove unknown properties
473
475
  Object.keys(sanitized).forEach(key => {
package/utils/security.js CHANGED
@@ -531,10 +531,12 @@ class SecurityUtils {
531
531
  'theme', 'ui', 'setup', 'reports', 'display', 'interface',
532
532
  // Security and settings
533
533
  'security', 'settings', 'preferences', 'config', 'configuration',
534
- // Additional common properties
535
- 'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
536
- 'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled'
537
- ]);
534
+ // Additional common properties
535
+ 'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
536
+ 'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled',
537
+ // Date and reporting options used by existing settings
538
+ 'dateFormat', 'timeFormat', 'timezone', 'reportLanguage', 'dateTime'
539
+ ]);
538
540
 
539
541
  // Remove unknown properties
540
542
  Object.keys(sanitized).forEach(key => {