i18ntk 2.3.7 → 2.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "2.3.7",
3
+ "version": "2.4.0",
4
4
  "description": "🚀 The fastest internationalization toolkit with 97% performance boost! Zero-dependency, enterprise-grade internationalization for React, Vue, Angular, Python, Java, PHP & more. Features PIN protection, auto framework detection, 7+ UI languages, and comprehensive translation management. Perfect for startups to enterprises.",
5
5
  "keywords": [
6
6
  "i18n",
@@ -213,7 +213,7 @@
213
213
  "languages:select": "node settings/settings-cli.js",
214
214
  "languages:list": "node settings/settings-cli.js --list-languages",
215
215
  "languages:status": "node settings/settings-cli.js --language-status",
216
- "lint:locales": "node scripts/lint-locales.js"
216
+ "lint:locales": "node scripts/lint-locales.js"
217
217
  },
218
218
  "engines": {
219
219
  "node": ">=16.0.0",
@@ -224,26 +224,29 @@
224
224
  },
225
225
  "preferGlobal": true,
226
226
  "versionInfo": {
227
- "version": "2.3.7",
228
- "releaseDate": "12/04/2026",
229
- "lastUpdated": "12/04/2026",
227
+ "version": "2.4.0",
228
+ "releaseDate": "16/04/2026",
229
+ "lastUpdated": "16/04/2026",
230
230
  "maintainer": "Vlad Noskov",
231
231
  "changelog": "./CHANGELOG.md",
232
232
  "documentation": "./README.md",
233
233
  "apiReference": "./docs/api/API_REFERENCE.md",
234
234
  "majorChanges": [
235
+ "LOGGING: Introduced centralized structured logger with silent-by-default production behavior and DEBUG_MODE/JSON_LOG toggles.",
236
+ "SECURITY: Added internal path whitelist detection to prevent false-positive traversal warnings for package/project internals.",
237
+ "I18N: Added missing-key warning TTL cache to eliminate repeated translation-key spam during builds.",
235
238
  "HOTFIX: Removed deprecated package-path fallback that caused production build warnings for non-exported subpaths.",
236
239
  "CRITICAL FIX: Resolved sizing and usage-analysis regressions in v2 command flow.",
237
240
  "PACKAGING: Reduced publish footprint by removing internal development scripts and legacy fixed-file artifacts.",
238
241
  "SECURITY: Hardened release checks and added explicit support guidance to update from pre-2.3.5 versions.",
239
242
  "CONFIG: Added cross-process file locking for .i18ntk-config writes to prevent production rename races.",
240
243
  "CONFIG: Made autosave runtime-safe with non-throwing save failures and I18NTK_DISABLE_AUTOSAVE support.",
241
- "SECURITY: Hardened reset/backup path handling and made npm update checks explicit opt-in.",
242
- "CLI: Added npm registry version check with upgrade warning for out-of-date installs.",
244
+ "SECURITY: Hardened reset/backup path handling and disabled manager backup-route execution in current builds.",
245
+ "CLI: Removed npm registry startup update checks to eliminate outbound network calls in scanner-restricted environments.",
243
246
  "I18N: Completed internal UI locale parity and actionable untranslated-key cleanup across supported languages."
244
247
  ],
245
248
  "breakingChanges": [],
246
- "nextVersion": "2.4.0",
249
+ "nextVersion": "2.4.1",
247
250
  "supportedNodeVersions": ">=16.0.0",
248
251
  "supportedFrameworks": {
249
252
  "react-i18next": ">=11.0.0",
@@ -265,7 +268,7 @@
265
268
  "spring-boot": ">=2.5.0",
266
269
  "laravel": ">=8.0.0"
267
270
  },
268
- "supportPolicy": "Versions earlier than 2.3.7 may be unstable or insecure. Upgrade to 2.3.6 or newer."
271
+ "supportPolicy": "Versions earlier than 2.4.0 may be unstable or insecure. Upgrade to 2.4.0 or newer."
269
272
  },
