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/CHANGELOG.md +366 -0
- package/CODE_OF_CONDUCT.md +133 -0
- package/CONTRIBUTING.md +41 -0
- package/FUNDING.md +5 -0
- package/README.md +38 -16
- package/SECURITY.md +52 -0
- package/main/i18ntk-analyze.js +4 -4
- package/main/i18ntk-scanner.js +14 -12
- package/main/i18ntk-validate.js +25 -18
- package/main/manage/commands/AnalyzeCommand.js +7 -4
- package/main/manage/commands/FixerCommand.js +11 -1
- package/main/manage/commands/ScannerCommand.js +12 -10
- package/main/manage/commands/ValidateCommand.js +21 -17
- package/main/manage/index.js +6 -7
- package/package.json +12 -165
- package/runtime/enhanced.js +64 -10
- package/runtime/i18ntk.d.ts +10 -6
- package/runtime/index.js +45 -22
- package/utils/admin-auth.js +85 -21
- package/utils/config-helper.js +43 -37
- package/utils/config-manager.js +59 -49
- package/utils/config.js +13 -4
- package/utils/env-manager.js +3 -1
- package/utils/i18n-helper.js +41 -13
- package/utils/init-helper.js +23 -21
- package/utils/secure-errors.js +10 -6
- package/utils/security.js +30 -4
- package/utils/setup-enforcer.js +22 -33
- package/utils/watch-locales.js +12 -5
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.
|
package/main/i18ntk-analyze.js
CHANGED
|
@@ -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(
|
|
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
|
|
912
|
-
const pin = await
|
|
913
|
-
const isValid = await
|
|
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'));
|
package/main/i18ntk-scanner.js
CHANGED
|
@@ -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
|
|
186
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
}
|
package/main/i18ntk-validate.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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(
|
|
688
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
|
180
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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(
|
|
669
|
-
|
|
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', {
|
package/main/manage/index.js
CHANGED
|
@@ -765,10 +765,11 @@ class I18nManager {
|
|
|
765
765
|
|
|
766
766
|
function findFiles(dir, results = []) {
|
|
767
767
|
try {
|
|
768
|
-
const items =
|
|
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
|
-
|
|
780
|
-
|
|
781
|
-
if (stat.isDirectory()) {
|
|
780
|
+
if (item.isDirectory()) {
|
|
782
781
|
findFiles(fullPath, results);
|
|
783
|
-
} else if (
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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": "
|
|
111
|
-
"url": "https://
|
|
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
|
-
"
|
|
211
|
-
"
|
|
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
|
}
|