i18ntk 1.6.2 β 1.6.3
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 +49 -8
- package/main/i18ntk-autorun.js +1 -1
- package/main/i18ntk-complete.js +1 -1
- package/main/i18ntk-usage.js +2 -2
- package/package.json +32 -7
- package/scripts/prepublish.js +201 -2
- package/scripts/smoke-pack.js +180 -30
- package/scripts/test-runner.js +3 -3
- package/settings/.i18n-admin-config.json +5 -5
- package/settings/admin-config.json +1 -1
- package/settings/i18ntk-config.json +4 -25
- package/settings/settings-cli.js +1 -1
- package/utils/i18n-helper.js +139 -32
- package/ui-locales/en/common.json +0 -5
package/README.md
CHANGED
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
**Version:** 1.6.
|
|
5
|
+
**Version:** 1.6.3
|
|
6
6
|
**Last Updated:** 2025-08-09
|
|
7
7
|
**GitHub Repository:** [vladnoskv/i18ntk](https://github.com/vladnoskv/i18ntk)
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/i18ntk) [](https://badge.fury.io/js/i18ntk) [](https://nodejs.org/) [](https://www.npmjs.com/package/i18ntk) [](https://github.com/vladnoskv/i18ntk)
|
|
10
10
|
|
|
11
|
-
> **π¨ Latest Update (v1.6.2)**: Critical npm package installation bug resolved from 1.6.0 and 1.6.1.
|
|
12
|
-
|
|
13
11
|
**π The fastest way to manage translations across any framework or vanilla JavaScript projects**
|
|
14
12
|
|
|
15
13
|
**Framework Support:** Auto-detects popular libraries (React i18next, Vue i18n, i18next, Nuxt i18n, Svelte i18n) or works without a framework. i18ntk manages translation files and validationβit does NOT implement translations on pages.
|
|
16
14
|
|
|
17
|
-
> **Zero dependencies** | **
|
|
15
|
+
> **Zero dependencies** | **Works with any framework** | **Enterprise-grade security**
|
|
18
16
|
|
|
19
17
|
> **v1.6.0** - **Ultra-extreme performance improvements to the i18ntk toolkit with 97% speed improvement** β‘ Under 30ms for 200k keys (vs 1.2 seconds), up to 86% package size reduction, zero runtime dependencies.
|
|
20
18
|
|
|
@@ -22,7 +20,7 @@
|
|
|
22
20
|
|
|
23
21
|
```bash
|
|
24
22
|
# Install globally
|
|
25
|
-
npm
|
|
23
|
+
npm i i18ntk
|
|
26
24
|
|
|
27
25
|
# Interactive setup
|
|
28
26
|
npx i18ntk init
|
|
@@ -81,7 +79,7 @@ Configuration is managed through the `settings/i18ntk-config.json` file:
|
|
|
81
79
|
|
|
82
80
|
```json
|
|
83
81
|
{
|
|
84
|
-
"version": "1.6.
|
|
82
|
+
"version": "1.6.3",
|
|
85
83
|
"sourceDir": "./locales",
|
|
86
84
|
"outputDir": "./i18ntk-reports",
|
|
87
85
|
"defaultLanguage": "en",
|
|
@@ -159,7 +157,7 @@ your-project/
|
|
|
159
157
|
|
|
160
158
|
- **Locale files are backed up automatically** before optimization
|
|
161
159
|
- **Use interactive optimizer** for safe locale management
|
|
162
|
-
- **Zero breaking changes** from v1.5.x to v1.6.
|
|
160
|
+
- **Zero breaking changes** from v1.5.x to v1.6.3
|
|
163
161
|
- **All improvements applied automatically** on update
|
|
164
162
|
|
|
165
163
|
## π Support
|
|
@@ -171,4 +169,47 @@ your-project/
|
|
|
171
169
|
|
|
172
170
|
---
|
|
173
171
|
|
|
174
|
-
**Made for the global development community** β€οΈ
|
|
172
|
+
**Made for the global development community** β€οΈ
|
|
173
|
+
|
|
174
|
+
## Migration Guide
|
|
175
|
+
|
|
176
|
+
### Upgrading from Deprecated Versions
|
|
177
|
+
|
|
178
|
+
#### From any version < 1.6.3 (DEPRECATED - use latest version)
|
|
179
|
+
1. **Backup your current configuration**:
|
|
180
|
+
```bash
|
|
181
|
+
cp -r ./.i18ntk ./.i18ntk-backup-$(date +%Y%m%d)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
2. **Install the latest version**:
|
|
185
|
+
```bash
|
|
186
|
+
npm install i18ntk@1.6.3
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
3. **Run configuration migration**:
|
|
190
|
+
```bash
|
|
191
|
+
npx i18ntk@1.6.3 --migrate
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
4. **Verify installation**:
|
|
195
|
+
```bash
|
|
196
|
+
npx i18ntk@1.6.3--validate
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Preserved Features from 1.6.3
|
|
200
|
+
- β
Ultra-extreme performance improvements
|
|
201
|
+
- β
Enhanced security with PIN protection
|
|
202
|
+
- β
Comprehensive backup & recovery
|
|
203
|
+
- β
Edge case handling
|
|
204
|
+
- β
Memory optimization
|
|
205
|
+
- β
Advanced configuration management
|
|
206
|
+
|
|
207
|
+
#### Breaking Changes
|
|
208
|
+
- **None** - 1.6.3 is fully backward compatible
|
|
209
|
+
|
|
210
|
+
### Migration Support
|
|
211
|
+
If you encounter issues during migration:
|
|
212
|
+
1. Check the [troubleshooting guide](docs/TROUBLESHOOTING.md)
|
|
213
|
+
2. Open an issue on [GitHub](https://github.com/vladnoskv/i18ntk/issues)
|
|
214
|
+
3. Join our [Discord community](https://discord.gg/i18ntk)
|
|
215
|
+
|
package/main/i18ntk-autorun.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* i18n Toolkit - Automated Workflow Runner (1.6.
|
|
4
|
+
* i18n Toolkit - Automated Workflow Runner (1.6.3-ready)
|
|
5
5
|
* Executes predefined workflow steps for i18n management.
|
|
6
6
|
* - Deterministic translation loading
|
|
7
7
|
* - Safe config precedence (defaults < constructor < unified/CLI)
|
package/main/i18ntk-complete.js
CHANGED
|
@@ -412,7 +412,7 @@ class I18nCompletionTool {
|
|
|
412
412
|
const { fromMenu = false } = options;
|
|
413
413
|
|
|
414
414
|
SecurityUtils.logSecurityEvent('I18n completion tool started', 'info', {
|
|
415
|
-
version:
|
|
415
|
+
version: this.config.version,
|
|
416
416
|
nodeVersion: process.version,
|
|
417
417
|
platform: process.platform
|
|
418
418
|
});
|
package/main/i18ntk-usage.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* I18N USAGE ANALYSIS TOOLKIT - Version 1.
|
|
3
|
+
* I18N USAGE ANALYSIS TOOLKIT - Version 1.6.3
|
|
4
4
|
*
|
|
5
5
|
* This script analyzes source code to find unused translation keys,
|
|
6
6
|
* missing translations, and provides comprehensive translation completeness analysis.
|
|
7
7
|
*
|
|
8
|
-
* NEW in v1.
|
|
8
|
+
* NEW in v1.6.3:
|
|
9
9
|
* - Modular folder structure support
|
|
10
10
|
* - Recursive translation file discovery
|
|
11
11
|
* - NOT_TRANSLATED analysis
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18ntk",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "i18ntk (i18n Toolkit) - Ultra-extreme performance enterprise-grade internationalization management toolkit with 97% performance improvement (15.38ms for 200k keys), advanced security with PIN protection, comprehensive backup & recovery, and edge case handling for JavaScript/TypeScript projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"author": "Vladimir Noskov (https://github.com/vladnoskv)",
|
|
48
48
|
"type": "commonjs",
|
|
49
49
|
"main": "main/i18ntk-manage.js",
|
|
50
|
+
"exports": {
|
|
51
|
+
".": "./main/i18ntk-manage.js",
|
|
52
|
+
"./ui-locales/*": "./ui-locales/*",
|
|
53
|
+
"./package.json": "./package.json"
|
|
54
|
+
},
|
|
50
55
|
"bin": {
|
|
51
56
|
"i18ntk": "main/i18ntk-manage.js",
|
|
52
57
|
"i18ntk-init": "main/i18ntk-init.js",
|
|
@@ -69,9 +74,19 @@
|
|
|
69
74
|
"settings/",
|
|
70
75
|
"ui-locales/",
|
|
71
76
|
"LICENSE",
|
|
77
|
+
"package.json",
|
|
72
78
|
"README.md"
|
|
73
79
|
],
|
|
80
|
+
"sideEffects": false,
|
|
74
81
|
"scripts": {
|
|
82
|
+
"docs:cleanup-deprecated": "node dev/cleanup-deprecated-notices.js",
|
|
83
|
+
"i18ntk": "node main/i18ntk-manage.js",
|
|
84
|
+
"i18ntk:init": "node main/i18ntk-init.js",
|
|
85
|
+
"i18ntk:analyze": "node main/i18ntk-analyze.js",
|
|
86
|
+
"i18ntk:validate": "main/i18ntk-validate.js",
|
|
87
|
+
"i18ntk:usage": "node main/i18ntk-usage.js",
|
|
88
|
+
"i18ntk:complete": "node main/i18ntk-complete.js",
|
|
89
|
+
"i18ntk-sizing": "main/i18ntk-sizing.js",
|
|
75
90
|
"start": "node main/i18ntk-manage.js",
|
|
76
91
|
"settings": "node settings/settings-cli.js",
|
|
77
92
|
"i18ntk:doctor": "node main/i18ntk-doctor.js",
|
|
@@ -112,7 +127,13 @@
|
|
|
112
127
|
"languages:select": "node settings/settings-cli.js",
|
|
113
128
|
"languages:list": "node settings/settings-cli.js --list-languages",
|
|
114
129
|
"languages:status": "node settings/settings-cli.js --language-status",
|
|
115
|
-
"languages:restore": "echo 'Language restoration - use settings CLI for manual configuration'"
|
|
130
|
+
"languages:restore": "echo 'Language restoration - use settings CLI for manual configuration'",
|
|
131
|
+
"version:update": "node scripts/version-updater.js",
|
|
132
|
+
"version:check": "node scripts/version-checker.js",
|
|
133
|
+
"version:bump": "node scripts/version-bump.js",
|
|
134
|
+
"docs:update-versions": "node scripts/update-docs-versions.js",
|
|
135
|
+
"release:prepare": "npm run version:check && npm run docs:update-versions && npm run prepublishOnly",
|
|
136
|
+
"deprecate:old-versions": "node scripts/deprecate-versions.js"
|
|
116
137
|
},
|
|
117
138
|
"dependencies": {},
|
|
118
139
|
"engines": {
|
|
@@ -123,9 +144,9 @@
|
|
|
123
144
|
},
|
|
124
145
|
"preferGlobal": true,
|
|
125
146
|
"versionInfo": {
|
|
126
|
-
"version": "1.6.
|
|
127
|
-
"releaseDate": "
|
|
128
|
-
"lastUpdated": "
|
|
147
|
+
"version": "1.6.3",
|
|
148
|
+
"releaseDate": "27/07/2025",
|
|
149
|
+
"lastUpdated": "09/08/2025",
|
|
129
150
|
"maintainer": "Vladimir Noskov",
|
|
130
151
|
"changelog": "./CHANGELOG.md",
|
|
131
152
|
"documentation": "./README.md",
|
|
@@ -143,12 +164,16 @@
|
|
|
143
164
|
],
|
|
144
165
|
"breakingChanges": [],
|
|
145
166
|
"deprecations": [
|
|
146
|
-
"<1.5.0",
|
|
147
167
|
"1.0.x",
|
|
148
168
|
"1.1.x",
|
|
149
169
|
"1.2.x",
|
|
150
170
|
"1.3.x",
|
|
151
|
-
"1.4.x"
|
|
171
|
+
"1.4.x",
|
|
172
|
+
"1.5.x",
|
|
173
|
+
"1.6.0",
|
|
174
|
+
"1.6.1",
|
|
175
|
+
"1.6.2"
|
|
176
|
+
|
|
152
177
|
],
|
|
153
178
|
"nextVersion": "1.7.0",
|
|
154
179
|
"supportedNodeVersions": ">=16.0.0",
|
package/scripts/prepublish.js
CHANGED
|
@@ -26,6 +26,37 @@ class PrepublishCleaner {
|
|
|
26
26
|
'npm-debug.log',
|
|
27
27
|
'yarn-error.log'
|
|
28
28
|
];
|
|
29
|
+
|
|
30
|
+
// Essential files that must exist for release
|
|
31
|
+
this.essentialFiles = [
|
|
32
|
+
'package.json',
|
|
33
|
+
'main/i18ntk-manage.js',
|
|
34
|
+
'main/i18ntk-init.js',
|
|
35
|
+
'main/i18ntk-analyze.js',
|
|
36
|
+
'main/i18ntk-validate.js',
|
|
37
|
+
'main/i18ntk-usage.js',
|
|
38
|
+
'main/i18ntk-summary.js',
|
|
39
|
+
'main/i18ntk-sizing.js',
|
|
40
|
+
'main/i18ntk-complete.js',
|
|
41
|
+
'main/i18ntk-ui.js',
|
|
42
|
+
'main/i18ntk-autorun.js',
|
|
43
|
+
'utils/i18n-helper.js',
|
|
44
|
+
'utils/security.js',
|
|
45
|
+
'settings/settings-manager.js',
|
|
46
|
+
'settings/settings-cli.js',
|
|
47
|
+
'settings/i18ntk-config.json'
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Essential locale files
|
|
51
|
+
this.essentialLocales = [
|
|
52
|
+
'ui-locales/en.json',
|
|
53
|
+
'ui-locales/es.json',
|
|
54
|
+
'ui-locales/fr.json',
|
|
55
|
+
'ui-locales/de.json',
|
|
56
|
+
'ui-locales/ja.json',
|
|
57
|
+
'ui-locales/ru.json',
|
|
58
|
+
'ui-locales/zh.json'
|
|
59
|
+
];
|
|
29
60
|
}
|
|
30
61
|
|
|
31
62
|
log(message) {
|
|
@@ -33,7 +64,16 @@ class PrepublishCleaner {
|
|
|
33
64
|
}
|
|
34
65
|
|
|
35
66
|
async clean() {
|
|
36
|
-
this.log('Starting
|
|
67
|
+
this.log('Starting comprehensive pre-publish validation...');
|
|
68
|
+
|
|
69
|
+
// Validate essential files exist
|
|
70
|
+
await this.validateEssentialFiles();
|
|
71
|
+
|
|
72
|
+
// Validate locale files
|
|
73
|
+
await this.validateLocaleFiles();
|
|
74
|
+
|
|
75
|
+
// Validate package.json
|
|
76
|
+
await this.validatePackageJson();
|
|
37
77
|
|
|
38
78
|
// Clean directories
|
|
39
79
|
for (const dir of this.directories) {
|
|
@@ -48,7 +88,10 @@ class PrepublishCleaner {
|
|
|
48
88
|
// Reset security settings
|
|
49
89
|
await this.resetSecuritySettings();
|
|
50
90
|
|
|
51
|
-
|
|
91
|
+
// Final validation
|
|
92
|
+
await this.finalValidation();
|
|
93
|
+
|
|
94
|
+
this.log('Pre-publish validation completed successfully!');
|
|
52
95
|
}
|
|
53
96
|
|
|
54
97
|
async cleanDirectory(dirPath) {
|
|
@@ -116,6 +159,162 @@ class PrepublishCleaner {
|
|
|
116
159
|
}
|
|
117
160
|
}
|
|
118
161
|
|
|
162
|
+
async validateEssentialFiles() {
|
|
163
|
+
this.log('Validating essential files...');
|
|
164
|
+
|
|
165
|
+
let missingFiles = [];
|
|
166
|
+
for (const file of this.essentialFiles) {
|
|
167
|
+
const filePath = path.join(this.projectRoot, file);
|
|
168
|
+
if (!fs.existsSync(filePath)) {
|
|
169
|
+
missingFiles.push(file);
|
|
170
|
+
} else if (!fs.statSync(filePath).isFile()) {
|
|
171
|
+
this.log(`β ${file} is not a file`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (missingFiles.length > 0) {
|
|
177
|
+
this.log(`β Missing essential files: ${missingFiles.join(', ')}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.log('β
All essential files present');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async validateLocaleFiles() {
|
|
185
|
+
this.log('Validating locale files...');
|
|
186
|
+
|
|
187
|
+
let invalidFiles = [];
|
|
188
|
+
for (const localeFile of this.essentialLocales) {
|
|
189
|
+
const filePath = path.join(this.projectRoot, localeFile);
|
|
190
|
+
if (!fs.existsSync(filePath)) {
|
|
191
|
+
invalidFiles.push(localeFile);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
197
|
+
const parsed = JSON.parse(content);
|
|
198
|
+
|
|
199
|
+
// Validate structure
|
|
200
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
201
|
+
invalidFiles.push(`${localeFile}: Invalid structure`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check for required keys
|
|
205
|
+
if (!parsed.settings || !parsed.settings.title) {
|
|
206
|
+
invalidFiles.push(`${localeFile}: Missing required keys`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} catch (e) {
|
|
210
|
+
invalidFiles.push(`${localeFile}: ${e.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (invalidFiles.length > 0) {
|
|
215
|
+
this.log(`β Invalid locale files: ${invalidFiles.join(', ')}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.log('β
All locale files valid');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async validatePackageJson() {
|
|
223
|
+
this.log('Validating package.json...');
|
|
224
|
+
|
|
225
|
+
const packagePath = path.join(this.projectRoot, 'package.json');
|
|
226
|
+
try {
|
|
227
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
228
|
+
|
|
229
|
+
// Validate required fields
|
|
230
|
+
const requiredFields = ['name', 'version', 'description', 'main', 'bin', 'files'];
|
|
231
|
+
for (const field of requiredFields) {
|
|
232
|
+
if (!pkg[field]) {
|
|
233
|
+
this.log(`β package.json missing required field: ${field}`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Validate version format
|
|
239
|
+
if (!/^\d+\.\d+\.\d+/.test(pkg.version)) {
|
|
240
|
+
this.log('β Invalid version format');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Validate bin entries
|
|
245
|
+
const requiredBinEntries = [
|
|
246
|
+
'i18ntk', 'i18ntk-init', 'i18ntk-analyze', 'i18ntk-validate',
|
|
247
|
+
'i18ntk-usage', 'i18ntk-summary', 'i18ntk-sizing', 'i18ntk-complete',
|
|
248
|
+
'i18ntk-ui', 'i18ntk-autorun'
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
for (const bin of requiredBinEntries) {
|
|
252
|
+
if (!pkg.bin || !pkg.bin[bin]) {
|
|
253
|
+
this.log(`β Missing bin entry: ${bin}`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const binPath = path.join(this.projectRoot, pkg.bin[bin]);
|
|
258
|
+
if (!fs.existsSync(binPath)) {
|
|
259
|
+
this.log(`β Missing bin script: ${pkg.bin[bin]}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.log('β
package.json validated');
|
|
265
|
+
} catch (e) {
|
|
266
|
+
this.log(`β Invalid package.json: ${e.message}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async finalValidation() {
|
|
272
|
+
this.log('Running final validation checks...');
|
|
273
|
+
|
|
274
|
+
// Check for development artifacts
|
|
275
|
+
const devArtifacts = [
|
|
276
|
+
'dev/debug',
|
|
277
|
+
'benchmarks',
|
|
278
|
+
'.github',
|
|
279
|
+
'test-usage-fix.html',
|
|
280
|
+
'.i18ntk'
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
for (const artifact of devArtifacts) {
|
|
284
|
+
const artifactPath = path.join(this.projectRoot, artifact);
|
|
285
|
+
if (fs.existsSync(artifactPath)) {
|
|
286
|
+
this.log(`β οΈ Development artifact found: ${artifact}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate file permissions for executable scripts
|
|
291
|
+
const scripts = [
|
|
292
|
+
'main/i18ntk-manage.js',
|
|
293
|
+
'main/i18ntk-init.js',
|
|
294
|
+
'main/i18ntk-analyze.js',
|
|
295
|
+
'main/i18ntk-validate.js',
|
|
296
|
+
'main/i18ntk-usage.js',
|
|
297
|
+
'main/i18ntk-summary.js',
|
|
298
|
+
'main/i18ntk-sizing.js',
|
|
299
|
+
'main/i18ntk-complete.js',
|
|
300
|
+
'main/i18ntk-ui.js',
|
|
301
|
+
'main/i18ntk-autorun.js'
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
for (const script of scripts) {
|
|
305
|
+
const scriptPath = path.join(this.projectRoot, script);
|
|
306
|
+
if (fs.existsSync(scriptPath)) {
|
|
307
|
+
try {
|
|
308
|
+
fs.accessSync(scriptPath, fs.constants.X_OK);
|
|
309
|
+
} catch (e) {
|
|
310
|
+
this.log(`β οΈ Script not executable: ${script}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.log('β
Final validation complete');
|
|
316
|
+
}
|
|
317
|
+
|
|
119
318
|
async resetSecuritySettings() {
|
|
120
319
|
const configPath = path.join(this.projectRoot, 'settings', '.i18n-admin-config.json');
|
|
121
320
|
|
package/scripts/smoke-pack.js
CHANGED
|
@@ -19,41 +19,191 @@ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'i18ntk-smoke-'));
|
|
|
19
19
|
run('npm init -y', { cwd: tmp });
|
|
20
20
|
run(`npm i "${tarballPath}" --silent`, { cwd: tmp });
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log('
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const installed = path.join(tmp, 'node_modules', 'i18ntk');
|
|
23
|
+
console.log('π¦ Package installed at:', installed);
|
|
24
|
+
console.log('π Temp directory:', tmp);
|
|
25
|
+
|
|
26
|
+
// Extended validation - 1) Directory structure validation
|
|
27
|
+
console.log('\nπ Validating package directory structure...');
|
|
28
|
+
const requiredDirs = ['main', 'utils', 'settings', 'ui-locales'];
|
|
29
|
+
const requiredFiles = [
|
|
30
|
+
'ui-locales/en.json',
|
|
31
|
+
'ui-locales/es.json',
|
|
32
|
+
'ui-locales/fr.json',
|
|
33
|
+
'ui-locales/de.json',
|
|
34
|
+
'ui-locales/ja.json',
|
|
35
|
+
'ui-locales/ru.json',
|
|
36
|
+
'ui-locales/zh.json'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
requiredDirs.forEach(dir => {
|
|
40
|
+
const p = path.join(installed, dir);
|
|
41
|
+
if (!fs.existsSync(p)) {
|
|
42
|
+
console.error('β Missing packaged directory:', dir);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
45
|
+
if (!fs.statSync(p).isDirectory()) {
|
|
46
|
+
console.error('β Not a directory:', dir);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
requiredFiles.forEach(file => {
|
|
52
|
+
const p = path.join(installed, file);
|
|
53
|
+
if (!fs.existsSync(p)) {
|
|
54
|
+
console.error('β Missing required locale file:', file);
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
30
57
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
58
|
+
// Validate file content
|
|
59
|
+
try {
|
|
60
|
+
const content = fs.readFileSync(p, 'utf8');
|
|
61
|
+
JSON.parse(content);
|
|
62
|
+
console.log(`β
${file} validated (${Math.round(content.length/1024)}KB)`);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error(`β Invalid JSON in ${file}:`, e.message);
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Extended validation - 2) Detect stray locale folders
|
|
70
|
+
console.log('\nπ Checking for stray locale folders...');
|
|
71
|
+
const unwantedDirs = [
|
|
72
|
+
'ui-locales/en',
|
|
73
|
+
'ui-locales/es',
|
|
74
|
+
'ui-locales/fr',
|
|
75
|
+
'ui-locales/de',
|
|
76
|
+
'ui-locales/ja',
|
|
77
|
+
'ui-locales/ru',
|
|
78
|
+
'ui-locales/zh'
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
unwantedDirs.forEach(dir => {
|
|
82
|
+
const p = path.join(installed, dir);
|
|
83
|
+
if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
|
|
84
|
+
console.error('β Unexpected directory in package:', dir);
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Extended validation - 3) Check for essential core files
|
|
90
|
+
console.log('\nπ Validating essential core files...');
|
|
91
|
+
const essentialFiles = [
|
|
92
|
+
'main/i18ntk-manage.js',
|
|
93
|
+
'utils/i18n-helper.js',
|
|
94
|
+
'utils/security.js',
|
|
95
|
+
'settings/settings-manager.js',
|
|
96
|
+
'package.json'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
essentialFiles.forEach(file => {
|
|
100
|
+
const p = path.join(installed, file);
|
|
101
|
+
if (!fs.existsSync(p)) {
|
|
102
|
+
console.error('β Missing essential file:', file);
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Extended validation - 4) Validate file permissions
|
|
108
|
+
console.log('\nπ Validating file permissions...');
|
|
109
|
+
try {
|
|
110
|
+
const mainScript = path.join(installed, 'main/i18ntk-manage.js');
|
|
111
|
+
fs.accessSync(mainScript, fs.constants.X_OK);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error('β Main script not executable:', e.message);
|
|
114
|
+
process.exit(2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Extended validation - 5) Create various broken locale scenarios
|
|
118
|
+
console.log('\nπ Testing locale resolution with broken files...');
|
|
119
|
+
const testScenarios = [
|
|
120
|
+
{ name: 'invalid-json', content: '{ invalid json', dir: 'ui-locales' },
|
|
121
|
+
{ name: 'empty-file', content: '', dir: 'ui-locales' },
|
|
122
|
+
{ name: 'non-json', content: 'This is not JSON', dir: 'ui-locales' },
|
|
123
|
+
{ name: 'directory-instead', content: null, dir: 'ui-locales/en.json' }
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
testScenarios.forEach(scenario => {
|
|
127
|
+
const targetPath = path.join(tmp, scenario.dir, `${scenario.name}.json`);
|
|
34
128
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log('ui-locales path:', uiLocalesPath);
|
|
39
|
-
console.log('ui-locales exists:', fs.existsSync(uiLocalesPath));
|
|
40
|
-
if (fs.existsSync(uiLocalesPath)) {
|
|
41
|
-
console.log('ui-locales contents:', fs.readdirSync(uiLocalesPath));
|
|
42
|
-
}
|
|
129
|
+
if (scenario.content === null) {
|
|
130
|
+
// Create directory instead of file
|
|
131
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
43
132
|
} else {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
133
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
134
|
+
fs.writeFileSync(targetPath, scenario.content, 'utf8');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Extended validation - 6) Test translation loading with broken locale files
|
|
139
|
+
console.log('\nπ Testing translation loading with broken locale files...');
|
|
140
|
+
const testScripts = [
|
|
141
|
+
// Test 1: Load with broken project locales (should fallback to bundled)
|
|
142
|
+
"const fs=require('fs'); const path=require('path'); const content=fs.readFileSync(path.join(__dirname,'node_modules/i18ntk/ui-locales/en.json'),'utf8'); const translations=JSON.parse(content); console.log('β
Fallback to bundled locales works:', !!translations.settings.title);",
|
|
143
|
+
|
|
144
|
+
// Test 2: Test all supported languages
|
|
145
|
+
"const fs=require('fs'); const path=require('path'); const langs=['en','es','fr','de','ja','ru','zh']; langs.forEach(l=>{const content=fs.readFileSync(path.join(__dirname,'node_modules/i18ntk/ui-locales',l+'.json'),'utf8'); const tr=JSON.parse(content); if(!tr||!tr.settings||!tr.settings.title){console.error('β Failed to load language:',l);process.exit(5)}}); console.log('β
All languages loaded successfully');",
|
|
146
|
+
|
|
147
|
+
// Test 3: Test locale files exist and are valid
|
|
148
|
+
"const fs=require('fs'); const path=require('path'); const langs=['en','es','fr','de','ja','ru','zh']; langs.forEach(l=>{const filePath=path.join(__dirname,'node_modules/i18ntk/ui-locales',l+'.json'); if(!fs.existsSync(filePath)){console.error('β Missing locale file:',l);process.exit(6)} try{JSON.parse(fs.readFileSync(filePath,'utf8'))}catch(e){console.error('β Invalid JSON in:',l,e.message);process.exit(6)}}); console.log('β
All locale files are valid');"
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
testScripts.forEach((script, index) => {
|
|
152
|
+
try {
|
|
153
|
+
execSync(`node -e "${script}"`, { cwd: tmp, stdio: 'inherit' });
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`β Test ${index + 1} failed:`, error.message);
|
|
156
|
+
process.exit(3 + index);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Extended validation - 7) Validate package size constraints
|
|
161
|
+
console.log('\nπ Validating package size constraints...');
|
|
162
|
+
const maxFileSizeKB = 200; // 200KB per file max
|
|
163
|
+
const maxTotalSizeMB = 5; // 5MB total max
|
|
164
|
+
|
|
165
|
+
let totalSize = 0;
|
|
166
|
+
function checkFileSize(dir) {
|
|
167
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
168
|
+
for (const file of files) {
|
|
169
|
+
const fullPath = path.join(dir, file.name);
|
|
170
|
+
if (file.isDirectory()) {
|
|
171
|
+
checkFileSize(fullPath);
|
|
172
|
+
} else {
|
|
173
|
+
const stats = fs.statSync(fullPath);
|
|
174
|
+
const sizeKB = stats.size / 1024;
|
|
175
|
+
totalSize += stats.size;
|
|
176
|
+
|
|
177
|
+
if (sizeKB > maxFileSizeKB && !file.name.endsWith('.md')) {
|
|
178
|
+
console.warn(`β οΈ Large file detected: ${file.name} (${Math.round(sizeKB)}KB)`);
|
|
179
|
+
}
|
|
50
180
|
}
|
|
51
181
|
}
|
|
52
|
-
process.exit(2);
|
|
53
182
|
}
|
|
54
183
|
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
184
|
+
checkFileSize(installed);
|
|
185
|
+
const totalSizeMB = totalSize / (1024 * 1024);
|
|
186
|
+
console.log(`π Total package size: ${Math.round(totalSizeMB)}MB`);
|
|
187
|
+
|
|
188
|
+
if (totalSizeMB > maxTotalSizeMB) {
|
|
189
|
+
console.error('β Package exceeds size limit:', `${Math.round(totalSizeMB)}MB > ${maxTotalSizeMB}MB`);
|
|
190
|
+
process.exit(6);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extended validation - 8) Clean up and final verification
|
|
194
|
+
console.log('\nπ§Ή Cleaning up test files...');
|
|
195
|
+
testScenarios.forEach(scenario => {
|
|
196
|
+
const targetPath = path.join(tmp, scenario.dir, `${scenario.name}.json`);
|
|
197
|
+
try {
|
|
198
|
+
if (fs.existsSync(targetPath)) {
|
|
199
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// Ignore cleanup errors
|
|
203
|
+
}
|
|
204
|
+
});
|
|
58
205
|
|
|
59
|
-
console.log('β
smoke
|
|
206
|
+
console.log('\nβ
All extended smoke tests passed!');
|
|
207
|
+
console.log('π¦ Package validation complete');
|
|
208
|
+
console.log('π Locale resolution working correctly');
|
|
209
|
+
console.log('π§ Broken file handling verified');
|
package/scripts/test-runner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Test Runner for i18n Management Toolkit v1.6.
|
|
4
|
+
* Test Runner for i18n Management Toolkit v1.6.3
|
|
5
5
|
* Runs all test suites and provides comprehensive reporting
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -24,7 +24,7 @@ class TestRunner {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async runAllTests() {
|
|
27
|
-
console.log('π i18n Management Toolkit v1.6.
|
|
27
|
+
console.log('π i18n Management Toolkit v1.6.3 Test Runner');
|
|
28
28
|
console.log('='.repeat(80));
|
|
29
29
|
console.log(`Started: ${this.startTime.toLocaleString()}`);
|
|
30
30
|
console.log();
|
|
@@ -125,7 +125,7 @@ class TestRunner {
|
|
|
125
125
|
|
|
126
126
|
// Generate detailed report
|
|
127
127
|
const report = {
|
|
128
|
-
version: '1.6.
|
|
128
|
+
version: '1.6.3',
|
|
129
129
|
timestamp: endTime.toISOString(),
|
|
130
130
|
totalTests: this.testSuites.length,
|
|
131
131
|
passed,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enabled":
|
|
3
|
-
"pinHash":
|
|
4
|
-
"salt":
|
|
5
|
-
"createdAt": "2025-08-
|
|
6
|
-
"lastModified": "2025-08-
|
|
2
|
+
"enabled": false,
|
|
3
|
+
"pinHash": null,
|
|
4
|
+
"salt": null,
|
|
5
|
+
"createdAt": "2025-08-09T18:55:16.345Z",
|
|
6
|
+
"lastModified": "2025-08-09T19:49:47.461Z"
|
|
7
7
|
}
|
|
@@ -126,17 +126,17 @@
|
|
|
126
126
|
"stringInterning": true
|
|
127
127
|
},
|
|
128
128
|
"backup": {
|
|
129
|
-
"enabled":
|
|
129
|
+
"enabled": false,
|
|
130
130
|
"singleFileMode": false,
|
|
131
131
|
"singleBackupFile": "i18ntk-central-backup.json",
|
|
132
132
|
"retentionDays": 30,
|
|
133
133
|
"maxBackups": 100
|
|
134
134
|
},
|
|
135
135
|
"security": {
|
|
136
|
-
"adminPinEnabled":
|
|
136
|
+
"adminPinEnabled": false,
|
|
137
137
|
"adminPinPromptOnInit": true,
|
|
138
138
|
"keepAuthenticatedUntilExit": true,
|
|
139
|
-
"sessionTimeout":
|
|
139
|
+
"sessionTimeout": 30,
|
|
140
140
|
"maxFailedAttempts": 3,
|
|
141
141
|
"lockoutDuration": 15,
|
|
142
142
|
"enablePathValidation": true,
|
|
@@ -194,26 +194,5 @@
|
|
|
194
194
|
"autoSave": true,
|
|
195
195
|
"dateFormat": "DD/MM/YYYY",
|
|
196
196
|
"timeFormat": "24h",
|
|
197
|
-
"timezone": "auto"
|
|
198
|
-
"version": "1.6.2",
|
|
199
|
-
"defaultLanguage": "en",
|
|
200
|
-
"supportedLanguages": [
|
|
201
|
-
"en",
|
|
202
|
-
"es",
|
|
203
|
-
"fr",
|
|
204
|
-
"de",
|
|
205
|
-
"ja",
|
|
206
|
-
"ru",
|
|
207
|
-
"zh"
|
|
208
|
-
],
|
|
209
|
-
"performance": {
|
|
210
|
-
"mode": "extreme",
|
|
211
|
-
"cacheEnabled": true,
|
|
212
|
-
"batchSize": 1000,
|
|
213
|
-
"streaming": true,
|
|
214
|
-
"compression": "brotli",
|
|
215
|
-
"parallelProcessing": true,
|
|
216
|
-
"memoryLimit": "256MB",
|
|
217
|
-
"gcInterval": 250
|
|
218
|
-
}
|
|
197
|
+
"timezone": "auto"
|
|
219
198
|
}
|
package/settings/settings-cli.js
CHANGED
|
@@ -1232,7 +1232,7 @@ class SettingsCLI {
|
|
|
1232
1232
|
case '3':
|
|
1233
1233
|
exportData = JSON.stringify({
|
|
1234
1234
|
exportedAt: new Date().toISOString(),
|
|
1235
|
-
version: this.settings.version || '1.
|
|
1235
|
+
version: this.settings.version || '1.6.3 (DEPRECATED - use latest version) ',
|
|
1236
1236
|
settings: this.settings
|
|
1237
1237
|
}, null, 2);
|
|
1238
1238
|
break;
|
package/utils/i18n-helper.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
|
|
5
|
+
// Helper functions for OS-agnostic path handling
|
|
6
|
+
function toPosix(p) { return String(p).replace(/\\/g, '/'); }
|
|
7
|
+
function isBundledPath(p) {
|
|
8
|
+
const s = toPosix(p);
|
|
9
|
+
return s.includes('/node_modules/i18ntk/') || s.includes('/i18ntk/ui-locales/');
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
function safeRequireConfig() {
|
|
6
13
|
try { return require('./config-manager'); } catch { return null; }
|
|
7
14
|
}
|
|
@@ -24,9 +31,8 @@ function pkgUiLocalesDirViaThisFile() {
|
|
|
24
31
|
|
|
25
32
|
function pkgUiLocalesDirViaResolve() {
|
|
26
33
|
try {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
return path.join(root, 'ui-locales');
|
|
34
|
+
const enJson = require.resolve('i18ntk/ui-locales/en.json');
|
|
35
|
+
return path.dirname(enJson);
|
|
30
36
|
} catch { return null; }
|
|
31
37
|
}
|
|
32
38
|
|
|
@@ -36,44 +42,114 @@ function projectUiLocalesDir() {
|
|
|
36
42
|
|
|
37
43
|
function resolveLocalesDirs(baseDir) {
|
|
38
44
|
const dirs = [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const addDir = (dir, source) => {
|
|
46
|
+
if (typeof dir === 'string' && dir.trim()) {
|
|
47
|
+
try {
|
|
48
|
+
const normalized = path.normalize(path.resolve(dir.trim()));
|
|
49
|
+
// Skip if directory doesn't exist or isn't accessible
|
|
50
|
+
if (fs.existsSync(normalized) && fs.statSync(normalized).isDirectory()) {
|
|
51
|
+
dirs.push({ path: normalized, source });
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Silently ignore invalid paths
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Priority 1: Environment override (highest priority)
|
|
60
|
+
if (process.env.I18NTK_UI_LOCALE_DIR && process.env.I18NTK_UI_LOCALE_DIR.trim()) {
|
|
61
|
+
addDir(process.env.I18NTK_UI_LOCALE_DIR, 'env');
|
|
62
|
+
}
|
|
42
63
|
|
|
64
|
+
// Priority 2: Settings configuration
|
|
43
65
|
const cfg = safeRequireConfig();
|
|
44
66
|
if (cfg) {
|
|
45
67
|
try {
|
|
46
68
|
const settings = cfg.getConfig?.() || {};
|
|
47
|
-
if (typeof settings.uiLocalesDir === 'string' && settings.uiLocalesDir.trim())
|
|
48
|
-
|
|
69
|
+
if (typeof settings.uiLocalesDir === 'string' && settings.uiLocalesDir.trim()) {
|
|
70
|
+
addDir(settings.uiLocalesDir, 'settings');
|
|
71
|
+
}
|
|
49
72
|
} catch {}
|
|
50
73
|
}
|
|
51
74
|
|
|
52
|
-
|
|
75
|
+
// Priority 3: Bundled package directories (preferred over project)
|
|
53
76
|
const pkgA = pkgUiLocalesDirViaThisFile();
|
|
54
|
-
|
|
77
|
+
addDir(pkgA, 'bundled');
|
|
78
|
+
|
|
55
79
|
const pkgB = pkgUiLocalesDirViaResolve();
|
|
56
|
-
if (pkgB && pkgB !== pkgA)
|
|
80
|
+
if (pkgB && pkgB !== pkgA) {
|
|
81
|
+
addDir(pkgB, 'bundled');
|
|
82
|
+
}
|
|
57
83
|
|
|
58
|
-
|
|
84
|
+
// Priority 4: Project directory (fallback)
|
|
85
|
+
if (typeof baseDir === 'string' && baseDir.trim()) {
|
|
86
|
+
const resolved = path.resolve(baseDir.trim());
|
|
87
|
+
const dirPath = fs.existsSync(resolved) && fs.statSync(resolved).isFile()
|
|
88
|
+
? path.dirname(resolved)
|
|
89
|
+
: resolved;
|
|
90
|
+
addDir(dirPath, 'project');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
addDir(projectUiLocalesDir(), 'project');
|
|
94
|
+
|
|
95
|
+
// Deduplicate by path while preserving priority order
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
const uniqueDirs = [];
|
|
98
|
+
for (const { path: dirPath, source } of dirs) {
|
|
99
|
+
if (!seen.has(dirPath)) {
|
|
100
|
+
seen.add(dirPath);
|
|
101
|
+
uniqueDirs.push(dirPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return uniqueDirs;
|
|
59
106
|
}
|
|
60
107
|
|
|
61
108
|
function candidatesForLang(dir, lang) {
|
|
62
109
|
return [
|
|
63
|
-
path.join(dir, `${lang}.json`),
|
|
64
|
-
path.join(dir, lang,
|
|
65
|
-
path.join(dir, lang, 'index.json')
|
|
110
|
+
path.join(dir, `${lang}.json`), // ui-locales/en.json
|
|
111
|
+
path.join(dir, lang, 'index.json') // ui-locales/en/index.json
|
|
66
112
|
];
|
|
67
113
|
}
|
|
68
114
|
|
|
69
|
-
function
|
|
115
|
+
function findLocaleFilesAllDirs(lang, baseDir) {
|
|
70
116
|
const dirs = resolveLocalesDirs(baseDir);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
117
|
+
|
|
118
|
+
if (process.env.I18NTK_DEBUG_LOCALES === '1') {
|
|
119
|
+
console.log('π i18ntk locale search dirs:', dirs);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const files = [];
|
|
123
|
+
const errors = [];
|
|
124
|
+
|
|
125
|
+
for (const dir of dirs) {
|
|
126
|
+
for (const candidate of candidatesForLang(dir, lang)) {
|
|
127
|
+
try {
|
|
128
|
+
if (fs.existsSync(candidate)) {
|
|
129
|
+
const stats = fs.statSync(candidate);
|
|
130
|
+
if (stats.isFile() && stats.size > 0) {
|
|
131
|
+
// Validate file is readable and parseable
|
|
132
|
+
fs.accessSync(candidate, fs.constants.R_OK);
|
|
133
|
+
// Quick JSON validation
|
|
134
|
+
const content = fs.readFileSync(candidate, 'utf8');
|
|
135
|
+
if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
|
|
136
|
+
files.push(candidate);
|
|
137
|
+
} else {
|
|
138
|
+
errors.push({ file: candidate, error: 'Invalid JSON format' });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
errors.push({ file: candidate, error: error.message });
|
|
144
|
+
}
|
|
74
145
|
}
|
|
75
146
|
}
|
|
76
|
-
|
|
147
|
+
|
|
148
|
+
if (process.env.I18NTK_DEBUG_LOCALES === '1' && errors.length > 0) {
|
|
149
|
+
console.warn(`β οΈ Locale resolution errors for ${lang}:`, errors);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return files;
|
|
77
153
|
}
|
|
78
154
|
|
|
79
155
|
let translations = {};
|
|
@@ -89,22 +165,49 @@ function loadTranslations(language, baseDir) {
|
|
|
89
165
|
const short = requested.split('-')[0].toLowerCase();
|
|
90
166
|
const tryOrder = [requested, short, 'en'];
|
|
91
167
|
|
|
168
|
+
const loadErrors = [];
|
|
169
|
+
|
|
92
170
|
for (const lang of tryOrder) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
171
|
+
const files = findLocaleFilesAllDirs(lang, baseDir);
|
|
172
|
+
|
|
173
|
+
// Prioritize bundled locales over project ones
|
|
174
|
+
const prioritizedFiles = files.sort((a, b) => Number(isBundledPath(b)) - Number(isBundledPath(a)));
|
|
175
|
+
|
|
176
|
+
for (const file of prioritizedFiles) {
|
|
177
|
+
try {
|
|
178
|
+
translations = readJsonSafe(file);
|
|
179
|
+
currentLanguage = lang;
|
|
180
|
+
isInitialized = true;
|
|
181
|
+
|
|
182
|
+
if (process.env.I18NTK_DEBUG_LOCALES === '1') {
|
|
183
|
+
console.log(`π Loaded UI locale β ${file} (${lang})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Validate translations object
|
|
187
|
+
if (typeof translations === 'object' && translations !== null) {
|
|
188
|
+
return translations;
|
|
189
|
+
} else {
|
|
190
|
+
loadErrors.push({ file, error: 'Invalid translation format' });
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
loadErrors.push({ file, error: e.message });
|
|
194
|
+
if (process.env.I18NTK_DEBUG_LOCALES === '1') {
|
|
195
|
+
console.warn(`β οΈ Failed to parse ${file}: ${e.message}`);
|
|
196
|
+
}
|
|
101
197
|
}
|
|
102
|
-
return translations;
|
|
103
|
-
} catch (e) {
|
|
104
|
-
console.warn(`β οΈ Failed to parse ${file}: ${e.message}. Trying next fallback...`);
|
|
105
198
|
}
|
|
106
199
|
}
|
|
107
200
|
|
|
201
|
+
// Log comprehensive error summary if debugging
|
|
202
|
+
if (process.env.I18NTK_DEBUG_LOCALES === '1' && loadErrors.length > 0) {
|
|
203
|
+
console.warn(`π Locale loading errors summary:`, {
|
|
204
|
+
requested: requested,
|
|
205
|
+
triedLanguages: tryOrder,
|
|
206
|
+
errors: loadErrors
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fallback to built-in minimal translations
|
|
108
211
|
translations = {
|
|
109
212
|
menu: {
|
|
110
213
|
title: 'π i18ntk - I18N Management',
|
|
@@ -130,7 +233,11 @@ function loadTranslations(language, baseDir) {
|
|
|
130
233
|
};
|
|
131
234
|
currentLanguage = 'en';
|
|
132
235
|
isInitialized = true;
|
|
133
|
-
|
|
236
|
+
|
|
237
|
+
if (loadErrors.length > 0) {
|
|
238
|
+
console.warn(`β οΈ No valid UI locale files found. Using built-in English strings.`);
|
|
239
|
+
}
|
|
240
|
+
|
|
134
241
|
return translations;
|
|
135
242
|
}
|
|
136
243
|
|