270
273
  "_comment": "This package is zero-dependency and uses only native Node.js modules"
271
274
  }
@@ -1,8 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const os = require('os');
4
- const crypto = require('crypto');
5
- const SecurityUtils = require('./security');
3
+ const os = require('os');
4
+ const crypto = require('crypto');
5
+ const SecurityUtils = require('./security');
6
+ const { logger } = require('./logger');
6
7
 
7
8
  // Determine package directory and user project root
8
9
  const packageDir = path.resolve(__dirname, '..');
@@ -19,7 +20,55 @@ const CONFIG_LOCK_PATH = `${PROJECT_CONFIG_PATH}.lock`;
19
20
  const CONFIG_LOCK_TIMEOUT_MS = 5000;
20
21
  const CONFIG_LOCK_STALE_MS = 15000;
21
22
  const CONFIG_LOCK_RETRY_MS = 50;
22
- let autosaveDisabledWarned = false;
23
+ let autosaveDisabledWarned = false;
24
+ let defaultConfigNoticeShown = false;
25
+ let configFallbackNoticeShown = false;
26
+
27
+ function logInfo(message, details) {
28
+ logger.info(message, details);
29
+ }
30
+
31
+ function logWarn(message, details) {
32
+ logger.warn(message, details);
33
+ }
34
+
35
+ function logError(message, details) {
36
+ logger.error(message, details);
37
+ }
38
+
39
+ function notifyDefaultConfig(reason, error) {
40
+ if (defaultConfigNoticeShown) {
41
+ return;
42
+ }
43
+ defaultConfigNoticeShown = true;
44
+
45
+ logger.info('Using default configuration (reason: configuration error)', { reason });
46
+ if (logger.isDebugMode() && error) {
47
+ logger.debug(`Default configuration reason details: ${error.message}`, {
48
+ stack: error.stack
49
+ });
50
+ }
51
+ }
52
+
53
+ function notifyConfigFallback(error) {
54
+ if (!configFallbackNoticeShown) {
55
+ configFallbackNoticeShown = true;
56
+ logger.info('Using default configuration (reason: configuration error)', {
57
+ reason: 'configuration error'
58
+ });
59
+ }
60
+
61
+ logger.recordFirstError('config:load-fallback', {
62
+ message: error && error.message ? error.message : 'Unknown configuration error',
63
+ stack: error && error.stack ? error.stack : null
64
+ });
65
+
66
+ if (logger.isDebugMode() && error) {
67
+ logger.debug(`Configuration load fallback details: ${error.message}`, {
68
+ stack: error.stack
69
+ });
70
+ }
71
+ }
23
72
 
24
73
  // Setup tracking file
25
74
  const SETUP_COMPLETED_FILE = path.join(PROJECT_SETTINGS_DIR, 'setup.json');
@@ -381,7 +430,7 @@ function tryReadJson(filePath) {
381
430
 
382
431
  const data = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
383
432
  if (!data || data.trim() === '') {
384
- console.warn(`[i18ntk] Warning: Empty or invalid JSON file at ${filePath}`);
433
+ logWarn(`[i18ntk] Warning: Empty or invalid JSON file at ${filePath}`);
385
434
  return null;
386
435
  }
387
436
 
@@ -390,18 +439,18 @@ function tryReadJson(filePath) {
390
439
  return parsed;
391
440
  }
392
441
 
393
- console.error(`[i18ntk] Error parsing JSON from ${filePath}: Invalid JSON content`);
442
+ logError(`[i18ntk] Error parsing JSON from ${filePath}: Invalid JSON content`);
394
443
  // Create a backup of the corrupted file
395
444
  const backupPath = `${filePath}.corrupted-${Date.now()}.bak`;
396
445
  try {
397
446
  SecurityUtils.safeWriteFileSync(backupPath, data, path.dirname(backupPath), 'utf8');
398
- console.warn(`[i18ntk] Created backup of corrupted config at ${backupPath}`);
447
+ logWarn(`[i18ntk] Created backup of corrupted config at ${backupPath}`);
399
448
  } catch (backupError) {
400
- console.error(`[i18ntk] Failed to create backup of corrupted config: ${backupError.message}`);
449
+ logError(`[i18ntk] Failed to create backup of corrupted config: ${backupError.message}`);
401
450
  }
402
451
  return null;
403
452
  } catch (error) {
404
- console.error(`[i18ntk] Error reading config file at ${filePath}: ${error.message}`);
453
+ logError(`[i18ntk] Error reading config file at ${filePath}: ${error.message}`);
405
454
  return null;
406
455
  }
