i18ntk 4.5.4 → 4.6.1
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 +45 -2
- package/README.md +124 -536
- package/main/i18ntk-init.js +56 -25
- package/main/i18ntk-scanner.js +148 -29
- package/main/i18ntk-translate.js +6 -3
- package/main/manage/commands/ScannerCommand.js +145 -28
- package/main/manage/services/FrameworkDetectionService.js +67 -9
- package/main/manage/services/InitService.js +56 -25
- package/package.json +27 -43
- package/utils/config-helper.js +2 -2
- package/utils/framework-detector.js +317 -6
- package/utils/report-model.js +17 -4
- package/utils/usage-source.js +2 -1
- package/utils/validation-risk.js +22 -13
package/utils/report-model.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { getSourceExtensions, getExcludeDirs } = require('./framework-detector');
|
|
5
6
|
const SecurityUtils = require('./security');
|
|
6
7
|
|
|
8
|
+
const EXCLUDE_DIRS = new Set(getExcludeDirs());
|
|
9
|
+
const SOURCE_EXTENSIONS = getSourceExtensions();
|
|
10
|
+
|
|
7
11
|
const ISSUE_TYPES = new Set([
|
|
8
12
|
'missing_key',
|
|
9
13
|
'unused_key',
|
|
@@ -265,7 +269,7 @@ function discoverLocaleFiles(localesDir) {
|
|
|
265
269
|
function walk(dir, extensions, acc = [], basePath = process.cwd()) {
|
|
266
270
|
if (!SecurityUtils.safeExistsSync(dir, basePath)) return acc;
|
|
267
271
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
268
|
-
if (
|
|
272
|
+
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
269
273
|
const entryPath = path.join(dir, entry.name);
|
|
270
274
|
if (entry.isDirectory()) walk(entryPath, extensions, acc, basePath);
|
|
271
275
|
else if (entry.isFile() && extensions.includes(path.extname(entry.name))) acc.push(entryPath);
|
|
@@ -290,7 +294,7 @@ function scanSourceFiles(sourceDir, availableKeys) {
|
|
|
290
294
|
const hardcodedTexts = [];
|
|
291
295
|
const translationValueIndex = new Map();
|
|
292
296
|
const basePath = path.resolve(sourceDir);
|
|
293
|
-
const files = walk(sourceDir,
|
|
297
|
+
const files = walk(sourceDir, SOURCE_EXTENSIONS, [], basePath);
|
|
294
298
|
|
|
295
299
|
for (const file of files) {
|
|
296
300
|
const content = SecurityUtils.safeReadFileSync(file, basePath, 'utf8');
|
|
@@ -339,7 +343,13 @@ function findKeyByValue(_text, _availableKeys) {
|
|
|
339
343
|
function extractPlaceholders(value) {
|
|
340
344
|
const placeholders = new Set();
|
|
341
345
|
const text = String(value || '');
|
|
342
|
-
const patterns = [
|
|
346
|
+
const patterns = [
|
|
347
|
+
/\{\{\s*([\w.-]+)\s*\}\}/g,
|
|
348
|
+
/%\{([\w.-]+)\}/g,
|
|
349
|
+
/\{([\w.-]+)\}/g,
|
|
350
|
+
/\$\{([\w.-]+)\}/g,
|
|
351
|
+
/\$([a-zA-Z_][a-zA-Z0-9_-]+)/g
|
|
352
|
+
];
|
|
343
353
|
for (const pattern of patterns) {
|
|
344
354
|
let match;
|
|
345
355
|
while ((match = pattern.exec(text))) placeholders.add(match[1]);
|
|
@@ -356,7 +366,10 @@ function looksLikelyUntranslated(source, target, locale) {
|
|
|
356
366
|
}
|
|
357
367
|
|
|
358
368
|
function normalizeText(value) {
|
|
359
|
-
return String(value || '')
|
|
369
|
+
return String(value || '')
|
|
370
|
+
.replace(/\{\{[^}]+\}\}|\{[^}]+\}|%\{[^}]+\}/g, '')
|
|
371
|
+
.replace(/\$[a-zA-Z_][a-zA-Z0-9_-]*(?:\s*\{\s*[^}]+\})?/g, '')
|
|
372
|
+
.trim().toLowerCase();
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
function getExpansionPercent(source, target) {
|
package/utils/usage-source.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const SecurityUtils = require('./security');
|
|
5
|
+
const { SOURCE_DIRS } = require('./framework-detector');
|
|
5
6
|
|
|
6
|
-
const DEFAULT_SOURCE_DIRS =
|
|
7
|
+
const DEFAULT_SOURCE_DIRS = SOURCE_DIRS;
|
|
7
8
|
|
|
8
9
|
function resolveUsageSourceDir(options = {}) {
|
|
9
10
|
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|
package/utils/validation-risk.js
CHANGED
|
@@ -34,24 +34,32 @@ const ENGLISH_WORDS = new Set([
|
|
|
34
34
|
'warning', 'when', 'with', 'without'
|
|
35
35
|
]);
|
|
36
36
|
|
|
37
|
-
const DEFAULT_ALLOWED_ENGLISH_TERMS = new Set([
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const DEFAULT_ALLOWED_ENGLISH_TERMS = new Set(['api']);
|
|
38
|
+
|
|
39
|
+
function resolveAllowedEnglishTerms(options, projectConfig) {
|
|
40
|
+
const terms = new Set(DEFAULT_ALLOWED_ENGLISH_TERMS);
|
|
41
|
+
const sources = [
|
|
42
|
+
options && options.allowedEnglishTerms,
|
|
43
|
+
projectConfig && projectConfig.allowedEnglishTerms
|
|
44
|
+
];
|
|
45
|
+
for (const source of sources) {
|
|
46
|
+
if (Array.isArray(source)) {
|
|
47
|
+
source.forEach(term => {
|
|
48
|
+
if (typeof term === 'string' && term.trim()) {
|
|
49
|
+
terms.add(term.trim().toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return terms;
|
|
55
|
+
}
|
|
40
56
|
|
|
41
57
|
function normalizeLanguage(language) {
|
|
42
58
|
return String(language || '').toLowerCase().split(/[-_]/)[0];
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
function toAllowedTermSet(terms) {
|
|
46
|
-
|
|
47
|
-
if (Array.isArray(terms)) {
|
|
48
|
-
terms.forEach(term => {
|
|
49
|
-
if (typeof term === 'string' && term.trim()) {
|
|
50
|
-
allowed.add(term.trim().toLowerCase());
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return allowed;
|
|
62
|
+
return resolveAllowedEnglishTerms({ allowedEnglishTerms: terms });
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
function stripNonLanguageTokens(value) {
|
|
@@ -171,5 +179,6 @@ module.exports = {
|
|
|
171
179
|
DEFAULT_ENGLISH_THRESHOLD_PERCENT,
|
|
172
180
|
analyzeEnglishContent,
|
|
173
181
|
detectTranslationContentRisks,
|
|
174
|
-
hasSecretLikeValue
|
|
182
|
+
hasSecretLikeValue,
|
|
183
|
+
resolveAllowedEnglishTerms
|
|
175
184
|
};
|