i18ntk 2.4.0 → 2.5.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 CHANGED
@@ -1,4 +1,4 @@
1
- # i18ntk v2.4.0
1
+ # i18ntk v2.5.0
2
2
 
3
3
  Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, and translation completion.
4
4
 
@@ -9,12 +9,12 @@ Zero-dependency internationalization toolkit for setup, scanning, analysis, vali
9
9
  [![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)
10
10
  [![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)
11
11
  [![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
12
- [![socket](https://socket.dev/api/badge/npm/package/i18ntk/2.4.0)](https://socket.dev/npm/package/i18ntk/overview/2.4.0)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/2.5.0)](https://socket.dev/npm/package/i18ntk/overview/2.5.0)
13
13
 
14
14
  ## Upgrade Notice
15
15
 
16
- Versions earlier than `2.4.0` may contain known stability and security issues.
17
- They are considered unsupported for production use. Upgrade to `2.4.0` or newer.
16
+ Versions earlier than `2.5.0` may contain known stability and security issues.
17
+ They are considered unsupported for production use. Upgrade to `2.5.0` or newer.
18
18
 
19
19
  ## What i18ntk Does
20
20
 
@@ -154,7 +154,7 @@ Example `.i18ntk-config`:
154
154
 
155
155
  ```json
156
156
  {
157
- "version": "2.4.0",
157
+ "version": "2.5.0",
158
158
  "sourceDir": "./locales",
159
159
  "i18nDir": "./locales",
160
160
  "outputDir": "./i18ntk-reports",
@@ -177,6 +177,7 @@ See [docs/api/CONFIGURATION.md](docs/api/CONFIGURATION.md) for the full configur
177
177
  - [Runtime API Guide](docs/runtime.md)
178
178
  - [Scanner Guide](docs/scanner-guide.md)
179
179
  - [Environment Variables](docs/environment-variables.md)
180
+ - [Migration Guide v2.5.0](docs/migration-guide-v2.5.0.md)
180
181
  - [Migration Guide v2.4.0](docs/migration-guide-v2.4.0.md)
181
182
  - [Optimization Prompt](docs/development/package-optimization-prompt.md)
182
183
 
@@ -27,21 +27,21 @@ class FixerCommand {
27
27
  // Initialize fixer properties
28
28
  this.sourceDir = null;
29
29
  this.outputDir = null;
30
- this.backupDir = null;
31
- this.dryRun = false;
32
- this.force = false;
33
- }
34
-
35
- isExcludedLanguageDirectory(name) {
36
- if (!name || typeof name !== 'string') return true;
37
- const lowered = name.toLowerCase();
38
- return lowered.startsWith('backup-') ||
39
- lowered === 'backup' ||
40
- lowered === 'backups' ||
41
- lowered === 'i18ntk-backups' ||
42
- lowered === 'reports' ||
43
- lowered === 'i18ntk-reports';
44
- }
30
+ this.backupDir = null;
31
+ this.dryRun = false;
32
+ this.force = false;
33
+ }
34
+
35
+ isExcludedLanguageDirectory(name) {
36
+ if (!name || typeof name !== 'string') return true;
37
+ const lowered = name.toLowerCase();
38
+ return lowered.startsWith('backup-') ||
39
+ lowered === 'backup' ||
40
+ lowered === 'backups' ||
41
+ lowered === 'i18ntk-backups' ||
42
+ lowered === 'reports' ||
43
+ lowered === 'i18ntk-reports';
44
+ }
45
45
 
46
46
  /**
47
47
  * Set runtime dependencies for interactive operations
@@ -79,7 +79,7 @@ class FixerCommand {
79
79
 
80
80
  this.sourceDir = this.config.sourceDir;
81
81
  this.outputDir = this.config.outputDir;
82
- this.backupDir = path.resolve(this.config.backup?.location || './i18ntk-backups', 'fixer');
82
+ this.backupDir = path.resolve(this.config.backup?.location || './i18ntk-backups', 'fixer');
83
83
 
84
84
  // Validate source directory exists
85
85
  const { validateSourceDir } = require('../../../utils/config-helper');
@@ -138,15 +138,15 @@ class FixerCommand {
138
138
  const languages = [];
139
139
 
140
140
  // Check for directory-based structure
141
- const directories = items
142
- .filter(item => item.isDirectory())
143
- .map(item => item.name)
144
- .filter(name =>
145
- name !== 'node_modules' &&
146
- !name.startsWith('.') &&
147
- name !== this.config.sourceLanguage &&
148
- !this.isExcludedLanguageDirectory(name)
149
- );
141
+ const directories = items
142
+ .filter(item => item.isDirectory())
143
+ .map(item => item.name)
144
+ .filter(name =>
145
+ name !== 'node_modules' &&
146
+ !name.startsWith('.') &&
147
+ name !== this.config.sourceLanguage &&
148
+ !this.isExcludedLanguageDirectory(name)
149
+ );
150
150
 
151
151
  // Check for monolith files (language.json files)
152
152
  const files = items
@@ -158,13 +158,13 @@ class FixerCommand {
158
158
 
159
159
  // Add monolith files as languages (without .json extension)
160
160
  const monolithLanguages = files
161
- .map(file => file.replace('.json', ''))
162
- .filter(lang =>
163
- !languages.includes(lang) &&
164
- lang !== this.config.sourceLanguage &&
165
- !this.isExcludedLanguageDirectory(lang)
166
- );
167
- languages.push(...monolithLanguages);
161
+ .map(file => file.replace('.json', ''))
162
+ .filter(lang =>
163
+ !languages.includes(lang) &&
164
+ lang !== this.config.sourceLanguage &&
165
+ !this.isExcludedLanguageDirectory(lang)
166
+ );
167
+ languages.push(...monolithLanguages);
168
168
 
169
169
  return [...new Set(languages)].sort();
170
170
  } catch (error) {
@@ -299,21 +299,21 @@ class FixerCommand {
299
299
  }
300
300
 
301
301
  // Create backup of translation files
302
- async createBackup() {
303
- if (this.dryRun) return;
304
-
305
- try {
306
- const backupEnabled = this.config?.backup?.enabled === true;
307
- if (!backupEnabled) {
308
- this.backupDir = null;
309
- return;
310
- }
311
-
312
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
313
- const backupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups');
314
- this.backupDir = path.join(backupRoot, 'fixer', `backup-${timestamp}`);
315
-
316
- console.log(t('fixer.creatingBackup', { dir: this.backupDir }));
302
+ async createBackup() {
303
+ if (this.dryRun) return;
304
+
305
+ try {
306
+ const backupEnabled = this.config?.backup?.enabled === true;
307
+ if (!backupEnabled) {
308
+ this.backupDir = null;
309
+ return;
310
+ }
311
+
312
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
313
+ const backupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups');
314
+ this.backupDir = path.join(backupRoot, 'fixer', `backup-${timestamp}`);
315
+
316
+ console.log(t('fixer.creatingBackup', { dir: this.backupDir }));
317
317
 
318
318
  // Ensure backup directory exists
319
319
  const dirCreated = SecurityUtils.safeMkdirSync(this.backupDir, process.cwd(), { recursive: true });
@@ -326,8 +326,8 @@ class FixerCommand {
326
326
  const languages = this.getAvailableLanguages();
327
327
  languages.push(this.config.sourceLanguage); // Include source language
328
328
 
329
- for (const language of languages) {
330
- const languageFiles = this.getLanguageFiles(language);
329
+ for (const language of languages) {
330
+ const languageFiles = this.getLanguageFiles(language);
331
331
 
332
332
  for (const fileName of languageFiles) {
333
333
  const sourcePath = path.join(this.sourceDir, language, fileName);
@@ -343,42 +343,42 @@ class FixerCommand {
343
343
  SecurityUtils.safeWriteFileSync(backupPath, content, process.cwd(), 'utf8');
344
344
  }
345
345
  }
346
- }
347
-
348
- this.cleanupOldBackups();
349
-
350
- console.log(t('fixer.backupCreated'));
351
- } catch (error) {
352
- console.warn(`Failed to create backup: ${error.message}`);
353
- }
354
- }
355
-
356
- cleanupOldBackups() {
357
- try {
358
- const fixerBackupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups', 'fixer');
359
- if (!SecurityUtils.safeExistsSync(fixerBackupRoot, process.cwd())) return;
360
-
361
- const configuredKeep = parseInt(this.config?.backup?.maxBackups, 10);
362
- const keepCount = Number.isInteger(configuredKeep) ? Math.min(Math.max(configuredKeep, 1), 3) : 1;
363
-
364
- const backupDirs = SecurityUtils.safeReaddirSync(fixerBackupRoot, process.cwd(), { withFileTypes: true })
365
- .filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
366
- .map(entry => {
367
- const dirPath = path.join(fixerBackupRoot, entry.name);
368
- const stat = SecurityUtils.safeStatSync(dirPath, process.cwd());
369
- return { name: entry.name, path: dirPath, mtimeMs: stat ? stat.mtimeMs : 0 };
370
- })
371
- .sort((a, b) => b.mtimeMs - a.mtimeMs);
372
-
373
- if (backupDirs.length <= keepCount) return;
374
-
375
- for (const staleDir of backupDirs.slice(keepCount)) {
376
- fs.rmSync(staleDir.path, { recursive: true, force: true });
377
- }
378
- } catch (error) {
379
- console.warn(`Failed to clean old backups: ${error.message}`);
380
- }
381
- }
346
+ }
347
+
348
+ this.cleanupOldBackups();
349
+
350
+ console.log(t('fixer.backupCreated'));
351
+ } catch (error) {
352
+ console.warn(`Failed to create backup: ${error.message}`);
353
+ }
354
+ }
355
+
356
+ cleanupOldBackups() {
357
+ try {
358
+ const fixerBackupRoot = path.resolve(this.config?.backup?.location || './i18ntk-backups', 'fixer');
359
+ if (!SecurityUtils.safeExistsSync(fixerBackupRoot, process.cwd())) return;
360
+
361
+ const configuredKeep = parseInt(this.config?.backup?.maxBackups, 10);
362
+ const keepCount = Number.isInteger(configuredKeep) ? Math.min(Math.max(configuredKeep, 1), 3) : 1;
363
+
364
+ const backupDirs = SecurityUtils.safeReaddirSync(fixerBackupRoot, process.cwd(), { withFileTypes: true })
365
+ .filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
366
+ .map(entry => {
367
+ const dirPath = path.join(fixerBackupRoot, entry.name);
368
+ const stat = SecurityUtils.safeStatSync(dirPath, process.cwd());
369
+ return { name: entry.name, path: dirPath, mtimeMs: stat ? stat.mtimeMs : 0 };
370
+ })
371
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
372
+
373
+ if (backupDirs.length <= keepCount) return;
374
+
375
+ for (const staleDir of backupDirs.slice(keepCount)) {
376
+ fs.rmSync(staleDir.path, { recursive: true, force: true });
377
+ }
378
+ } catch (error) {
379
+ console.warn(`Failed to clean old backups: ${error.message}`);
380
+ }
381
+ }
382
382
 
383
383
  // Analyze translation issues for fixing
384
384
  analyzeIssues(language, fileName) {
@@ -420,7 +420,7 @@ class FixerCommand {
420
420
  type: 'missing_key',
421
421
  key,
422
422
  sourceValue,
423
- fix: () => this.setValueByPath(targetObj, key, sourceValue)
423
+ fix: (obj) => this.setValueByPath(obj, key, sourceValue)
424
424
  });
425
425
  } else if (targetValue === '') {
426
426
  // Empty value
@@ -428,7 +428,7 @@ class FixerCommand {
428
428
  type: 'empty_value',
429
429
  key,
430
430
  sourceValue,
431
- fix: () => this.setValueByPath(targetObj, key, sourceValue)
431
+ fix: (obj) => this.setValueByPath(obj, key, sourceValue)
432
432
  });
433
433
  } else {
434
434
  const markers = this.config.notTranslatedMarkers || [this.config.notTranslatedMarker];
@@ -438,7 +438,7 @@ class FixerCommand {
438
438
  type: 'untranslated_marker',
439
439
  key,
440
440
  sourceValue,
441
- fix: () => this.setValueByPath(targetObj, key, sourceValue)
441
+ fix: (obj) => this.setValueByPath(obj, key, sourceValue)
442
442
  });
443
443
  }
444
444
  }
@@ -486,7 +486,7 @@ class FixerCommand {
486
486
 
487
487
  for (const issue of issues) {
488
488
  if (typeof issue.fix === 'function') {
489
- issue.fix();
489
+ issue.fix(targetObj);
490
490
  fixes.files[fileName].fixed++;
491
491
  fixes.fixedIssues++;
492
492
  }
@@ -494,7 +494,7 @@ class FixerCommand {
494
494
 
495
495
  // Write back the fixed content
496
496
  const fixedContent = JSON.stringify(targetObj, null, 2);
497
- SecurityUtils.safeWriteFileSync(targetFilePath, fixedContent, process.cwd(), 'utf8');
497
+ SecurityUtils.safeWriteFileSync(targetFilePath, fixedContent, this.sourceDir, 'utf8');
498
498
 
499
499
  } catch (error) {
500
500
  console.warn(`Error fixing ${language}/${fileName}: ${error.message}`);
@@ -527,9 +527,9 @@ class FixerCommand {
527
527
  }
528
528
 
529
529
  // Create backup unless disabled
530
- if (!args.noBackup && !this.dryRun && this.config?.backup?.enabled === true) {
531
- await this.createBackup();
532
- }
530
+ if (!args.noBackup && !this.dryRun && this.config?.backup?.enabled === true) {
531
+ await this.createBackup();
532
+ }
533
533
 
534
534
  const languages = this.getAvailableLanguages();
535
535
 
@@ -591,9 +591,9 @@ class FixerCommand {
591
591
  console.log(t('fixer.totalIssues', { count: totalIssues }));
592
592
  console.log(t('fixer.totalFixed', { count: totalFixed }));
593
593
 
594
- if (this.backupDir && !args.noBackup && this.config?.backup?.enabled === true) {
595
- console.log(t('fixer.backupLocation', { dir: this.backupDir }));
596
- }
594
+ if (this.backupDir && !args.noBackup && this.config?.backup?.enabled === true) {
595
+ console.log(t('fixer.backupLocation', { dir: this.backupDir }));
596
+ }
597
597
 
598
598
  console.log(t('fixer.completed'));
599
599
 
@@ -677,4 +677,4 @@ class FixerCommand {
677
677
  }
678
678
  }
679
679
 
680
- module.exports = FixerCommand;
680
+ module.exports = FixerCommand;
@@ -3,10 +3,11 @@
3
3
  * @module managers/DebugMenu
4
4
  */
5
5
 
6
- const path = require('path');
7
- const fs = require('fs');
8
- const { t } = require('../../../utils/i18n-helper');
9
- const cliHelper = require('../../../utils/cli-helper');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const { t } = require('../../../utils/i18n-helper');
9
+ const cliHelper = require('../../../utils/cli-helper');
10
+ const SecurityUtils = require('../../../utils/security');
10
11
 
11
12
  module.exports = class DebugMenu {
12
13
  constructor(manager) {
@@ -22,7 +23,7 @@ module.exports = class DebugMenu {
22
23
  const authRequired = await this.adminAuth.isAuthRequiredForScript('debugMenu');
23
24
  if (authRequired) {
24
25
  console.log(`\n${t('adminPin.protectedAccess')}`);
25
- const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
26
+ const pin = await cliHelper.promptPin(t('adminPin.enterPin') + ': ');
26
27
  const isValid = await this.adminAuth.verifyPin(pin);
27
28
 
28
29
  if (!isValid) {
@@ -66,7 +67,7 @@ module.exports = class DebugMenu {
66
67
  console.log(t('debug.runningDebugTool', { displayName }));
67
68
  try {
68
69
  const toolPath = path.join(__dirname, '..', '..', 'scripts', 'debug', toolName);
69
- if (fs.existsSync(toolPath)) {
70
+ if (SecurityUtils.safeExistsSync(toolPath, path.dirname(toolPath))) {
70
71
  console.log(`Debug tool available: ${toolName}`);
71
72
  console.log(`To run this tool manually: node "${toolPath}"`);
72
73
  console.log(`Working directory: ${path.join(__dirname, '..', '..')}`);
@@ -90,7 +91,7 @@ module.exports = class DebugMenu {
90
91
 
91
92
  try {
92
93
  const logsDir = path.join(__dirname, '..', '..', 'scripts', 'debug', 'logs');
93
- if (fs.existsSync(logsDir)) {
94
+ if (SecurityUtils.safeExistsSync(logsDir, path.dirname(logsDir))) {
94
95
  const files = fs.readdirSync(logsDir)
95
96
  .filter(file => file.endsWith('.log') || file.endsWith('.txt'))
96
97
  .sort((a, b) => {
@@ -111,7 +112,7 @@ module.exports = class DebugMenu {
111
112
  const fileIndex = parseInt(choice) - 1;
112
113
 
113
114
  if (fileIndex >= 0 && fileIndex < files.length) {
114
- const logContent = fs.readFileSync(path.join(logsDir, files[fileIndex]), 'utf8');
115
+ const logContent = SecurityUtils.safeReadFileSync(path.join(logsDir, files[fileIndex]), logsDir, 'utf8');
115
116
  console.log(`\n${t('debug.contentOf', { filename: files[fileIndex] })}:`);
116
117
  console.log('============================================================');
117
118
  console.log(logContent.slice(-2000)); // Show last 2000 characters
@@ -137,4 +138,4 @@ module.exports = class DebugMenu {
137
138
  async show() {
138
139
  return this.showDebugMenu();
139
140
  }
140
- };
141
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "2.4.0",
3
+ "version": "2.5.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",
@@ -131,9 +131,6 @@
131
131
  "default": "./runtime/enhanced.js"
132
132
  },
133
133
  "./runtime/*": "./runtime/*",
134
- "./main/*": "./main/*",
135
- "./utils/*": "./utils/*",
136
- "./settings/*": "./settings/*",
137
134
  "./ui-locales/*": "./ui-locales/*",
138
135
  "./package.json": "./package.json"
139
136
  },
@@ -158,20 +155,57 @@
158
155
  "test": "tests"
159
156
  },
160
157
  "files": [
161
- "main/",
158
+ "main/i18ntk-analyze.js",
159
+ "main/i18ntk-backup-class.js",
160
+ "main/i18ntk-backup.js",
161
+ "main/i18ntk-complete.js",
162
+ "main/i18ntk-doctor.js",
163
+ "main/i18ntk-fixer.js",
164
+ "main/i18ntk-init.js",
165
+ "main/i18ntk-scanner.js",
166
+ "main/i18ntk-setup.js",
167
+ "main/i18ntk-sizing.js",
168
+ "main/i18ntk-summary.js",
169
+ "main/i18ntk-ui.js",
170
+ "main/i18ntk-usage.js",
171
+ "main/i18ntk-validate.js",
172
+ "main/manage/",
162
173
  "runtime/",
163
- "utils/",
164
- "settings/",
174
+ "settings/language-config.json",
175
+ "settings/settings-cli.js",
176
+ "settings/settings-manager.js",
165
177
  "ui-locales/",
166
178
  "!main/manage/index-fixed.js",
167
- "!main/i18ntk-manage.js",
168
- "!main/i18ntk-py.js",
169
- "!main/i18ntk-js.js",
170
- "!main/i18ntk-java.js",
171
- "!main/i18ntk-php.js",
172
- "!main/i18ntk-go.js",
173
- "!main/i18ntk-settings.js",
174
- "!utils/security-fixed.js",
179
+ "utils/admin-auth.js",
180
+ "utils/admin-cli.js",
181
+ "utils/cli-helper.js",
182
+ "utils/cli.js",
183
+ "utils/colors-new.js",
184
+ "utils/config-helper.js",
185
+ "utils/config-manager.js",
186
+ "utils/config.js",
187
+ "utils/env-manager.js",
188
+ "utils/exit-codes.js",
189
+ "utils/extractor-manager.js",
190
+ "utils/extractors/regex.js",
191
+ "utils/format-manager.js",
192
+ "utils/formats/json.js",
193
+ "utils/framework-detector.js",
194
+ "utils/i18n-helper.js",
195
+ "utils/init-helper.js",
196
+ "utils/json-output.js",
197
+ "utils/locale-optimizer.js",
198
+ "utils/logger.js",
199
+ "utils/npm-version-warning.js",
200
+ "utils/plugin-loader.js",
201
+ "utils/prompt-helper.js",
202
+ "utils/prompt.js",
203
+ "utils/secure-errors.js",
204
+ "utils/security.js",
205
+ "utils/setup-enforcer.js",
206
+ "utils/terminal-icons.js",
207
+ "utils/version-utils.js",
208
+ "utils/watch-locales.js",
175
209
  "LICENSE",
176
210
  "package.json",
177
211
  "README.md"
@@ -203,6 +237,7 @@
203
237
  "security:audit": "npm run security:check && npm run security:test",
204
238
  "test": "npm run security:test",
205
239
  "test:all": "npm run security:audit",
240
+ "release:reset": "node scripts/reset-release-state.js",
206
241
  "prepublishOnly": "npm run security:audit",
207
242
  "prepare": "npm run security:check",
208
243
  "backup:create": "node main/i18ntk-backup.js create",
@@ -224,29 +259,23 @@
224
259
  },
225
260
  "preferGlobal": true,
226
261
  "versionInfo": {
227
- "version": "2.4.0",
228
- "releaseDate": "16/04/2026",
229
- "lastUpdated": "16/04/2026",
262
+ "version": "2.5.0",
263
+ "releaseDate": "29/04/2026",
264
+ "lastUpdated": "29/04/2026",
230
265
  "maintainer": "Vlad Noskov",
231
266
  "changelog": "./CHANGELOG.md",
232
267
  "documentation": "./README.md",
233
268
  "apiReference": "./docs/api/API_REFERENCE.md",
234
269
  "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.",
238
- "HOTFIX: Removed deprecated package-path fallback that caused production build warnings for non-exported subpaths.",
239
- "CRITICAL FIX: Resolved sizing and usage-analysis regressions in v2 command flow.",
240
- "PACKAGING: Reduced publish footprint by removing internal development scripts and legacy fixed-file artifacts.",
241
- "SECURITY: Hardened release checks and added explicit support guidance to update from pre-2.3.5 versions.",
242
- "CONFIG: Added cross-process file locking for .i18ntk-config writes to prevent production rename races.",
243
- "CONFIG: Made autosave runtime-safe with non-throwing save failures and I18NTK_DISABLE_AUTOSAVE support.",
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.",
246
- "I18N: Completed internal UI locale parity and actionable untranslated-key cleanup across supported languages."
270
+ "SECURITY: Centralized environment-variable access behind an explicit allowlist.",
271
+ "SECURITY: Hardened path containment checks to reject sibling-prefix traversal cases.",
272
+ "SECURITY: Added timing-safe admin PIN hash comparison and fixed expired-session cleanup.",
273
+ "SECURITY: Expanded release security scanning to nested production source files.",
274
+ "FIXER: Fixed translation fixer writes for absolute source directories and confirmed applied fixes persist.",
275
+ "PACKAGING: Updated package and documentation metadata for the 2.5.0 release."
247
276
  ],
248
277
  "breakingChanges": [],
249
- "nextVersion": "2.4.1",
278
+ "nextVersion": "2.5.1",
250
279
  "supportedNodeVersions": ">=16.0.0",
251
280
  "supportedFrameworks": {
252
281
  "react-i18next": ">=11.0.0",
@@ -268,7 +297,7 @@
268
297
  "spring-boot": ">=2.5.0",
269
298
  "laravel": ">=8.0.0"
270
299
  },
271
- "supportPolicy": "Versions earlier than 2.4.0 may be unstable or insecure. Upgrade to 2.4.0 or newer."
300
+ "supportPolicy": "Versions earlier than 2.5.0 may be unstable or insecure. Upgrade to 2.5.0 or newer."
272
301
  },
273
302
  "_comment": "This package is zero-dependency and uses only native Node.js modules"
274
303
  }
package/runtime/index.js CHANGED
@@ -6,6 +6,7 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const SecurityUtils = require('../utils/security');
9
+ const { envManager } = require('../utils/env-manager');
9
10
 
10
11
  let configManager = null;
11
12
  try { configManager = require('../utils/config-manager'); } catch (_) { /* optional */ }
@@ -54,15 +55,18 @@ function resolveBaseDir(explicitBaseDir) {
54
55
  // 1) Highest priority: explicit option
55
56
  if (explicitBaseDir) return path.resolve(explicitBaseDir);
56
57
  // 2) Environment override for CI/explicit control
57
- if (process.env.I18NTK_RUNTIME_DIR) {
58
- return path.resolve(process.env.I18NTK_RUNTIME_DIR);
58
+ const runtimeDir = envManager.get('I18NTK_RUNTIME_DIR');
59
+ if (runtimeDir) {
60
+ return path.resolve(runtimeDir);
59
61
  }
60
62
  // 2b) Respect config-style env overrides, even without config-manager
61
- if (process.env.I18NTK_I18N_DIR) {
62
- return path.resolve(process.env.I18NTK_I18N_DIR);
63
+ const envI18nDir = envManager.get('I18NTK_I18N_DIR');
64
+ if (envI18nDir) {
65
+ return path.resolve(envI18nDir);
63
66
  }
64
- if (process.env.I18NTK_SOURCE_DIR) {
65
- return path.resolve(process.env.I18NTK_SOURCE_DIR);
67
+ const envSourceDir = envManager.get('I18NTK_SOURCE_DIR');
68
+ if (envSourceDir) {
69
+ return path.resolve(envSourceDir);
66
70
  }
67
71
  // 3) Use config-manager if available (single source of truth: i18ntk-config.json)
68
72
  try {
@@ -72,11 +76,13 @@ function resolveBaseDir(explicitBaseDir) {
72
76
  // If config-manager resolved absolute paths, use as-is; otherwise resolve from project cwd
73
77
  const isAbs = typeof base === 'string' && path.isAbsolute(base);
74
78
  if (isAbs) return base;
75
- const root = process.env.I18NTK_PROJECT_ROOT ? path.resolve(process.env.I18NTK_PROJECT_ROOT) : process.cwd();
79
+ const envProjectRoot = envManager.get('I18NTK_PROJECT_ROOT');
80
+ const root = envProjectRoot ? path.resolve(envProjectRoot) : process.cwd();
76
81
  return path.resolve(root, base);
77
82
  } catch (_) {
78
83
  // 4) Fallback to conventional './locales' from project CWD
79
- const root = process.env.I18NTK_PROJECT_ROOT ? path.resolve(process.env.I18NTK_PROJECT_ROOT) : process.cwd();
84
+ const envProjectRoot = envManager.get('I18NTK_PROJECT_ROOT');
85
+ const root = envProjectRoot ? path.resolve(envProjectRoot) : process.cwd();
80
86
  return path.resolve(root, './locales');
81
87
  }
82
88
  }