i18ntk 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -32
- package/main/i18ntk-analyze.js +59 -54
- package/main/i18ntk-ui.js +96 -49
- package/main/manage/commands/AnalyzeCommand.js +59 -54
- package/main/manage/index.js +140 -71
- package/package.json +290 -290
- package/scripts/fix-all-i18n.js +41 -20
- package/scripts/fix-and-purify-i18n.js +43 -23
- package/scripts/prepublish-dev.js +221 -0
- package/scripts/prepublish.js +155 -141
- package/scripts/validate-all-translations.js +190 -134
- package/ui-locales/de.json +149 -155
- package/ui-locales/en.json +1 -7
- package/ui-locales/es.json +159 -173
- package/ui-locales/fr.json +143 -150
- package/ui-locales/ja.json +181 -233
- package/ui-locales/ru.json +133 -185
- package/ui-locales/zh.json +168 -175
- package/utils/cli-helper.js +26 -98
- package/utils/extractors/regex.js +39 -12
- package/utils/i18n-helper.js +181 -128
- package/utils/security-check-improved.js +16 -13
- package/utils/security-fixed.js +6 -4
- package/utils/security.js +6 -4
- package/main/manage/services/ConfigurationService-fixed.js +0 -449
package/scripts/fix-all-i18n.js
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* - Optional: prune extra keys not present in EN
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const SecurityUtils = require('../utils/security');
|
|
13
14
|
|
|
14
15
|
const argv = Object.fromEntries(
|
|
15
16
|
process.argv.slice(2).map(a => {
|
|
@@ -37,11 +38,11 @@ const KEY_PATTERNS = [
|
|
|
37
38
|
const EXCLUDE_DIRS = new Set(['node_modules', '.git', path.basename(I18N_DIR)]);
|
|
38
39
|
|
|
39
40
|
function readUTF8(p) { try { return SecurityUtils.safeReadFileSync(p, path.dirname(p), 'utf8'); } catch { return null; } }
|
|
40
|
-
function writeJSON(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true }); SecurityUtils.safeWriteFileSync(p, JSON.stringify(obj, null, 2) + '\n', 'utf8'); }
|
|
41
|
+
function writeJSON(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true }); SecurityUtils.safeWriteFileSync(p, JSON.stringify(obj, null, 2) + '\n', path.dirname(p), 'utf8'); }
|
|
41
42
|
function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
|
|
42
43
|
function isFile(p) { try { return fs.statSync(p).isFile(); } catch { return false; } }
|
|
43
44
|
|
|
44
|
-
function listFilesRecursive(dir, exts = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']) {
|
|
45
|
+
function listFilesRecursive(dir, exts = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']) {
|
|
45
46
|
const out = [];
|
|
46
47
|
(function walk(d) {
|
|
47
48
|
for (const name of fs.readdirSync(d)) {
|
|
@@ -54,22 +55,42 @@ function listFilesRecursive(dir, exts = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '
|
|
|
54
55
|
} catch {}
|
|
55
56
|
}
|
|
56
57
|
})(dir);
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeKeyCandidate(rawKey) {
|
|
62
|
+
if (rawKey === null || rawKey === undefined) return null;
|
|
63
|
+
let key = String(rawKey).trim();
|
|
64
|
+
if (!key) return null;
|
|
65
|
+
|
|
66
|
+
key = key.replace(/\$\{[^}]+\}/g, '*');
|
|
67
|
+
|
|
68
|
+
if (/[\r\n\t]/.test(key)) return null;
|
|
69
|
+
if (/\s/.test(key)) return null;
|
|
70
|
+
if (/(=>|\|\||&&|function\b|return\b|includes\()/i.test(key)) return null;
|
|
71
|
+
if (!/^[A-Za-z0-9_.:*-]+$/.test(key)) return null;
|
|
72
|
+
if (key.startsWith('.') || key.endsWith('.') || key.includes('..')) return null;
|
|
73
|
+
if (key === '*' || key.includes('*')) return null;
|
|
74
|
+
|
|
75
|
+
return key;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function extractKeysFromSource(file, patterns = KEY_PATTERNS) {
|
|
79
|
+
const content = readUTF8(file);
|
|
80
|
+
if (!content) return [];
|
|
81
|
+
const keys = [];
|
|
82
|
+
for (const re of patterns) {
|
|
83
|
+
re.lastIndex = 0;
|
|
84
|
+
let m; let guard = 0;
|
|
85
|
+
while ((m = re.exec(content)) && guard++ < 10000) {
|
|
86
|
+
if (m[1]) {
|
|
87
|
+
const normalized = normalizeKeyCandidate(m[1]);
|
|
88
|
+
if (normalized) keys.push(normalized);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return keys;
|
|
93
|
+
}
|
|
73
94
|
|
|
74
95
|
function flatten(obj, prefix = '') {
|
|
75
96
|
const out = {};
|
|
@@ -48,12 +48,12 @@ const COUNTRY_CODES = { de: 'DE', es: 'ES', fr: 'FR', ru: 'RU', ja: 'JA', zh: 'Z
|
|
|
48
48
|
function readUTF8(p) {
|
|
49
49
|
try { return SecurityUtils.safeReadFileSync(p, path.dirname(p), 'utf8'); } catch { return null; }
|
|
50
50
|
}
|
|
51
|
-
function writeJSON(p, obj) {
|
|
52
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
53
|
-
SecurityUtils.safeWriteFileSync(p, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
54
|
-
}
|
|
55
|
-
function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
|
|
56
|
-
function isFile(p) { try { return fs.statSync(p).isFile(); } catch { return false; } }
|
|
51
|
+
function writeJSON(p, obj) {
|
|
52
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
53
|
+
SecurityUtils.safeWriteFileSync(p, JSON.stringify(obj, null, 2) + '\n', path.dirname(p), 'utf8');
|
|
54
|
+
}
|
|
55
|
+
function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
|
|
56
|
+
function isFile(p) { try { return fs.statSync(p).isFile(); } catch { return false; } }
|
|
57
57
|
|
|
58
58
|
function listFilesRecursive(dir, exts = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']) {
|
|
59
59
|
const out = [];
|
|
@@ -67,23 +67,43 @@ function listFilesRecursive(dir, exts = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '
|
|
|
67
67
|
else if (st.isFile() && exts.includes(path.extname(name))) out.push(full);
|
|
68
68
|
} catch {}
|
|
69
69
|
}
|
|
70
|
-
})(dir);
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return
|
|
86
|
-
|
|
70
|
+
})(dir);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeKeyCandidate(rawKey) {
|
|
75
|
+
if (rawKey === null || rawKey === undefined) return null;
|
|
76
|
+
let key = String(rawKey).trim();
|
|
77
|
+
if (!key) return null;
|
|
78
|
+
|
|
79
|
+
key = key.replace(/\$\{[^}]+\}/g, '*');
|
|
80
|
+
|
|
81
|
+
if (/[\r\n\t]/.test(key)) return null;
|
|
82
|
+
if (/\s/.test(key)) return null;
|
|
83
|
+
if (/(=>|\|\||&&|function\b|return\b|includes\()/i.test(key)) return null;
|
|
84
|
+
if (!/^[A-Za-z0-9_.:*-]+$/.test(key)) return null;
|
|
85
|
+
if (key.startsWith('.') || key.endsWith('.') || key.includes('..')) return null;
|
|
86
|
+
if (key === '*' || key.includes('*')) return null;
|
|
87
|
+
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function extractKeysFromSource(file, patterns = KEY_PATTERNS) {
|
|
92
|
+
const content = readUTF8(file);
|
|
93
|
+
if (!content) return [];
|
|
94
|
+
const keys = [];
|
|
95
|
+
for (const re of patterns) {
|
|
96
|
+
re.lastIndex = 0;
|
|
97
|
+
let m; let guard = 0;
|
|
98
|
+
while ((m = re.exec(content)) && guard++ < 10000) {
|
|
99
|
+
if (m[1]) {
|
|
100
|
+
const normalized = normalizeKeyCandidate(m[1]);
|
|
101
|
+
if (normalized) keys.push(normalized);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return keys;
|
|
106
|
+
}
|
|
87
107
|
|
|
88
108
|
function flatten(obj, prefix = '') {
|
|
89
109
|
const out = {};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DEVELOPMENT-ONLY Prepublish Script
|
|
5
|
+
* This script is for development use only and should NEVER be included in production packages
|
|
6
|
+
* It provides additional development validation and cleanup functionality
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const SecurityUtils = require('../utils/security');
|
|
12
|
+
|
|
13
|
+
class DevelopmentPrepublishCleaner {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.projectRoot = path.join(__dirname, '..');
|
|
16
|
+
this.directories = [
|
|
17
|
+
'scripts/debug/logs',
|
|
18
|
+
'scripts/debug/reports',
|
|
19
|
+
'settings/backups',
|
|
20
|
+
'i18ntk-reports',
|
|
21
|
+
'reports',
|
|
22
|
+
'tests/temp'
|
|
23
|
+
];
|
|
24
|
+
this.files = [
|
|
25
|
+
'settings/.i18n-admin-config.json',
|
|
26
|
+
'test-*.json',
|
|
27
|
+
'debug-*.log',
|
|
28
|
+
'npm-debug.log',
|
|
29
|
+
'yarn-error.log',
|
|
30
|
+
'.env',
|
|
31
|
+
'.env.test'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Essential files that must exist for development
|
|
35
|
+
this.essentialFiles = [
|
|
36
|
+
'package.json',
|
|
37
|
+
'main/manage/index.js',
|
|
38
|
+
'main/i18ntk-init.js',
|
|
39
|
+
'main/i18ntk-analyze.js',
|
|
40
|
+
'main/i18ntk-validate.js',
|
|
41
|
+
'utils/security.js'
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
log(message) {
|
|
46
|
+
console.log(`[Prepublish-Dev] ${message}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async clean() {
|
|
50
|
+
this.log('Starting development pre-publish validation...');
|
|
51
|
+
|
|
52
|
+
// Validate essential files exist
|
|
53
|
+
await this.validateEssentialFiles();
|
|
54
|
+
|
|
55
|
+
// Clean directories
|
|
56
|
+
for (const dir of this.directories) {
|
|
57
|
+
await this.cleanDirectory(path.join(this.projectRoot, dir));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Clean files
|
|
61
|
+
for (const file of this.files) {
|
|
62
|
+
await this.cleanFile(file);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Development-specific validations
|
|
66
|
+
await this.devValidations();
|
|
67
|
+
|
|
68
|
+
this.log('Development pre-publish validation completed successfully!');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async cleanDirectory(dirPath) {
|
|
72
|
+
const validatedPath = SecurityUtils.validatePath(dirPath, this.projectRoot);
|
|
73
|
+
if (!validatedPath || !SecurityUtils.safeExistsSync(validatedPath)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const files = SecurityUtils.safeReaddirSync(validatedPath, this.projectRoot);
|
|
79
|
+
let deletedCount = 0;
|
|
80
|
+
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
const filePath = path.join(validatedPath, file);
|
|
83
|
+
const validatedFilePath = SecurityUtils.validatePath(filePath, this.projectRoot);
|
|
84
|
+
if (!validatedFilePath) continue;
|
|
85
|
+
|
|
86
|
+
const stat = SecurityUtils.safeStatSync(validatedFilePath, this.projectRoot);
|
|
87
|
+
if (!stat) continue;
|
|
88
|
+
|
|
89
|
+
if (stat.isFile()) {
|
|
90
|
+
if (SecurityUtils.safeExistsSync(validatedFilePath)) {
|
|
91
|
+
fs.unlinkSync(validatedFilePath);
|
|
92
|
+
deletedCount++;
|
|
93
|
+
}
|
|
94
|
+
} else if (stat.isDirectory()) {
|
|
95
|
+
// Recursively clean subdirectories
|
|
96
|
+
await this.cleanDirectory(validatedFilePath);
|
|
97
|
+
// Remove empty directories
|
|
98
|
+
try {
|
|
99
|
+
fs.rmdirSync(validatedFilePath);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// Directory not empty, skip
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (deletedCount > 0) {
|
|
107
|
+
this.log(`Cleaned ${deletedCount} files from ${path.relative(this.projectRoot, validatedPath)}`);
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.log(`Warning: Could not clean ${dirPath}: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async cleanFile(pattern) {
|
|
115
|
+
const searchPath = path.join(this.projectRoot, pattern);
|
|
116
|
+
const validatedSearchPath = SecurityUtils.validatePath(searchPath, this.projectRoot);
|
|
117
|
+
if (!validatedSearchPath) return;
|
|
118
|
+
|
|
119
|
+
if (pattern.includes('*')) {
|
|
120
|
+
// Handle glob patterns
|
|
121
|
+
const dir = path.dirname(validatedSearchPath);
|
|
122
|
+
const filenamePattern = path.basename(validatedSearchPath);
|
|
123
|
+
const validatedDir = SecurityUtils.validatePath(dir, this.projectRoot);
|
|
124
|
+
if (!validatedDir) return;
|
|
125
|
+
|
|
126
|
+
if (SecurityUtils.safeExistsSync(validatedDir)) {
|
|
127
|
+
const files = SecurityUtils.safeReaddirSync(validatedDir, this.projectRoot);
|
|
128
|
+
const regex = new RegExp(filenamePattern.replace('*', '.*'));
|
|
129
|
+
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
if (regex.test(file)) {
|
|
132
|
+
const filePath = path.join(validatedDir, file);
|
|
133
|
+
const validatedFilePath = SecurityUtils.validatePath(filePath, this.projectRoot);
|
|
134
|
+
if (validatedFilePath && SecurityUtils.safeExistsSync(validatedFilePath)) {
|
|
135
|
+
fs.unlinkSync(validatedFilePath);
|
|
136
|
+
this.log(`Deleted ${path.relative(this.projectRoot, validatedFilePath)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
// Handle exact files
|
|
143
|
+
const validatedFilePath = SecurityUtils.validatePath(searchPath, this.projectRoot);
|
|
144
|
+
if (validatedFilePath && SecurityUtils.safeExistsSync(validatedFilePath)) {
|
|
145
|
+
fs.unlinkSync(validatedFilePath);
|
|
146
|
+
this.log(`Deleted ${path.relative(this.projectRoot, validatedFilePath)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async validateEssentialFiles() {
|
|
152
|
+
this.log('Validating essential development files...');
|
|
153
|
+
|
|
154
|
+
let missingFiles = [];
|
|
155
|
+
for (const file of this.essentialFiles) {
|
|
156
|
+
const filePath = path.join(this.projectRoot, file);
|
|
157
|
+
const validatedPath = SecurityUtils.validatePath(filePath, this.projectRoot);
|
|
158
|
+
if (!validatedPath || !SecurityUtils.safeExistsSync(validatedPath)) {
|
|
159
|
+
missingFiles.push(file);
|
|
160
|
+
} else {
|
|
161
|
+
const stat = SecurityUtils.safeStatSync(validatedPath, this.projectRoot);
|
|
162
|
+
if (!stat || !stat.isFile()) {
|
|
163
|
+
this.log(`❌ ${file} is not a file`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (missingFiles.length > 0) {
|
|
170
|
+
this.log(`❌ Missing essential files: ${missingFiles.join(', ')}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.log('✅ All essential development files present');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async devValidations() {
|
|
178
|
+
this.log('Running development-specific validations...');
|
|
179
|
+
|
|
180
|
+
// Check for test files
|
|
181
|
+
const testFiles = [
|
|
182
|
+
'tests/security.test.js',
|
|
183
|
+
'tests/config-system.test.js',
|
|
184
|
+
'tests/comprehensive-test.js'
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
for (const testFile of testFiles) {
|
|
188
|
+
const testPath = path.join(this.projectRoot, testFile);
|
|
189
|
+
const validatedPath = SecurityUtils.validatePath(testPath, this.projectRoot);
|
|
190
|
+
if (!validatedPath || !SecurityUtils.safeExistsSync(validatedPath)) {
|
|
191
|
+
this.log(`⚠️ Missing test file: ${testFile}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for development tools
|
|
196
|
+
const devTools = [
|
|
197
|
+
'.vscode/',
|
|
198
|
+
'.idea/',
|
|
199
|
+
'node_modules/'
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
for (const tool of devTools) {
|
|
203
|
+
const toolPath = path.join(this.projectRoot, tool);
|
|
204
|
+
const validatedPath = SecurityUtils.validatePath(toolPath, this.projectRoot);
|
|
205
|
+
if (validatedPath && SecurityUtils.safeExistsSync(validatedPath)) {
|
|
206
|
+
this.log(`ℹ️ Development tool found: ${tool}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Run if called directly
|
|
213
|
+
if (require.main === module) {
|
|
214
|
+
const cleaner = new DevelopmentPrepublishCleaner();
|
|
215
|
+
cleaner.clean().catch(error => {
|
|
216
|
+
console.error('Error during development cleanup:', error);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = DevelopmentPrepublishCleaner;
|