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 +6 -5
- package/main/manage/commands/FixerCommand.js +97 -97
- package/main/manage/managers/DebugMenu.js +10 -9
- package/package.json +61 -32
- package/runtime/index.js +14 -8
- package/utils/admin-auth.js +594 -576
- package/utils/config-manager.js +72 -72
- package/utils/env-manager.js +117 -26
- package/utils/i18n-helper.js +50 -49
- package/utils/json-output.js +13 -12
- package/utils/logger.js +7 -6
- package/utils/prompt-helper.js +44 -41
- package/utils/secure-errors.js +156 -154
- package/utils/security.js +235 -233
- package/utils/setup-enforcer.js +110 -109
- package/utils/terminal-icons.js +164 -163
- package/settings/i18ntk-config.json +0 -283
- package/utils/admin-pin.js +0 -520
- package/utils/arg-parser.js +0 -40
- package/utils/cli-args.js +0 -210
- package/utils/mini-commander.js +0 -179
- package/utils/missing-key-validator.js +0 -858
- package/utils/path-utils.js +0 -33
- package/utils/performance-optimizer.js +0 -246
- package/utils/prompt-new.js +0 -55
- package/utils/promptPin.js +0 -76
- package/utils/safe-json.js +0 -40
- package/utils/secure-backup.js +0 -340
- package/utils/security-check-improved.js +0 -393
- package/utils/security-config.js +0 -239
- package/utils/setup-validator.js +0 -717
- package/utils/ultra-performance-optimizer.js +0 -352
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# i18ntk v2.
|
|
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
|
[](https://nodejs.org)
|
|
10
10
|
[](https://www.npmjs.com/package/i18ntk)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](https://socket.dev/npm/package/i18ntk/overview/2.5.0)
|
|
13
13
|
|
|
14
14
|
## Upgrade Notice
|
|
15
15
|
|
|
16
|
-
Versions earlier than `2.
|
|
17
|
-
They are considered unsupported for production use. Upgrade to `2.
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
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.
|
|
228
|
-
"releaseDate": "
|
|
229
|
-
"lastUpdated": "
|
|
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
|
-
"
|
|
236
|
-
"SECURITY:
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
"PACKAGING:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
+
const envI18nDir = envManager.get('I18NTK_I18N_DIR');
|
|
64
|
+
if (envI18nDir) {
|
|
65
|
+
return path.resolve(envI18nDir);
|
|
63
66
|
}
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
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
|
}
|