407
456
  }
@@ -420,11 +469,11 @@ async function migrateLegacyIfNeeded(baseCfg) {
420
469
  // Best-effort removal of legacy file to prevent future use
421
470
  try { fs.unlinkSync(LEGACY_CONFIG_PATH); } catch (_) {}
422
471
  // Deprecation notice
423
- console.warn('[i18ntk] Deprecated config location detected (~/.i18ntk). Configuration was migrated to project .i18ntk-config.');
472
+ logWarn('[i18ntk] Deprecated config location detected (~/.i18ntk). Configuration was migrated to project .i18ntk-config.');
424
473
  return merged;
425
474
  } catch (_) {
426
475
  // If write fails, fall back to in-memory config without deleting legacy
427
- console.warn('[i18ntk] Deprecated config location detected (~/.i18ntk). Using migrated settings in memory; failed to persist to .i18ntk-config.');
476
+ logWarn('[i18ntk] Deprecated config location detected (~/.i18ntk). Using migrated settings in memory; failed to persist to .i18ntk-config.');
428
477
  return merged;
429
478
  }
430
479
  }
@@ -445,7 +494,7 @@ function loadConfig() {
445
494
 
446
495
  // Prevent concurrent loading
447
496
  if (configLoadInProgress) {
448
- console.warn('[i18ntk] Configuration loading already in progress, returning defaults');
497
+ logWarn('[i18ntk] Configuration loading already in progress, returning defaults');
449
498
  resetRecursionGuard();
450
499
  return clone(DEFAULT_CONFIG);
451
500
  }
@@ -472,7 +521,7 @@ function loadConfig() {
472
521
  // Attempt to migrate to project settings
473
522
  // Ignore migration errors; we still return merged cfg in memory
474
523
  // eslint-disable-next-line no-unused-vars
475
- console.warn('[i18ntk] Detected legacy config at ~/.i18ntk. Migrating to project settings directory...');
524
+ logWarn('[i18ntk] Detected legacy config at ~/.i18ntk. Migrating to project settings directory...');
476
525
  const _ = (async () => { await migrateLegacyIfNeeded(DEFAULT_CONFIG); })();
477
526
  }
478
527
  }
