i18ntk 2.5.0 → 2.6.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/SECURITY.md ADDED
@@ -0,0 +1,52 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ The supported production line is `2.5.x`.
6
+
7
+ Versions earlier than `2.5.0` are not recommended for production use because later releases include package, filesystem, environment, and admin-auth hardening.
8
+
9
+ ## Security Model
10
+
11
+ i18ntk is a local developer CLI and runtime helper. It is expected to read and write project files, but it must do so with conservative path validation and without external runtime dependencies.
12
+
13
+ Security priorities:
14
+
15
+ - zero runtime dependencies
16
+ - no install-time lifecycle commands in the public package manifest
17
+ - no shipped local setup state, admin PINs, backups, reports, logs, credentials, or generated artifacts
18
+ - centralized environment-variable access through `utils/env-manager.js`
19
+ - path containment checks based on resolved paths and `path.relative()`
20
+ - timing-safe comparison for authentication hashes or tokens
21
+ - silent-by-default logging for production-like contexts
22
+
23
+ ## Published Package Controls
24
+
25
+ The npm package uses a stripped public manifest. It must not contain install-time lifecycle commands, dependency fields, local setup state, or development tooling.
26
+
27
+ ## Reporting Vulnerabilities
28
+
29
+ Do not report security vulnerabilities in public GitHub issues.
30
+
31
+ Use GitHub Security Advisories for private vulnerability reports. Include:
32
+
33
+ - affected version
34
+ - clear reproduction steps
35
+ - expected and actual behavior
36
+ - impact assessment
37
+ - proof of concept, if safe to share privately
38
+
39
+ ## Disclosure Process
40
+
41
+ Security reports are reviewed privately first. Confirmed issues should receive:
42
+
43
+ - a fix or mitigation
44
+ - a release note or migration note when user action is required
45
+ - an npm release when the fix affects published users
46
+
47
+ ## User Guidance
48
+
49
+ - Keep i18ntk updated to `2.5.0` or newer.
50
+ - Do not commit `.i18ntk-config`, admin PIN files, backup directories, generated reports, logs, npm credentials, or secret material.
51
+ - Run i18ntk only in project directories you trust.
52
+ - Review generated translation changes before committing them.
@@ -252,7 +252,7 @@ class I18nAnalyzer {
252
252
  const relativePath = path.relative(this.sourceDir, fullPath);
253
253
  const shouldExclude = (this.config.excludeFiles || []).some(pattern => {
254
254
  if (typeof pattern === 'string') {
255
- return relativePath === pattern || relativePath.endsWith(path.sep + pattern);
255
+ return relativePath === pattern || relativePath.endsWith('/' + pattern) || relativePath.endsWith('\\' + pattern);
256
256
  }
257
257
  if (pattern instanceof RegExp) {
258
258
  return pattern.test(relativePath);
@@ -908,9 +908,9 @@ try {
908
908
  const isRequired = await adminAuth.isAuthRequired();
909
909
  if (isRequired) {
910
910
  console.log('\n' + t('adminCli.authRequiredForOperation', { operation: 'analyze translations' }));
911
- const cliHelper = require('../utils/cli-helper');
912
- const pin = await cliHelper.promptPin(t('adminCli.enterPin'));
913
- const isValid = await this.adminAuth.verifyPin(pin);
911
+ const cli = require('../utils/cli-helper');
912
+ const pin = await cli.promptPin(t('adminCli.enterPin'));
913
+ const isValid = await adminAuth.verifyPin(pin);
914
914
 
915
915
  if (!isValid) {
916
916
  console.log(t('adminCli.invalidPin'));
@@ -181,9 +181,9 @@ class I18nTextScanner {
181
181
  if (pyproject.includes('Flask')) return 'flask';
182
182
  }
183
183
 
184
- // Check for Python files
185
- const hasPythonFiles = fs.readdirSync(projectRoot, { recursive: true })
186
- .some(file => file.endsWith && file.endsWith('.py'));
184
+ // Check for Python files using safeReaddirSync
185
+ const pythonItems = SecurityUtils.safeReaddirSync(projectRoot, projectRoot, { withFileTypes: true }) || [];
186
+ const hasPythonFiles = pythonItems.some(item => item.isFile && item.name && item.name.endsWith('.py'));
187
187
  if (hasPythonFiles) return 'python';
188
188
  } catch (error) {
189
189
  // Continue to JS frameworks
@@ -420,20 +420,22 @@ class I18nTextScanner {
420
420
  const extensions = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.html', '.svelte', '.py', '.pyx', '.pyi'];
421
421
 
422
422
  const scanRecursive = (currentDir) => {
423
- const items = fs.readdirSync(currentDir);
423
+ const items = SecurityUtils.safeReaddirSync(currentDir, path.dirname(currentDir), { withFileTypes: true });
424
+ if (!items) return;
424
425
 
425
426
  for (const item of items) {
426
- const fullPath = path.join(currentDir, item);
427
- const stat = fs.statSync(fullPath);
427
+ const fullPath = path.join(currentDir, item.name);
428
+ const stat = SecurityUtils.safeStatSync(fullPath, currentDir);
429
+ if (!stat) continue;
428
430
 
429
431
  if (stat.isDirectory()) {
430
- if (!item.startsWith('.') && !this.shouldExcludeFile(fullPath, exclusions)) {
432
+ if (!item.name.startsWith('.') && !this.shouldExcludeFile(fullPath, exclusions)) {
431
433
  scanRecursive(fullPath);
432
434
  }
433
435
  } else if (stat.isFile()) {
434
- const ext = path.extname(item);
436
+ const ext = path.extname(item.name);
435
437
  if (extensions.includes(ext) && !this.shouldExcludeFile(fullPath, exclusions)) {
436
- if (!includeTests && (item.includes('.test.') || item.includes('.spec.'))) {
438
+ if (!includeTests && (item.name.includes('.test.') || item.name.includes('.spec.'))) {
437
439
  continue;
438
440
  }
439
441
 
@@ -455,7 +457,7 @@ class I18nTextScanner {
455
457
 
456
458
  async generateReport(results, outputDir) {
457
459
  if (!SecurityUtils.safeExistsSync(outputDir, path.dirname(outputDir))) {
458
- fs.mkdirSync(outputDir, { recursive: true });
460
+ SecurityUtils.safeMkdirSync(outputDir, process.cwd(), { recursive: true });
459
461
  }
460
462
 
461
463
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -472,11 +474,11 @@ class I18nTextScanner {
472
474
  };
473
475
 
474
476
  // JSON report
475
- SecurityUtils.safeWriteFileSync(reportFile, JSON.stringify(summary, null, 2), outputDir);
477
+ SecurityUtils.safeWriteFileSync(reportFile, JSON.stringify(summary, null, 2), outputDir, 'utf8');
476
478
 
477
479
  // Markdown summary
478
480
  const mdContent = this.generateMarkdownReport(summary);
479
- SecurityUtils.safeWriteFileSync(summaryFile, mdContent, outputDir);
481
+ SecurityUtils.safeWriteFileSync(summaryFile, mdContent, outputDir, 'utf8');
480
482
 
481
483
  return { reportFile, summaryFile, summary };
482
484
  }
@@ -33,7 +33,7 @@ if (isUppercase) {
33
33
  console.error(' npm run i18ntk:manage');
34
34
  console.error('');
35
35
  console.error('📖 For more information, run: npx i18ntk --help');
36
- process.exit(ExitCodes.CONFIG_ERROR);
36
+ process.exit(1);
37
37
  }
38
38
 
39
39
  const fs = require('fs');
@@ -112,8 +112,8 @@ class I18nValidator {
112
112
  } else {
113
113
  console.warn(t('config.dirFallbackWarning', { dir: this.sourceDir, fallback: this.sourceLanguageDir }) ||
114
114
  `Warning: Directory ${this.sourceDir} not found. Using ${this.sourceLanguageDir}.`);
115
- if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir)) {
116
- fs.mkdirSync(this.sourceLanguageDir, { recursive: true });
115
+ if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir, process.cwd())) {
116
+ SecurityUtils.safeMkdirSync(this.sourceLanguageDir, process.cwd(), { recursive: true });
117
117
  }
118
118
  }
119
119
  }
@@ -204,13 +204,17 @@ class I18nValidator {
204
204
  throw new Error(`Source directory not found: ${this.sourceDir}`);
205
205
  }
206
206
 
207
- const languages = fs.readdirSync(this.sourceDir)
208
- .filter(item => {
209
- const itemPath = path.join(this.sourceDir, item);
210
- return fs.statSync(itemPath).isDirectory() &&
211
- item !== this.config.sourceLanguage &&
212
- !this.isExcludedLanguageDirectory(item);
213
- });
207
+ const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
208
+ if (!items) {
209
+ throw new Error(`Source directory not found: ${this.sourceDir}`);
210
+ }
211
+
212
+ const languages = items
213
+ .filter(item => {
214
+ return item.isDirectory() &&
215
+ item.name !== this.config.sourceLanguage &&
216
+ !this.isExcludedLanguageDirectory(item.name);
217
+ }).map(item => item.name);
214
218
 
215
219
  return languages;
216
220
  } catch (error) {
@@ -228,11 +232,14 @@ class I18nValidator {
228
232
  return [];
229
233
  }
230
234
 
231
- const files = fs.readdirSync(languageDir)
232
- .filter(file => {
233
- return file.endsWith('.json') &&
234
- !this.config.excludeFiles.includes(file);
235
- });
235
+ const items = SecurityUtils.safeReaddirSync(languageDir, process.cwd(), { withFileTypes: true });
236
+ if (!items) return [];
237
+
238
+ const files = items
239
+ .filter(item => {
240
+ return item.isFile() && item.name.endsWith('.json') &&
241
+ !this.config.excludeFiles.includes(item.name);
242
+ }).map(item => item.name);
236
243
 
237
244
  return files;
238
245
  } catch (error) {
@@ -682,10 +689,10 @@ class I18nValidator {
682
689
 
683
690
  // Delete old validation report if it exists
684
691
  const reportPath = path.join(process.cwd(), 'validation-report.txt');
685
- SecurityUtils.validatePath(reportPath);
692
+ const validatedPath = SecurityUtils.validatePath(reportPath, process.cwd());
686
693
 
687
- if (SecurityUtils.safeExistsSync(reportPath)) {
688
- fs.unlinkSync(reportPath);
694
+ if (validatedPath && SecurityUtils.safeExistsSync(validatedPath, process.cwd())) {
695
+ SecurityUtils.safeUnlinkSync(validatedPath, process.cwd());
689
696
  console.log(t('validate.deletedOldReport'));
690
697
 
691
698
  SecurityUtils.logSecurityEvent(t('validate.fileDeleted'), 'info', {
@@ -129,10 +129,13 @@ class AnalyzeCommand {
129
129
  throw new Error(`Source directory does not exist or is not a directory: ${safePath}`);
130
130
  }
131
131
 
132
- try {
133
- fs.accessSync(safePath, fs.constants.R_OK | fs.constants.W_OK);
134
- } catch {
135
- throw new Error(`Insufficient permissions for source directory: ${safePath}`);
132
+ try {
133
+ const stat = SecurityUtils.safeStatSync(safePath, process.cwd());
134
+ if (!stat || !stat.isDirectory()) {
135
+ throw new Error(`Source directory is not accessible: ${safePath}`);
136
+ }
137
+ } catch (error) {
138
+ throw new Error(`Insufficient permissions for source directory: ${safePath}`);
136
139
  }
137
140
  }
138
141
 
@@ -373,7 +373,17 @@ class FixerCommand {
373
373
  if (backupDirs.length <= keepCount) return;
374
374
 
375
375
  for (const staleDir of backupDirs.slice(keepCount)) {
376
- fs.rmSync(staleDir.path, { recursive: true, force: true });
376
+ try {
377
+ SecurityUtils.safeUnlinkSync(staleDir.path, process.cwd());
378
+ } catch (_) {
379
+ // Directory not empty, use recursive removal
380
+ try {
381
+ const { rmSync } = require('fs');
382
+ rmSync(staleDir.path, { recursive: true, force: true });
383
+ } catch (_) {
384
+ // Best-effort cleanup
385
+ }
386
+ }
377
387
  }
378
388
  } catch (error) {
379
389
  console.warn(`Failed to clean old backups: ${error.message}`);
@@ -175,9 +175,9 @@ class ScannerCommand {
175
175
  if (pyproject.includes('Flask')) return 'flask';
176
176
  }
177
177
 
178
- // Check for Python files
179
- const hasPythonFiles = fs.readdirSync(projectRoot, { recursive: true })
180
- .some(file => file.endsWith && file.endsWith('.py'));
178
+ // Check for Python files using safeReaddirSync
179
+ const pythonItems = SecurityUtils.safeReaddirSync(projectRoot, projectRoot, { withFileTypes: true }) || [];
180
+ const hasPythonFiles = pythonItems.some(item => item.isFile && item.name && item.name.endsWith('.py'));
181
181
  if (hasPythonFiles) return 'python';
182
182
  } catch (error) {
183
183
  // Continue to JS frameworks
@@ -414,20 +414,22 @@ class ScannerCommand {
414
414
  const extensions = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.html', '.svelte', '.py', '.pyx', '.pyi'];
415
415
 
416
416
  const scanRecursive = (currentDir) => {
417
- const items = fs.readdirSync(currentDir);
417
+ const items = SecurityUtils.safeReaddirSync(currentDir, path.dirname(currentDir), { withFileTypes: true });
418
+ if (!items) return;
418
419
 
419
420
  for (const item of items) {
420
- const fullPath = path.join(currentDir, item);
421
- const stat = fs.statSync(fullPath);
421
+ const fullPath = path.join(currentDir, item.name);
422
+ const stat = SecurityUtils.safeStatSync(fullPath, currentDir);
423
+ if (!stat) continue;
422
424
 
423
425
  if (stat.isDirectory()) {
424
- if (!item.startsWith('.') && !this.shouldExcludeFile(fullPath, exclusions)) {
426
+ if (!item.name.startsWith('.') && !this.shouldExcludeFile(fullPath, exclusions)) {
425
427
  scanRecursive(fullPath);
426
428
  }
427
429
  } else if (stat.isFile()) {
428
- const ext = path.extname(item);
430
+ const ext = path.extname(item.name);
429
431
  if (extensions.includes(ext) && !this.shouldExcludeFile(fullPath, exclusions)) {
430
- if (!includeTests && (item.includes('.test.') || item.includes('.spec.'))) {
432
+ if (!includeTests && (item.name.includes('.test.') || item.name.includes('.spec.'))) {
431
433
  continue;
432
434
  }
433
435
 
@@ -449,7 +451,7 @@ class ScannerCommand {
449
451
 
450
452
  async generateReport(results, outputDir) {
451
453
  if (!SecurityUtils.safeExistsSync(outputDir, path.dirname(outputDir))) {
452
- fs.mkdirSync(outputDir, { recursive: true });
454
+ SecurityUtils.safeMkdirSync(outputDir, process.cwd(), { recursive: true });
453
455
  }
454
456
 
455
457
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
@@ -94,8 +94,8 @@ class ValidateCommand {
94
94
  } else {
95
95
  console.warn(t('config.dirFallbackWarning', { dir: this.sourceDir, fallback: this.sourceLanguageDir }) ||
96
96
  `Warning: Directory ${this.sourceDir} not found. Using ${this.sourceLanguageDir}.`);
97
- if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir)) {
98
- fs.mkdirSync(this.sourceLanguageDir, { recursive: true });
97
+ if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir, process.cwd())) {
98
+ SecurityUtils.safeMkdirSync(this.sourceLanguageDir, process.cwd(), { recursive: true });
99
99
  }
100
100
  }
101
101
  }
@@ -185,13 +185,15 @@ class ValidateCommand {
185
185
  throw new Error(`Source directory not found: ${this.sourceDir}`);
186
186
  }
187
187
 
188
- const languages = fs.readdirSync(this.sourceDir)
189
- .filter(item => {
190
- const itemPath = path.join(this.sourceDir, item);
191
- return fs.statSync(itemPath).isDirectory() &&
192
- item !== this.config.sourceLanguage &&
193
- !this.isExcludedLanguageDirectory(item);
194
- });
188
+ const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
189
+ if (!items) {
190
+ throw new Error(`Source directory not found: ${this.sourceDir}`);
191
+ }
192
+
193
+ const languages = items
194
+ .filter(item => item.isDirectory())
195
+ .filter(item => item.name !== this.config.sourceLanguage && !this.isExcludedLanguageDirectory(item.name))
196
+ .map(item => item.name);
195
197
 
196
198
  return languages;
197
199
  } catch (error) {
@@ -209,11 +211,13 @@ class ValidateCommand {
209
211
  return [];
210
212
  }
211
213
 
212
- const files = fs.readdirSync(languageDir)
213
- .filter(file => {
214
- return file.endsWith('.json') &&
215
- !this.config.excludeFiles.includes(file);
216
- });
214
+ const items = SecurityUtils.safeReaddirSync(languageDir, process.cwd(), { withFileTypes: true });
215
+ if (!items) return [];
216
+
217
+ const files = items
218
+ .filter(item => item.isFile() && item.name.endsWith('.json') &&
219
+ !this.config.excludeFiles.includes(item.name))
220
+ .map(item => item.name);
217
221
 
218
222
  return files;
219
223
  } catch (error) {
@@ -663,10 +667,10 @@ class ValidateCommand {
663
667
 
664
668
  // Delete old validation report if it exists
665
669
  const reportPath = path.join(process.cwd(), 'validation-report.txt');
666
- SecurityUtils.validatePath(reportPath);
670
+ const validatedPath = SecurityUtils.validatePath(reportPath, process.cwd());
667
671
 
668
- if (SecurityUtils.safeExistsSync(reportPath)) {
669
- fs.unlinkSync(reportPath);
672
+ if (validatedPath && SecurityUtils.safeExistsSync(validatedPath, process.cwd())) {
673
+ SecurityUtils.safeUnlinkSync(validatedPath, process.cwd());
670
674
  console.log(t('validate.deletedOldReport'));
671
675
 
672
676
  SecurityUtils.logSecurityEvent(t('validate.fileDeleted'), 'info', {
@@ -765,10 +765,11 @@ class I18nManager {
765
765
 
766
766
  function findFiles(dir, results = []) {
767
767
  try {
768
- const items = fs.readdirSync(dir);
768
+ const items = SecurityUtils.safeReaddirSync(dir, cwd, { withFileTypes: true });
769
+ if (!items) return results;
769
770
 
770
771
  for (const item of items) {
771
- const fullPath = path.join(dir, item);
772
+ const fullPath = path.join(dir, item.name);
772
773
  const relativePath = path.relative(cwd, fullPath);
773
774
 
774
775
  if (shouldIgnore(relativePath)) {
@@ -776,14 +777,12 @@ class I18nManager {
776
777
  }
777
778
 
778
779
  try {
779
- const stat = fs.statSync(fullPath);
780
-
781
- if (stat.isDirectory()) {
780
+ if (item.isDirectory()) {
782
781
  findFiles(fullPath, results);
783
- } else if (stat.isFile()) {
782
+ } else if (item.isFile()) {
784
783
  // Check if file matches any of our patterns
785
784
  for (const pattern of patterns) {
786
- if (matchesPattern(item, pattern)) {
785
+ if (matchesPattern(item.name, pattern)) {
787
786
  results.push(relativePath);
788
787
  break;
789
788
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "2.5.0",
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.",
3
+ "version": "2.6.0",
4
+ "description": "Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, fixing, reporting, and runtime translation loading.",
5
5
  "keywords": [
6
6
  "i18n",
7
7
  "internationalization",
@@ -9,94 +9,22 @@
9
9
  "translation",
10
10
  "l10n",
11
11
  "multilingual",
12
- "globalization",
13
12
  "i18next",
14
13
  "react-i18next",
15
14
  "vue-i18n",
16
15
  "angular-i18n",
17
16
  "next-i18next",
18
- "nuxt-i18n",
19
- "svelte-i18n",
20
- "django",
21
- "flask",
22
- "fastapi",
23
- "laravel",
24
- "spring-boot",
25
17
  "nodejs",
26
18
  "javascript",
27
19
  "typescript",
28
20
  "cli",
29
21
  "command-line",
30
- "automation",
31
22
  "developer-tools",
32
- "productivity",
33
23
  "translation-management",
34
- "language-support",
35
24
  "framework-detection",
36
25
  "zero-dependency",
37
- "lightweight",
38
- "fast",
39
- "performance",
40
26
  "security",
41
- "enterprise",
42
- "devops",
43
- "ci-cd",
44
- "json",
45
- "yaml",
46
- "csv",
47
- "po",
48
- "xliff",
49
- "android",
50
- "ios",
51
- "react-native",
52
- "flutter",
53
- "expo",
54
- "electron",
55
- "desktop",
56
- "mobile",
57
- "web",
58
- "full-stack",
59
- "backend",
60
- "frontend",
61
- "api",
62
- "microservices",
63
- "serverless",
64
- "docker",
65
- "kubernetes",
66
- "cloud",
67
- "aws",
68
- "azure",
69
- "gcp",
70
- "saas",
71
- "paas",
72
- "open-source",
73
- "free",
74
- "npm",
75
- "package",
76
- "tool",
77
- "utility",
78
- "workflow",
79
- "efficiency",
80
- "best-practices",
81
- "standards",
82
- "compliance",
83
- "accessibility",
84
- "a11y",
85
- "seo",
86
- "marketing",
87
- "growth",
88
- "scale",
89
- "startup",
90
- "business",
91
- "professional",
92
- "expert",
93
- "advanced",
94
- "comprehensive",
95
- "complete",
96
- "all-in-one",
97
- "suite",
98
- "platform",
99
- "solution"
27
+ "json"
100
28
  ],
101
29
  "homepage": "https://github.com/vladnoskv/i18ntk#readme",
102
30
  "bugs": {
@@ -107,8 +35,8 @@
107
35
  "url": "git+https://github.com/vladnoskv/i18ntk.git"
108
36
  },
109
37
  "funding": {
110
- "type": "github",
111
- "url": "https://github.com/sponsors/vladnoskv"
38
+ "type": "individual",
39
+ "url": "https://www.charitynavigator.org/"
112
40
  },
113
41
  "license": "MIT",
114
42
  "author": {
@@ -150,10 +78,6 @@
150
78
  "i18ntk-scanner": "main/i18ntk-scanner.js",
151
79
  "i18ntk-backup": "main/i18ntk-backup.js"
152
80
  },
153
- "directories": {
154
- "doc": "docs",
155
- "test": "tests"
156
- },
157
81
  "files": [
158
82
  "main/i18ntk-analyze.js",
159
83
  "main/i18ntk-backup-class.js",
@@ -207,49 +131,14 @@
207
131
  "utils/version-utils.js",
208
132
  "utils/watch-locales.js",
209
133
  "LICENSE",
210
- "package.json",
211
- "README.md"
134
+ "README.md",
135
+ "CHANGELOG.md",
136
+ "CODE_OF_CONDUCT.md",
137
+ "CONTRIBUTING.md",
138
+ "FUNDING.md",
139
+ "SECURITY.md"
212
140
  ],
213
141
  "sideEffects": false,
214
- "scripts": {
215
- "i18ntk": "node main/manage/index.js",
216
- "i18ntk-setup": "node main/i18ntk-setup.js",
217
- "i18ntk-manage": "node main/manage/index.js",
218
- "i18ntk-init": "node main/i18ntk-init.js",
219
- "i18ntk-analyze": "node main/i18ntk-analyze.js",
220
- "i18ntk-validate": "node main/i18ntk-validate.js",
221
- "i18ntk-usage": "node main/i18ntk-usage.js",
222
- "i18ntk-complete": "node main/i18ntk-complete.js",
223
- "i18ntk-sizing": "node main/i18ntk-sizing.js",
224
- "i18ntk-summary": "node main/i18ntk-summary.js",
225
- "i18ntk-doctor": "node main/i18ntk-doctor.js",
226
- "i18ntk-fixer": "node main/i18ntk-fixer.js",
227
- "i18ntk-scanner": "node main/i18ntk-scanner.js",
228
- "i18ntk-backup": "node main/i18ntk-backup.js",
229
- "i18ntk-py": "node main/i18ntk-py.js",
230
- "i18ntk-js": "node main/i18ntk-js.js",
231
- "i18ntk-java": "node main/i18ntk-java.js",
232
- "i18ntk-php": "node main/i18ntk-php.js",
233
- "i18ntk-go": "node main/i18ntk-go.js",
234
- "start": "node main/manage/index.js",
235
- "security:check": "node utils/security-check-improved.js",
236
- "security:test": "node --test tests/security.test.js",
237
- "security:audit": "npm run security:check && npm run security:test",
238
- "test": "npm run security:test",
239
- "test:all": "npm run security:audit",
240
- "release:reset": "node scripts/reset-release-state.js",
241
- "prepublishOnly": "npm run security:audit",
242
- "prepare": "npm run security:check",
243
- "backup:create": "node main/i18ntk-backup.js create",
244
- "backup:restore": "node main/i18ntk-backup.js restore",
245
- "backup:list": "node main/i18ntk-backup.js list",
246
- "backup:verify": "node main/i18ntk-backup.js verify",
247
- "backup:cleanup": "node main/i18ntk-backup.js cleanup",
248
- "languages:select": "node settings/settings-cli.js",
249
- "languages:list": "node settings/settings-cli.js --list-languages",
250
- "languages:status": "node settings/settings-cli.js --language-status",
251
- "lint:locales": "node scripts/lint-locales.js"
252
- },
253
142
  "engines": {
254
143
  "node": ">=16.0.0",
255
144
  "npm": ">=8.0.0"
@@ -257,47 +146,5 @@
257
146
  "publishConfig": {
258
147
  "access": "public"
259
148
  },
260
- "preferGlobal": true,
261
- "versionInfo": {
262
- "version": "2.5.0",
263
- "releaseDate": "29/04/2026",
264
- "lastUpdated": "29/04/2026",
265
- "maintainer": "Vlad Noskov",
266
- "changelog": "./CHANGELOG.md",
267
- "documentation": "./README.md",
268
- "apiReference": "./docs/api/API_REFERENCE.md",
269
- "majorChanges": [
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."
276
- ],
277
- "breakingChanges": [],
278
- "nextVersion": "2.5.1",
279
- "supportedNodeVersions": ">=16.0.0",
280
- "supportedFrameworks": {
281
- "react-i18next": ">=11.0.0",
282
- "vue-i18n": ">=9.0.0",
283
- "angular-i18n": ">=12.0.0",
284
- "next-i18next": ">=13.0.0",
285
- "nuxt-i18n": ">=8.0.0",
286
- "svelte-i18n": ">=3.0.0",
287
- "sveltekit-i18n": ">=2.0.0",
288
- "react-native-localize": ">=2.0.0",
289
- "expo-localization": ">=14.0.0",
290
- "ionic-angular": ">=6.0.0",
291
- "ember-intl": ">=5.0.0",
292
- "formatjs": ">=2.0.0",
293
- "i18next": ">=21.0.0",
294
- "django": ">=3.0.0",
295
- "flask-babel": ">=2.0.0",
296
- "fastapi": ">=0.70.0",
297
- "spring-boot": ">=2.5.0",
298
- "laravel": ">=8.0.0"
299
- },
300
- "supportPolicy": "Versions earlier than 2.5.0 may be unstable or insecure. Upgrade to 2.5.0 or newer."
301
- },
302
- "_comment": "This package is zero-dependency and uses only native Node.js modules"
149
+ "preferGlobal": true
303
150
  }