ai-localize-scanner 1.0.0 → 2.0.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 +13 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +35 -26
- package/dist/index.mjs +15 -6
- package/package.json +3 -3
- package/src/asset-scanner.ts +2 -2
- package/src/ast-scanner.ts +21 -6
- package/src/incremental-scanner.ts +2 -2
- package/src/project-scanner.ts +2 -2
package/CHANGELOG.md
ADDED
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from '
|
|
1
|
+
import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from 'ai-localize-shared';
|
|
2
2
|
|
|
3
3
|
interface AstScanOptions {
|
|
4
4
|
filePath: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from '
|
|
1
|
+
import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from 'ai-localize-shared';
|
|
2
2
|
|
|
3
3
|
interface AstScanOptions {
|
|
4
4
|
filePath: string;
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
42
|
var parser = __toESM(require("@babel/parser"));
|
|
43
43
|
var import_traverse = __toESM(require("@babel/traverse"));
|
|
44
44
|
var t = __toESM(require("@babel/types"));
|
|
45
|
-
var
|
|
45
|
+
var import_ai_localize_shared = require("ai-localize-shared");
|
|
46
46
|
var TRANSLATION_IMPORT_SOURCES = /* @__PURE__ */ new Set([
|
|
47
47
|
"react-i18next",
|
|
48
48
|
"i18next",
|
|
@@ -80,8 +80,8 @@ var AstScanner = class {
|
|
|
80
80
|
this.collectTranslationImports(ast);
|
|
81
81
|
(0, import_traverse.default)(ast, {
|
|
82
82
|
JSXText: (nodePath) => {
|
|
83
|
-
const text = (0,
|
|
84
|
-
if (!(0,
|
|
83
|
+
const text = (0, import_ai_localize_shared.normalizeText)(nodePath.node.value);
|
|
84
|
+
if (!(0, import_ai_localize_shared.isHumanReadableText)(text)) return;
|
|
85
85
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
86
86
|
this.addDetected(
|
|
87
87
|
text,
|
|
@@ -93,11 +93,11 @@ var AstScanner = class {
|
|
|
93
93
|
},
|
|
94
94
|
JSXAttribute: (nodePath) => {
|
|
95
95
|
const attrName = t.isJSXIdentifier(nodePath.node.name) ? nodePath.node.name.name : "";
|
|
96
|
-
if (!
|
|
96
|
+
if (!import_ai_localize_shared.TEXT_ATTRIBUTE_NAMES.has(attrName.toLowerCase())) return;
|
|
97
97
|
const valueNode = nodePath.node.value;
|
|
98
98
|
if (!t.isStringLiteral(valueNode)) return;
|
|
99
|
-
const text = (0,
|
|
100
|
-
if (!(0,
|
|
99
|
+
const text = (0, import_ai_localize_shared.normalizeText)(valueNode.value);
|
|
100
|
+
if (!(0, import_ai_localize_shared.isHumanReadableText)(text)) return;
|
|
101
101
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
102
102
|
const context = this.mapAttrToContext(attrName);
|
|
103
103
|
this.addDetected(
|
|
@@ -111,11 +111,20 @@ var AstScanner = class {
|
|
|
111
111
|
StringLiteral: (nodePath) => {
|
|
112
112
|
if (t.isImportDeclaration(nodePath.parent)) return;
|
|
113
113
|
if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
|
|
114
|
-
if (t.isJSXAttribute(nodePath.parent))
|
|
114
|
+
if (t.isJSXAttribute(nodePath.parent)) {
|
|
115
|
+
const attrName = t.isJSXIdentifier(nodePath.parent.name) ? nodePath.parent.name.name.toLowerCase() : "";
|
|
116
|
+
if (attrName === "classname" || attrName === "class") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
115
121
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
const val = nodePath.node.value;
|
|
123
|
+
if (/^[a-z][a-z0-9_.-]*$/.test(val) || /^#?[0-9a-fA-F]+$/.test(val) || /^[\w-]+\s[\w- ]+$/.test(val) && !val.includes(",")) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const text = (0, import_ai_localize_shared.normalizeText)(nodePath.node.value);
|
|
127
|
+
if (!(0, import_ai_localize_shared.isHumanReadableText)(text)) return;
|
|
119
128
|
this.addDetected(
|
|
120
129
|
text,
|
|
121
130
|
nodePath.node.loc?.start.line ?? 0,
|
|
@@ -127,8 +136,8 @@ var AstScanner = class {
|
|
|
127
136
|
TemplateLiteral: (nodePath) => {
|
|
128
137
|
if (nodePath.node.expressions.length > 0) return;
|
|
129
138
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
130
|
-
const text = (0,
|
|
131
|
-
if (!(0,
|
|
139
|
+
const text = (0, import_ai_localize_shared.normalizeText)(nodePath.node.quasis[0]?.value.cooked ?? "");
|
|
140
|
+
if (!(0, import_ai_localize_shared.isHumanReadableText)(text)) return;
|
|
132
141
|
this.addDetected(
|
|
133
142
|
text,
|
|
134
143
|
nodePath.node.loc?.start.line ?? 0,
|
|
@@ -170,7 +179,7 @@ var AstScanner = class {
|
|
|
170
179
|
return false;
|
|
171
180
|
}
|
|
172
181
|
addDetected(text, line, column, context, nodeType) {
|
|
173
|
-
const key = (0,
|
|
182
|
+
const key = (0, import_ai_localize_shared.generateLocaleKey)(
|
|
174
183
|
this.options.filePath,
|
|
175
184
|
text,
|
|
176
185
|
this.options.sourceRoot || "src"
|
|
@@ -202,9 +211,9 @@ var AstScanner = class {
|
|
|
202
211
|
let m;
|
|
203
212
|
jsxTextRegex.lastIndex = 0;
|
|
204
213
|
while ((m = jsxTextRegex.exec(line)) !== null) {
|
|
205
|
-
const text = (0,
|
|
206
|
-
if (!(0,
|
|
207
|
-
const key = (0,
|
|
214
|
+
const text = (0, import_ai_localize_shared.normalizeText)(m[1]);
|
|
215
|
+
if (!(0, import_ai_localize_shared.isHumanReadableText)(text)) continue;
|
|
216
|
+
const key = (0, import_ai_localize_shared.generateLocaleKey)(this.options.filePath, text, this.options.sourceRoot || "src");
|
|
208
217
|
results.push({
|
|
209
218
|
filePath: this.options.filePath,
|
|
210
219
|
line: idx + 1,
|
|
@@ -224,7 +233,7 @@ var AstScanner = class {
|
|
|
224
233
|
// src/asset-scanner.ts
|
|
225
234
|
var fs = __toESM(require("fs"));
|
|
226
235
|
var path = __toESM(require("path"));
|
|
227
|
-
var
|
|
236
|
+
var import_ai_localize_shared2 = require("ai-localize-shared");
|
|
228
237
|
var CDN_URL_PATTERN = /https?:\/\/[a-zA-Z0-9\-.]+\.[a-zA-Z]{2,}\/[^\s"'`\)\]>]+/g;
|
|
229
238
|
var CSS_URL_PATTERN = /url\(['"\s]?([^'")]+)['"\s]?\)/g;
|
|
230
239
|
var IMPORT_ASSET_PATTERN = /import\s+\w+\s+from\s+['"]([^'"]+\.(png|jpg|jpeg|svg|webp|gif|ico|woff|woff2|ttf|eot|mp4))['"];?/gi;
|
|
@@ -297,7 +306,7 @@ var AssetScanner = class {
|
|
|
297
306
|
CDN_URL_PATTERN.lastIndex = 0;
|
|
298
307
|
while ((m = CDN_URL_PATTERN.exec(content)) !== null) {
|
|
299
308
|
const url = m[0];
|
|
300
|
-
if (!
|
|
309
|
+
if (!import_ai_localize_shared2.ASSET_EXTENSIONS.some((ext) => url.includes(`.${ext}`))) continue;
|
|
301
310
|
const line = this.getLineNumber(content, m.index);
|
|
302
311
|
if (!legacyCdnUrls.find((u) => u.url === url && u.line === line)) {
|
|
303
312
|
legacyCdnUrls.push({ filePath, line, url, assetPath: this.extractPathFromUrl(url) });
|
|
@@ -310,7 +319,7 @@ var AssetScanner = class {
|
|
|
310
319
|
}
|
|
311
320
|
getAssetType(assetPath) {
|
|
312
321
|
const ext = path.extname(assetPath).toLowerCase().replace(".", "");
|
|
313
|
-
return
|
|
322
|
+
return import_ai_localize_shared2.ASSET_EXTENSIONS.includes(ext) ? ext : "other";
|
|
314
323
|
}
|
|
315
324
|
extractPathFromUrl(url) {
|
|
316
325
|
try {
|
|
@@ -325,17 +334,17 @@ var AssetScanner = class {
|
|
|
325
334
|
var fs2 = __toESM(require("fs"));
|
|
326
335
|
var path2 = __toESM(require("path"));
|
|
327
336
|
var crypto = __toESM(require("crypto"));
|
|
328
|
-
var
|
|
337
|
+
var import_ai_localize_shared3 = require("ai-localize-shared");
|
|
329
338
|
var IncrementalScanCache = class {
|
|
330
339
|
cachePath;
|
|
331
340
|
cache;
|
|
332
341
|
constructor(cacheDir) {
|
|
333
|
-
(0,
|
|
342
|
+
(0, import_ai_localize_shared3.ensureDir)(cacheDir);
|
|
334
343
|
this.cachePath = path2.join(cacheDir, "scan-cache.json");
|
|
335
344
|
this.cache = this.load();
|
|
336
345
|
}
|
|
337
346
|
load() {
|
|
338
|
-
const existing = (0,
|
|
347
|
+
const existing = (0, import_ai_localize_shared3.readJsonSafe)(this.cachePath);
|
|
339
348
|
if (existing?.version === "1") return existing;
|
|
340
349
|
return { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), fileHashes: {}, processedFiles: {} };
|
|
341
350
|
}
|
|
@@ -355,7 +364,7 @@ var IncrementalScanCache = class {
|
|
|
355
364
|
}
|
|
356
365
|
persist() {
|
|
357
366
|
this.cache.lastRun = (/* @__PURE__ */ new Date()).toISOString();
|
|
358
|
-
(0,
|
|
367
|
+
(0, import_ai_localize_shared3.writeJson)(this.cachePath, this.cache);
|
|
359
368
|
}
|
|
360
369
|
hashFile(filePath) {
|
|
361
370
|
try {
|
|
@@ -373,7 +382,7 @@ var IncrementalScanCache = class {
|
|
|
373
382
|
// src/project-scanner.ts
|
|
374
383
|
var path3 = __toESM(require("path"));
|
|
375
384
|
var os = __toESM(require("os"));
|
|
376
|
-
var
|
|
385
|
+
var import_ai_localize_shared4 = require("ai-localize-shared");
|
|
377
386
|
var ProjectScanner = class {
|
|
378
387
|
config;
|
|
379
388
|
sourceRoot;
|
|
@@ -391,8 +400,8 @@ var ProjectScanner = class {
|
|
|
391
400
|
}
|
|
392
401
|
async scan(options = {}) {
|
|
393
402
|
const startTime = Date.now();
|
|
394
|
-
const filesToScan = options.files?.length ? options.files : (0,
|
|
395
|
-
...
|
|
403
|
+
const filesToScan = options.files?.length ? options.files : (0, import_ai_localize_shared4.collectFiles)(this.sourceRoot, import_ai_localize_shared4.SOURCE_EXTENSIONS, [
|
|
404
|
+
...import_ai_localize_shared4.DEFAULT_IGNORE_DIRS,
|
|
396
405
|
...this.config.ignorePatterns || []
|
|
397
406
|
]);
|
|
398
407
|
const allTexts = [];
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
normalizeText,
|
|
8
8
|
TEXT_ATTRIBUTE_NAMES,
|
|
9
9
|
generateLocaleKey
|
|
10
|
-
} from "
|
|
10
|
+
} from "ai-localize-shared";
|
|
11
11
|
var TRANSLATION_IMPORT_SOURCES = /* @__PURE__ */ new Set([
|
|
12
12
|
"react-i18next",
|
|
13
13
|
"i18next",
|
|
@@ -76,9 +76,18 @@ var AstScanner = class {
|
|
|
76
76
|
StringLiteral: (nodePath) => {
|
|
77
77
|
if (t.isImportDeclaration(nodePath.parent)) return;
|
|
78
78
|
if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
|
|
79
|
-
if (t.isJSXAttribute(nodePath.parent))
|
|
79
|
+
if (t.isJSXAttribute(nodePath.parent)) {
|
|
80
|
+
const attrName = t.isJSXIdentifier(nodePath.parent.name) ? nodePath.parent.name.name.toLowerCase() : "";
|
|
81
|
+
if (attrName === "classname" || attrName === "class") {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
80
86
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
81
|
-
|
|
87
|
+
const val = nodePath.node.value;
|
|
88
|
+
if (/^[a-z][a-z0-9_.-]*$/.test(val) || /^#?[0-9a-fA-F]+$/.test(val) || /^[\w-]+\s[\w- ]+$/.test(val) && !val.includes(",")) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
82
91
|
const text = normalizeText(nodePath.node.value);
|
|
83
92
|
if (!isHumanReadableText(text)) return;
|
|
84
93
|
this.addDetected(
|
|
@@ -189,7 +198,7 @@ var AstScanner = class {
|
|
|
189
198
|
// src/asset-scanner.ts
|
|
190
199
|
import * as fs from "fs";
|
|
191
200
|
import * as path from "path";
|
|
192
|
-
import { ASSET_EXTENSIONS } from "
|
|
201
|
+
import { ASSET_EXTENSIONS } from "ai-localize-shared";
|
|
193
202
|
var CDN_URL_PATTERN = /https?:\/\/[a-zA-Z0-9\-.]+\.[a-zA-Z]{2,}\/[^\s"'`\)\]>]+/g;
|
|
194
203
|
var CSS_URL_PATTERN = /url\(['"\s]?([^'")]+)['"\s]?\)/g;
|
|
195
204
|
var IMPORT_ASSET_PATTERN = /import\s+\w+\s+from\s+['"]([^'"]+\.(png|jpg|jpeg|svg|webp|gif|ico|woff|woff2|ttf|eot|mp4))['"];?/gi;
|
|
@@ -290,7 +299,7 @@ var AssetScanner = class {
|
|
|
290
299
|
import * as fs2 from "fs";
|
|
291
300
|
import * as path2 from "path";
|
|
292
301
|
import * as crypto from "crypto";
|
|
293
|
-
import { readJsonSafe, writeJson, ensureDir } from "
|
|
302
|
+
import { readJsonSafe, writeJson, ensureDir } from "ai-localize-shared";
|
|
294
303
|
var IncrementalScanCache = class {
|
|
295
304
|
cachePath;
|
|
296
305
|
cache;
|
|
@@ -338,7 +347,7 @@ var IncrementalScanCache = class {
|
|
|
338
347
|
// src/project-scanner.ts
|
|
339
348
|
import * as path3 from "path";
|
|
340
349
|
import * as os from "os";
|
|
341
|
-
import { collectFiles, DEFAULT_IGNORE_DIRS, SOURCE_EXTENSIONS } from "
|
|
350
|
+
import { collectFiles, DEFAULT_IGNORE_DIRS, SOURCE_EXTENSIONS } from "ai-localize-shared";
|
|
342
351
|
var ProjectScanner = class {
|
|
343
352
|
config;
|
|
344
353
|
sourceRoot;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-localize-scanner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "AST-based hardcoded text scanner for frontend applications",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"@babel/traverse": "^7.23.9",
|
|
18
18
|
"@babel/types": "^7.23.9",
|
|
19
19
|
"glob": "^10.3.10",
|
|
20
|
-
"ai-localize-shared": "
|
|
21
|
-
"ai-localize-config": "
|
|
20
|
+
"ai-localize-shared": "2.0.0",
|
|
21
|
+
"ai-localize-config": "2.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/babel__traverse": "^7.20.5",
|
package/src/asset-scanner.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
|
|
4
|
-
import type { AssetReference, AssetType, LegacyCdnUrl } from '
|
|
5
|
-
import { ASSET_EXTENSIONS } from '
|
|
4
|
+
import type { AssetReference, AssetType, LegacyCdnUrl } from 'ai-localize-shared';
|
|
5
|
+
import { ASSET_EXTENSIONS } from 'ai-localize-shared';
|
|
6
6
|
|
|
7
7
|
const CDN_URL_PATTERN = /https?:\/\/[a-zA-Z0-9\-.]+\.[a-zA-Z]{2,}\/[^\s"'`\)\]>]+/g;
|
|
8
8
|
const CSS_URL_PATTERN = /url\(['"\s]?([^'")]+)['"\s]?\)/g;
|
package/src/ast-scanner.ts
CHANGED
|
@@ -2,13 +2,13 @@ import * as parser from '@babel/parser';
|
|
|
2
2
|
import traverse from '@babel/traverse';
|
|
3
3
|
import * as t from '@babel/types';
|
|
4
4
|
|
|
5
|
-
import type { DetectedText, TextContext } from '
|
|
5
|
+
import type { DetectedText, TextContext } from 'ai-localize-shared';
|
|
6
6
|
import {
|
|
7
7
|
isHumanReadableText,
|
|
8
8
|
normalizeText,
|
|
9
9
|
TEXT_ATTRIBUTE_NAMES,
|
|
10
10
|
generateLocaleKey,
|
|
11
|
-
} from '
|
|
11
|
+
} from 'ai-localize-shared';
|
|
12
12
|
|
|
13
13
|
export interface AstScanOptions {
|
|
14
14
|
filePath: string;
|
|
@@ -97,17 +97,32 @@ if (!t.isStringLiteral(valueNode)) return;
|
|
|
97
97
|
StringLiteral: (nodePath) => {
|
|
98
98
|
if (t.isImportDeclaration(nodePath.parent)) return;
|
|
99
99
|
if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
|
|
100
|
-
|
|
100
|
+
|
|
101
|
+
// Ignore className and similar attributes containing CSS classes
|
|
102
|
+
if (t.isJSXAttribute(nodePath.parent)) {
|
|
103
|
+
const attrName = t.isJSXIdentifier(nodePath.parent.name) ? nodePath.parent.name.name.toLowerCase() : '';
|
|
104
|
+
if (attrName === 'classname' || attrName === 'class') {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
return; // Ignore other unhandled JSX attributes here as well based on previous logic
|
|
108
|
+
}
|
|
109
|
+
|
|
101
110
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
102
|
-
|
|
111
|
+
|
|
112
|
+
// Improve heuristic to avoid matching CSS classes / HTML tags in standard string literals
|
|
113
|
+
const val = nodePath.node.value;
|
|
114
|
+
if (/^[a-z][a-z0-9_.-]*$/.test(val) || /^#?[0-9a-fA-F]+$/.test(val) || (/^[\w-]+\s[\w- ]+$/.test(val) && !val.includes(','))) {
|
|
115
|
+
return; // Probably a CSS class name, ID, color hex, or just words without punctuation that are often classes
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
const text = normalizeText(nodePath.node.value);
|
|
104
119
|
if (!isHumanReadableText(text)) return;
|
|
105
|
-
|
|
120
|
+
this.addDetected(
|
|
106
121
|
text,
|
|
107
122
|
nodePath.node.loc?.start.line ?? 0,
|
|
108
123
|
nodePath.node.loc?.start.column ?? 0,
|
|
109
124
|
'string-literal',
|
|
110
|
-
|
|
125
|
+
'StringLiteral'
|
|
111
126
|
);
|
|
112
127
|
},
|
|
113
128
|
|
|
@@ -2,8 +2,8 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as crypto from 'crypto';
|
|
4
4
|
|
|
5
|
-
import type { IncrementalCache, DetectedText } from '
|
|
6
|
-
import { readJsonSafe, writeJson, ensureDir } from '
|
|
5
|
+
import type { IncrementalCache, DetectedText } from 'ai-localize-shared';
|
|
6
|
+
import { readJsonSafe, writeJson, ensureDir } from 'ai-localize-shared';
|
|
7
7
|
|
|
8
8
|
export class IncrementalScanCache {
|
|
9
9
|
private cachePath: string;
|
package/src/project-scanner.ts
CHANGED
|
@@ -7,8 +7,8 @@ import type {
|
|
|
7
7
|
LegacyCdnUrl,
|
|
8
8
|
ScanResult,
|
|
9
9
|
LocalizationConfig,
|
|
10
|
-
} from '
|
|
11
|
-
import { collectFiles, DEFAULT_IGNORE_DIRS, SOURCE_EXTENSIONS } from '
|
|
10
|
+
} from 'ai-localize-shared';
|
|
11
|
+
import { collectFiles, DEFAULT_IGNORE_DIRS, SOURCE_EXTENSIONS } from 'ai-localize-shared';
|
|
12
12
|
|
|
13
13
|
import { AstScanner } from './ast-scanner.js';
|
|
14
14
|
import { AssetScanner } from './asset-scanner.js';
|