@@ -481,7 +530,8 @@ function loadConfig() {
481
530
  currentConfig = cfg;
482
531
  return currentConfig;
483
532
  } catch (error) {
484
- console.error('[i18ntk] Error in loadConfig:', error.message);
533
+ logError('[i18ntk] Error in loadConfig', { error: error.message });
534
+ notifyConfigFallback(error);
485
535
  currentConfig = clone(DEFAULT_CONFIG);
486
536
  return currentConfig;
487
537
  } finally {
@@ -498,7 +548,7 @@ async function saveConfig(cfg = currentConfig) {
498
548
  currentConfig = cfg;
499
549
  if (!autosaveDisabledWarned) {
500
550
  autosaveDisabledWarned = true;
501
- console.warn('[i18ntk] Autosave disabled by I18NTK_DISABLE_AUTOSAVE. Keeping configuration in memory only.');
551
+ logWarn('[i18ntk] Autosave disabled by I18NTK_DISABLE_AUTOSAVE. Keeping configuration in memory only.');
502
552
  }
503
553
  return false;
504
554
  }
@@ -540,7 +590,7 @@ async function saveConfig(cfg = currentConfig) {
540
590
  currentConfig = cfg;
541
591
  return true;
542
592
  } catch (error) {
543
- console.error('[i18ntk] Error saving configuration:', error.message);
593
+ logError('[i18ntk] Error saving configuration', { error: error.message });
544
594
  return false;
545
595
  } finally {
546
596
  if (releaseLock) {
@@ -593,7 +643,7 @@ function getConfig() {
593
643
 
594
644
  // Check for legacy config for migration
595
645
  if (SecurityUtils.safeExistsSync(LEGACY_CONFIG_PATH)) {
596
- console.log('📦 Migrating legacy configuration...');
646
+ logInfo('Migrating legacy configuration');
597
647
  const legacyRaw = SecurityUtils.safeReadFileSync(LEGACY_CONFIG_PATH, path.dirname(LEGACY_CONFIG_PATH), 'utf8');
598
648
  const legacyConfig = SecurityUtils.safeParseJSON(legacyRaw);
599
649
  if (!legacyConfig || typeof legacyConfig !== 'object') {
@@ -601,7 +651,7 @@ function getConfig() {
601
651
  }
602
652
  const migratedConfig = { ...DEFAULT_CONFIG, ...legacyConfig };
603
653
  saveConfig(migratedConfig).catch((err) => {
604
- console.warn('[i18ntk] Warning: failed to persist migrated configuration:', err.message);
654
+ logWarn('[i18ntk] Warning: failed to persist migrated configuration', { error: err.message });
605
655
  });
606
656
  currentConfig = migratedConfig;
607
657
 
@@ -619,18 +669,18 @@ function getConfig() {
619
669
  }
620
670
 
621
671
  // Use package defaults for new installation
622
- console.log('📦 Initializing with default configuration...');
623
- saveConfig(DEFAULT_CONFIG).catch((err) => {
624
- console.warn('[i18ntk] Warning: failed to persist default configuration:', err.message);
625
- });
626
- currentConfig = DEFAULT_CONFIG;
627
- return resolvePaths(DEFAULT_CONFIG);
628
-
629
- } catch (error) {
630
- console.warn('⚠️ Error loading configuration, using defaults:', error.message);
631
- currentConfig = DEFAULT_CONFIG;
632
- return resolvePaths(DEFAULT_CONFIG);
633
- }
672
+ notifyDefaultConfig('default initialization');
673
+ saveConfig(DEFAULT_CONFIG).catch((err) => {
674
+ logWarn('[i18ntk] Warning: failed to persist default configuration', { error: err.message });
675
+ });
676
+ currentConfig = DEFAULT_CONFIG;
677
+ return resolvePaths(DEFAULT_CONFIG);
678
+
679
+ } catch (error) {
680
+ notifyConfigFallback(error);
681
+ currentConfig = DEFAULT_CONFIG;
682
+ return resolvePaths(DEFAULT_CONFIG);
683
+ }
634
684
  }
635
685
 
636
686
  async function setConfig(cfg) {
@@ -693,3 +743,7 @@ module.exports = {
693
743
  toRelative,
694
744
  normalizePathValue,
695
745
  }
746
+
747
+
748
+
749
+
package/utils/config.js CHANGED
@@ -1,6 +1,7 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const SecurityUtils = require('./security');
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const SecurityUtils = require('./security');
4
+ const { logger } = require('./logger');
4
5
 
5
6
  const settingsManager = require('../settings/settings-manager');
6
7
  const CONFIG_FILE = 'i18ntk-config.json';
@@ -66,11 +67,11 @@ function loadConfig(cwd = settingsManager.configDir) {
66
67
  }
67
68
 
68
69
  throw new Error('Invalid configuration format');
69
- } catch (error) {
70
- console.error(`Error loading config: ${error.message}`);
71
- return null;
72
- }
73
- }
70
+ } catch (error) {
71
+ logger.debug(`Error loading config: ${error.message}`, { stack: error.stack });
72
+ return null;
73
+ }
74
+ }
74
75
 
75
76
  /**
76
77
  * Saves configuration to file
@@ -96,11 +97,11 @@ function saveConfig(config, cwd = settingsManager.configDir) {
96
97
  );
97
98
 
98
99
  return true;
99
- } catch (error) {
100
- console.error(`Error saving config: ${error.message}`);
101
- return false;
102
- }
103
- }
100
+ } catch (error) {
101
+ logger.error(`Error saving config: ${error.message}`);
102
+ return false;
103
+ }
104
+ }
104
105
 
105
106
  /**
106
107
  * Ensures default values for configuration
@@ -127,4 +128,4 @@ module.exports = {
127
128
  saveConfig,
128
129
  ensureConfigDefaults,
129
130
  validatePath // Exported for testing
130
- };
131
+ };
@@ -1,6 +1,7 @@
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
+ const { logger } = require('./logger');
4
5
 
5
6
  // Lazy load SecurityUtils to prevent circular dependencies
6
7
  let securityUtils;
@@ -200,10 +201,21 @@ function findLocaleFilesAllDirs(lang, preferredDir) {
200
201
  return files;
201
202
  }
202
203
 
203
- let translations = {};
204
- let currentLanguage = 'en';
205
- let isInitialized = false;
206
- const missingWarned = new Set();
204
+ let translations = {};
205
+ let currentLanguage = 'en';
206
+ let isInitialized = false;
207
+ const missingKeyCache = new Map();
208
+ const missingKeyTtlMs = 5 * 60 * 1000;
209
+
210
+ function shouldReportMissingKey(key) {
211
+ const now = Date.now();
212
+ const expiresAt = missingKeyCache.get(key) || 0;
213
+ if (expiresAt > now) {
214
+ return false;
215
+ }
216
+ missingKeyCache.set(key, now + missingKeyTtlMs);
217
+ return true;
218
+ }
207
219
 
208
220
  function loadTranslations(language) {
209
221
  const cfg = safeRequireConfig();
@@ -286,9 +298,11 @@ function loadTranslations(language) {
286
298
  currentLanguage = 'en';
287
299
  isInitialized = true;
288
300
 
289
- if (loadErrors.length > 0) {
290
- console.warn(`⚠️ No valid UI locale files found. Using built-in English strings.`);
291
- }
301
+ if (loadErrors.length > 0) {
302
+ logger.warn('No valid UI locale files found. Using built-in English strings.', {
303
+ errorCount: loadErrors.length
304
+ });
305
+ }
292
306
 
293
307
  return translations;
294
308
  }
@@ -337,23 +351,24 @@ function t(key, params = {}) {
337
351
  }
338
352
  }
339
353
 
340
- if (typeof value === 'undefined') {
341
- if (!missingWarned.has(key)) {
342
- missingWarned.add(key);
343
- console.warn(`Translation key not found: ${key}`);
344
- }
345
- return key;
346
- }
354
+ if (typeof value === 'undefined') {
355
+ if (shouldReportMissingKey(key)) {
356
+ logger.logMissingTranslationKey(key, 'Configuration error');
357
+ }
358
+ return key;
359
+ }
347
360
 
348
361
  // If we found a string, interpolate parameters
349
362
  if (typeof value === 'string') {
350
363
  return interpolateParams(value, params);
351
364
  }
352
365
 
353
- // Return the key if the final value is not a string
354
- console.warn(`Translation key does not resolve to a string: ${key}`);
355
- return key;
356
- }
366
+ // Return the key if the final value is not a string
367
+ if (shouldReportMissingKey(`${key}:non-string`)) {
368
+ logger.warn(`Translation key does not resolve to a string: ${key}`);
369
+ }
370
+ return key;
371
+ }
357
372
 
358
373
  /**
359
374
  * Interpolate parameters into a translation string