ai-localize-scanner 2.0.5 → 2.0.7
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 +27 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/index.d.mts +37 -20
- package/dist/index.d.ts +37 -20
- package/dist/index.js +358 -33
- package/dist/index.mjs +359 -33
- package/package.json +15 -4
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import traverse from "@babel/traverse";
|
|
|
4
4
|
import * as t from "@babel/types";
|
|
5
5
|
import {
|
|
6
6
|
isHumanReadableText,
|
|
7
|
+
isCssClassString,
|
|
7
8
|
normalizeText,
|
|
8
9
|
TEXT_ATTRIBUTE_NAMES,
|
|
9
10
|
generateKeyByStyle
|
|
@@ -14,9 +15,239 @@ var BUILTIN_TRANSLATION_IMPORT_SOURCES = /* @__PURE__ */ new Set([
|
|
|
14
15
|
"vue-i18n",
|
|
15
16
|
"@ngx-translate/core"
|
|
16
17
|
]);
|
|
18
|
+
var NON_TRANSLATABLE_ATTR_NAMES = /* @__PURE__ */ new Set([
|
|
19
|
+
"classname",
|
|
20
|
+
"class",
|
|
21
|
+
"id",
|
|
22
|
+
"name",
|
|
23
|
+
"key",
|
|
24
|
+
"ref",
|
|
25
|
+
"type",
|
|
26
|
+
"method",
|
|
27
|
+
"action",
|
|
28
|
+
"enctype",
|
|
29
|
+
"href",
|
|
30
|
+
"src",
|
|
31
|
+
"srcset",
|
|
32
|
+
"action",
|
|
33
|
+
"to",
|
|
34
|
+
"as",
|
|
35
|
+
"path",
|
|
36
|
+
"url",
|
|
37
|
+
"rel",
|
|
38
|
+
"target",
|
|
39
|
+
"referrerpolicy",
|
|
40
|
+
"crossorigin",
|
|
41
|
+
"fetchpriority",
|
|
42
|
+
"loading",
|
|
43
|
+
"decoding",
|
|
44
|
+
"sizes",
|
|
45
|
+
"media",
|
|
46
|
+
"role",
|
|
47
|
+
"tabindex",
|
|
48
|
+
"autocomplete",
|
|
49
|
+
"inputmode",
|
|
50
|
+
"dir",
|
|
51
|
+
"lang",
|
|
52
|
+
"charset",
|
|
53
|
+
"for",
|
|
54
|
+
"htmlfor",
|
|
55
|
+
"htmlFor",
|
|
56
|
+
"accept",
|
|
57
|
+
"capture",
|
|
58
|
+
"pattern",
|
|
59
|
+
"autocorrect",
|
|
60
|
+
"autocapitalize",
|
|
61
|
+
"spellcheck",
|
|
62
|
+
"style",
|
|
63
|
+
"css",
|
|
64
|
+
"data-testid",
|
|
65
|
+
"data-cy",
|
|
66
|
+
"data-test",
|
|
67
|
+
"data-id",
|
|
68
|
+
"variant",
|
|
69
|
+
"color",
|
|
70
|
+
"size",
|
|
71
|
+
"shape",
|
|
72
|
+
"align",
|
|
73
|
+
"valign",
|
|
74
|
+
"justify",
|
|
75
|
+
"orientation",
|
|
76
|
+
"direction",
|
|
77
|
+
"placement",
|
|
78
|
+
"position",
|
|
79
|
+
"icon",
|
|
80
|
+
"iconname",
|
|
81
|
+
"lefticon",
|
|
82
|
+
"righticon"
|
|
83
|
+
]);
|
|
84
|
+
var NON_TRANSLATABLE_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
85
|
+
// HTML/JSX structural
|
|
86
|
+
"className",
|
|
87
|
+
"class",
|
|
88
|
+
"id",
|
|
89
|
+
"name",
|
|
90
|
+
"key",
|
|
91
|
+
"ref",
|
|
92
|
+
"type",
|
|
93
|
+
"method",
|
|
94
|
+
"action",
|
|
95
|
+
"href",
|
|
96
|
+
"src",
|
|
97
|
+
"rel",
|
|
98
|
+
"target",
|
|
99
|
+
"role",
|
|
100
|
+
"style",
|
|
101
|
+
"css",
|
|
102
|
+
"to",
|
|
103
|
+
"as",
|
|
104
|
+
"path",
|
|
105
|
+
"url",
|
|
106
|
+
"link",
|
|
107
|
+
"icon",
|
|
108
|
+
"variant",
|
|
109
|
+
"color",
|
|
110
|
+
"size",
|
|
111
|
+
// CSS-in-JS style properties
|
|
112
|
+
"fontFamily",
|
|
113
|
+
"fontWeight",
|
|
114
|
+
"fontStyle",
|
|
115
|
+
"fontVariant",
|
|
116
|
+
"fontSize",
|
|
117
|
+
"fontStretch",
|
|
118
|
+
"color",
|
|
119
|
+
"backgroundColor",
|
|
120
|
+
"borderColor",
|
|
121
|
+
"outlineColor",
|
|
122
|
+
"textDecorationColor",
|
|
123
|
+
"display",
|
|
124
|
+
"position",
|
|
125
|
+
"visibility",
|
|
126
|
+
"overflow",
|
|
127
|
+
"overflowX",
|
|
128
|
+
"overflowY",
|
|
129
|
+
"cursor",
|
|
130
|
+
"pointerEvents",
|
|
131
|
+
"userSelect",
|
|
132
|
+
"appearance",
|
|
133
|
+
"resize",
|
|
134
|
+
"textAlign",
|
|
135
|
+
"verticalAlign",
|
|
136
|
+
"textDecoration",
|
|
137
|
+
"textTransform",
|
|
138
|
+
"textOverflow",
|
|
139
|
+
"whiteSpace",
|
|
140
|
+
"wordBreak",
|
|
141
|
+
"wordWrap",
|
|
142
|
+
"overflowWrap",
|
|
143
|
+
"lineBreak",
|
|
144
|
+
"hyphens",
|
|
145
|
+
"flexDirection",
|
|
146
|
+
"flexWrap",
|
|
147
|
+
"alignItems",
|
|
148
|
+
"alignContent",
|
|
149
|
+
"alignSelf",
|
|
150
|
+
"justifyContent",
|
|
151
|
+
"justifyItems",
|
|
152
|
+
"justifySelf",
|
|
153
|
+
"flexFlow",
|
|
154
|
+
"gridAutoFlow",
|
|
155
|
+
"gridAutoColumns",
|
|
156
|
+
"gridAutoRows",
|
|
157
|
+
"float",
|
|
158
|
+
"clear",
|
|
159
|
+
"objectFit",
|
|
160
|
+
"objectPosition",
|
|
161
|
+
"listStyle",
|
|
162
|
+
"listStyleType",
|
|
163
|
+
"borderStyle",
|
|
164
|
+
"outlineStyle",
|
|
165
|
+
"backgroundRepeat",
|
|
166
|
+
"backgroundAttachment",
|
|
167
|
+
"backgroundPosition",
|
|
168
|
+
"backgroundSize",
|
|
169
|
+
"backgroundBlendMode",
|
|
170
|
+
"mixBlendMode",
|
|
171
|
+
"isolation",
|
|
172
|
+
"boxSizing",
|
|
173
|
+
"tableLayout",
|
|
174
|
+
"captionSide",
|
|
175
|
+
"borderCollapse",
|
|
176
|
+
"imageRendering",
|
|
177
|
+
"shapeOutside",
|
|
178
|
+
"shapeBox",
|
|
179
|
+
"writingMode",
|
|
180
|
+
"direction",
|
|
181
|
+
"speak",
|
|
182
|
+
"contentVisibility",
|
|
183
|
+
// Animation / transition
|
|
184
|
+
"animationName",
|
|
185
|
+
"animationTimingFunction",
|
|
186
|
+
"animationFillMode",
|
|
187
|
+
"animationDirection",
|
|
188
|
+
"animationPlayState",
|
|
189
|
+
"transitionTimingFunction",
|
|
190
|
+
"transitionProperty",
|
|
191
|
+
"transformOrigin",
|
|
192
|
+
"transformBox",
|
|
193
|
+
"transformStyle",
|
|
194
|
+
// Event handler patterns
|
|
195
|
+
"onChange",
|
|
196
|
+
"onClick",
|
|
197
|
+
"onSubmit",
|
|
198
|
+
"onFocus",
|
|
199
|
+
"onBlur",
|
|
200
|
+
"onKeyDown",
|
|
201
|
+
"onKeyUp",
|
|
202
|
+
"onKeyPress",
|
|
203
|
+
"onMouseEnter",
|
|
204
|
+
"onMouseLeave",
|
|
205
|
+
"onMouseOver",
|
|
206
|
+
"onMouseOut",
|
|
207
|
+
"onMouseDown",
|
|
208
|
+
"onMouseUp",
|
|
209
|
+
"onDragStart",
|
|
210
|
+
"onDrop",
|
|
211
|
+
"onTouchStart",
|
|
212
|
+
"onTouchEnd",
|
|
213
|
+
"onTouchMove",
|
|
214
|
+
"onScroll",
|
|
215
|
+
"onResize",
|
|
216
|
+
"onLoad",
|
|
217
|
+
"onError",
|
|
218
|
+
"onAbort",
|
|
219
|
+
"onContextMenu",
|
|
220
|
+
"onDoubleClick",
|
|
221
|
+
"onSelect",
|
|
222
|
+
"onInput",
|
|
223
|
+
"onPaste",
|
|
224
|
+
"onCopy",
|
|
225
|
+
"onCut",
|
|
226
|
+
"onWheel"
|
|
227
|
+
]);
|
|
228
|
+
var CSS_UTILITY_FN_NAMES = /* @__PURE__ */ new Set([
|
|
229
|
+
"clsx",
|
|
230
|
+
"cx",
|
|
231
|
+
"classnames",
|
|
232
|
+
"classNames",
|
|
233
|
+
"cn",
|
|
234
|
+
"cc",
|
|
235
|
+
"twMerge",
|
|
236
|
+
"twJoin",
|
|
237
|
+
"tw",
|
|
238
|
+
"cva",
|
|
239
|
+
"ctl",
|
|
240
|
+
"classes",
|
|
241
|
+
"makeClasses",
|
|
242
|
+
"styled",
|
|
243
|
+
// styled-components / @emotion tag
|
|
244
|
+
"css"
|
|
245
|
+
// @emotion/css or linaria
|
|
246
|
+
]);
|
|
17
247
|
var AstScanner = class {
|
|
18
248
|
options;
|
|
19
249
|
detectedTexts = [];
|
|
250
|
+
compiledIgnorePatterns = [];
|
|
20
251
|
/** Identifiers whose call/bracket expressions contain already-translated strings. */
|
|
21
252
|
translationFunctionNames;
|
|
22
253
|
/**
|
|
@@ -27,6 +258,13 @@ var AstScanner = class {
|
|
|
27
258
|
importSourceMatchers;
|
|
28
259
|
constructor(options) {
|
|
29
260
|
this.options = options;
|
|
261
|
+
this.compiledIgnorePatterns = (options.ignoreTextPatterns ?? []).flatMap((p) => {
|
|
262
|
+
try {
|
|
263
|
+
return [new RegExp(p)];
|
|
264
|
+
} catch {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
});
|
|
30
268
|
this.translationFunctionNames = /* @__PURE__ */ new Set(["t", "$t", "i18n", "translate"]);
|
|
31
269
|
this.importSourceMatchers = Array.from(BUILTIN_TRANSLATION_IMPORT_SOURCES).map(
|
|
32
270
|
(pkg) => (src) => src === pkg
|
|
@@ -67,9 +305,10 @@ var AstScanner = class {
|
|
|
67
305
|
}
|
|
68
306
|
this.collectTranslationImports(ast);
|
|
69
307
|
traverse(ast, {
|
|
308
|
+
// ── JSX text nodes: <h1>Welcome</h1> ───────────────────────────────────
|
|
70
309
|
JSXText: (nodePath) => {
|
|
71
310
|
const text = normalizeText(nodePath.node.value);
|
|
72
|
-
if (!
|
|
311
|
+
if (!this.isTranslatableText(text)) return;
|
|
73
312
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
74
313
|
this.addDetected(
|
|
75
314
|
text,
|
|
@@ -79,13 +318,15 @@ var AstScanner = class {
|
|
|
79
318
|
"JSXText"
|
|
80
319
|
);
|
|
81
320
|
},
|
|
321
|
+
// ── JSX text-content attributes: placeholder="...", alt="..." ──────────
|
|
82
322
|
JSXAttribute: (nodePath) => {
|
|
83
323
|
const attrName = t.isJSXIdentifier(nodePath.node.name) ? nodePath.node.name.name : "";
|
|
84
324
|
if (!TEXT_ATTRIBUTE_NAMES.has(attrName.toLowerCase())) return;
|
|
325
|
+
if (NON_TRANSLATABLE_ATTR_NAMES.has(attrName.toLowerCase())) return;
|
|
85
326
|
const valueNode = nodePath.node.value;
|
|
86
327
|
if (!t.isStringLiteral(valueNode)) return;
|
|
87
328
|
const text = normalizeText(valueNode.value);
|
|
88
|
-
if (!
|
|
329
|
+
if (!this.isTranslatableText(text)) return;
|
|
89
330
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
90
331
|
const context = this.mapAttrToContext(attrName);
|
|
91
332
|
this.addDetected(
|
|
@@ -96,21 +337,23 @@ var AstScanner = class {
|
|
|
96
337
|
"JSXAttribute"
|
|
97
338
|
);
|
|
98
339
|
},
|
|
340
|
+
// ── StringLiteral in JS/TS expressions ─────────────────────────────────
|
|
99
341
|
StringLiteral: (nodePath) => {
|
|
100
342
|
if (t.isImportDeclaration(nodePath.parent)) return;
|
|
101
343
|
if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
|
|
102
|
-
if (t.isJSXAttribute(nodePath.parent))
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
return;
|
|
344
|
+
if (t.isJSXAttribute(nodePath.parent)) return;
|
|
345
|
+
if (t.isObjectProperty(nodePath.parent) && t.isIdentifier(nodePath.parent.key)) {
|
|
346
|
+
if (NON_TRANSLATABLE_PROP_KEYS.has(nodePath.parent.key.name)) return;
|
|
106
347
|
}
|
|
348
|
+
if (this.isInsideCssUtilityCall(nodePath)) return;
|
|
107
349
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
108
350
|
const val = nodePath.node.value;
|
|
109
|
-
if (/^[a-z][a-z0-9_
|
|
351
|
+
if (/^[a-z][a-z0-9_.\-]*$/.test(val) || // all-lowercase token (CSS class / id / key)
|
|
352
|
+
/^#?[0-9a-fA-F]+$/.test(val)) {
|
|
110
353
|
return;
|
|
111
354
|
}
|
|
112
|
-
const text = normalizeText(
|
|
113
|
-
if (!
|
|
355
|
+
const text = normalizeText(val);
|
|
356
|
+
if (!this.isTranslatableText(text)) return;
|
|
114
357
|
this.addDetected(
|
|
115
358
|
text,
|
|
116
359
|
nodePath.node.loc?.start.line ?? 0,
|
|
@@ -119,11 +362,12 @@ var AstScanner = class {
|
|
|
119
362
|
"StringLiteral"
|
|
120
363
|
);
|
|
121
364
|
},
|
|
365
|
+
// ── Template literals with no expressions: `Hello world` ───────────────
|
|
122
366
|
TemplateLiteral: (nodePath) => {
|
|
123
367
|
if (nodePath.node.expressions.length > 0) return;
|
|
124
368
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
125
369
|
const text = normalizeText(nodePath.node.quasis[0]?.value.cooked ?? "");
|
|
126
|
-
if (!
|
|
370
|
+
if (!this.isTranslatableText(text)) return;
|
|
127
371
|
this.addDetected(
|
|
128
372
|
text,
|
|
129
373
|
nodePath.node.loc?.start.line ?? 0,
|
|
@@ -135,6 +379,39 @@ var AstScanner = class {
|
|
|
135
379
|
});
|
|
136
380
|
return this.detectedTexts;
|
|
137
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Central check: is this text worth extracting as a locale key?
|
|
384
|
+
* Applies isHumanReadableText(), isCssClassString(), and user ignoreTextPatterns.
|
|
385
|
+
*/
|
|
386
|
+
isTranslatableText(text) {
|
|
387
|
+
if (!isHumanReadableText(text)) return false;
|
|
388
|
+
if (isCssClassString(text)) return false;
|
|
389
|
+
if (this.compiledIgnorePatterns.some((re) => re.test(text))) return false;
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Returns true when the node path is inside a CSS utility function call:
|
|
394
|
+
* clsx("a", "b"), cn("x"), twMerge("foo", "bar"), styled("div"), etc.
|
|
395
|
+
*/
|
|
396
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
397
|
+
isInsideCssUtilityCall(nodePath) {
|
|
398
|
+
let current = nodePath.parentPath;
|
|
399
|
+
while (current) {
|
|
400
|
+
const node = current.node;
|
|
401
|
+
if (t.isCallExpression(node)) {
|
|
402
|
+
const callee = node.callee;
|
|
403
|
+
if (t.isIdentifier(callee) && CSS_UTILITY_FN_NAMES.has(callee.name)) return true;
|
|
404
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property) && CSS_UTILITY_FN_NAMES.has(callee.property.name)) return true;
|
|
405
|
+
}
|
|
406
|
+
if (t.isTaggedTemplateExpression(node)) {
|
|
407
|
+
const tag = node.tag;
|
|
408
|
+
if (t.isIdentifier(tag) && CSS_UTILITY_FN_NAMES.has(tag.name)) return true;
|
|
409
|
+
if (t.isMemberExpression(tag) && t.isIdentifier(tag.object) && CSS_UTILITY_FN_NAMES.has(tag.object.name)) return true;
|
|
410
|
+
}
|
|
411
|
+
current = current.parentPath;
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
138
415
|
/**
|
|
139
416
|
* Walk import declarations; when the source matches a known translation
|
|
140
417
|
* import, collect all named/default imports as translation function names.
|
|
@@ -220,7 +497,7 @@ var AstScanner = class {
|
|
|
220
497
|
jsxTextRegex.lastIndex = 0;
|
|
221
498
|
while ((m = jsxTextRegex.exec(line)) !== null) {
|
|
222
499
|
const text = normalizeText(m[1]);
|
|
223
|
-
if (!
|
|
500
|
+
if (!this.isTranslatableText(text)) continue;
|
|
224
501
|
const key = generateKeyByStyle(
|
|
225
502
|
this.options.filePath,
|
|
226
503
|
text,
|
|
@@ -243,22 +520,22 @@ var AstScanner = class {
|
|
|
243
520
|
}
|
|
244
521
|
};
|
|
245
522
|
function buildImportMatcher(importPackage) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (
|
|
255
|
-
const
|
|
256
|
-
|
|
523
|
+
if (!importPackage.startsWith(".") && !importPackage.startsWith("/") && !importPackage.startsWith("@/")) {
|
|
524
|
+
return (src) => src === importPackage;
|
|
525
|
+
}
|
|
526
|
+
const normalise = (s) => s.replace(/^[@./]+/, "").replace(/\\/g, "/").toLowerCase();
|
|
527
|
+
const normTarget = normalise(importPackage);
|
|
528
|
+
const targetSegments = normTarget.split("/").filter(Boolean);
|
|
529
|
+
const segCount = targetSegments.length;
|
|
530
|
+
return (src) => {
|
|
531
|
+
if (src === importPackage) return true;
|
|
532
|
+
const normSrc = normalise(src);
|
|
533
|
+
const srcSegments = normSrc.split("/").filter(Boolean);
|
|
534
|
+
if (srcSegments.length < segCount) return false;
|
|
535
|
+
const tail = srcSegments.slice(-segCount);
|
|
536
|
+
return tail.join("/") === targetSegments.join("/");
|
|
257
537
|
};
|
|
258
538
|
}
|
|
259
|
-
function normalisePath(p) {
|
|
260
|
-
return p.replace(/\\/g, "/").replace(/^(@\/|\.{1,2}\/)+/, "").replace(/^@/, "");
|
|
261
|
-
}
|
|
262
539
|
|
|
263
540
|
// src/asset-scanner.ts
|
|
264
541
|
import * as fs from "fs";
|
|
@@ -368,15 +645,27 @@ import { readJsonSafe, writeJson, ensureDir } from "ai-localize-shared";
|
|
|
368
645
|
var IncrementalScanCache = class {
|
|
369
646
|
cachePath;
|
|
370
647
|
cache;
|
|
371
|
-
|
|
648
|
+
/**
|
|
649
|
+
* @param cacheDir Directory where `scan-cache.json` is stored.
|
|
650
|
+
* @param configHash SHA-256 hash of the resolved config object.
|
|
651
|
+
* When this differs from the persisted value the entire
|
|
652
|
+
* cache is invalidated so that config changes (keyStyle,
|
|
653
|
+
* ignoreTextPatterns, codemods, etc.) are always reflected.
|
|
654
|
+
*/
|
|
655
|
+
constructor(cacheDir, configHash) {
|
|
372
656
|
ensureDir(cacheDir);
|
|
373
657
|
this.cachePath = path2.join(cacheDir, "scan-cache.json");
|
|
374
|
-
this.cache = this.load();
|
|
658
|
+
this.cache = this.load(configHash);
|
|
375
659
|
}
|
|
376
|
-
load() {
|
|
660
|
+
load(configHash) {
|
|
377
661
|
const existing = readJsonSafe(this.cachePath);
|
|
378
|
-
if (existing?.version === "1")
|
|
379
|
-
|
|
662
|
+
if (existing?.version === "1") {
|
|
663
|
+
if (configHash && existing.configHash !== configHash) {
|
|
664
|
+
return { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), configHash, fileHashes: {}, processedFiles: {} };
|
|
665
|
+
}
|
|
666
|
+
return existing;
|
|
667
|
+
}
|
|
668
|
+
return { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), configHash, fileHashes: {}, processedFiles: {} };
|
|
380
669
|
}
|
|
381
670
|
isFileChanged(filePath) {
|
|
382
671
|
return this.hashFile(filePath) !== this.cache.fileHashes[filePath];
|
|
@@ -404,7 +693,13 @@ var IncrementalScanCache = class {
|
|
|
404
693
|
}
|
|
405
694
|
}
|
|
406
695
|
clear() {
|
|
407
|
-
this.cache = {
|
|
696
|
+
this.cache = {
|
|
697
|
+
version: "1",
|
|
698
|
+
lastRun: (/* @__PURE__ */ new Date()).toISOString(),
|
|
699
|
+
configHash: this.cache.configHash,
|
|
700
|
+
fileHashes: {},
|
|
701
|
+
processedFiles: {}
|
|
702
|
+
};
|
|
408
703
|
this.persist();
|
|
409
704
|
}
|
|
410
705
|
};
|
|
@@ -412,6 +707,7 @@ var IncrementalScanCache = class {
|
|
|
412
707
|
// src/project-scanner.ts
|
|
413
708
|
import * as path3 from "path";
|
|
414
709
|
import * as os from "os";
|
|
710
|
+
import * as crypto2 from "crypto";
|
|
415
711
|
import { collectFiles, DEFAULT_IGNORE_DIRS, SOURCE_EXTENSIONS } from "ai-localize-shared";
|
|
416
712
|
var ProjectScanner = class {
|
|
417
713
|
config;
|
|
@@ -424,10 +720,39 @@ var ProjectScanner = class {
|
|
|
424
720
|
this.assetScanner = new AssetScanner(config.aws?.legacyCdnPattern);
|
|
425
721
|
if (config.incrementalCache) {
|
|
426
722
|
this.cache = new IncrementalScanCache(
|
|
427
|
-
path3.join(process.cwd(), config.cacheDir || ".ai-localize-cache")
|
|
723
|
+
path3.join(process.cwd(), config.cacheDir || ".ai-localize-cache"),
|
|
724
|
+
this.hashConfig(config)
|
|
428
725
|
);
|
|
429
726
|
}
|
|
430
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Produces a stable SHA-256 fingerprint of the config fields that affect
|
|
730
|
+
* scan output. When any of these change the incremental cache is fully
|
|
731
|
+
* invalidated so the next run re-scans every file with the new settings.
|
|
732
|
+
*
|
|
733
|
+
* Fields intentionally excluded: `incrementalCache`, `cacheDir`, `aws`,
|
|
734
|
+
* `plugins` — none of those influence what text the AST scanner detects
|
|
735
|
+
* or how keys are generated.
|
|
736
|
+
*/
|
|
737
|
+
hashConfig(config) {
|
|
738
|
+
const relevant = {
|
|
739
|
+
framework: config.framework,
|
|
740
|
+
defaultLanguage: config.defaultLanguage,
|
|
741
|
+
targetLanguages: config.targetLanguages,
|
|
742
|
+
sourceDir: config.sourceDir,
|
|
743
|
+
localesDir: config.localesDir,
|
|
744
|
+
keyPrefix: config.keyPrefix,
|
|
745
|
+
namespaces: config.namespaces,
|
|
746
|
+
ignorePatterns: config.ignorePatterns,
|
|
747
|
+
includePatterns: config.includePatterns,
|
|
748
|
+
localeStructure: config.localeStructure,
|
|
749
|
+
keyStyle: config.keyStyle,
|
|
750
|
+
staticKeys: config.staticKeys,
|
|
751
|
+
ignoreTextPatterns: config.ignoreTextPatterns,
|
|
752
|
+
codemods: config.codemods
|
|
753
|
+
};
|
|
754
|
+
return crypto2.createHash("sha256").update(JSON.stringify(relevant)).digest("hex");
|
|
755
|
+
}
|
|
431
756
|
async scan(options = {}) {
|
|
432
757
|
const startTime = Date.now();
|
|
433
758
|
let filesToScan = [];
|
|
@@ -493,7 +818,8 @@ var ProjectScanner = class {
|
|
|
493
818
|
content,
|
|
494
819
|
sourceRoot: this.sourceRoot,
|
|
495
820
|
codemodConfig: this.config.codemods,
|
|
496
|
-
keyStyle: this.config.keyStyle ?? "path"
|
|
821
|
+
keyStyle: this.config.keyStyle ?? "path",
|
|
822
|
+
ignoreTextPatterns: this.config.ignoreTextPatterns ?? []
|
|
497
823
|
});
|
|
498
824
|
const texts = scanner.scan();
|
|
499
825
|
const { assets, legacyCdnUrls } = this.assetScanner.scanFile(filePath);
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-localize-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "AST-based hardcoded text scanner for frontend applications",
|
|
5
|
+
"author": "ai-localize-core contributors",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/ai-localize/ai-localize-core.git",
|
|
10
|
+
"directory": "packages/scanner"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/ai-localize/ai-localize-core/tree/main/packages/scanner#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ai-localize/ai-localize-core/issues"
|
|
15
|
+
},
|
|
5
16
|
"main": "./dist/index.js",
|
|
6
17
|
"module": "./dist/index.mjs",
|
|
7
18
|
"types": "./dist/index.d.ts",
|
|
19
|
+
"sideEffects": false,
|
|
8
20
|
"files": [
|
|
9
21
|
"dist",
|
|
10
22
|
"README.md",
|
|
@@ -39,8 +51,8 @@
|
|
|
39
51
|
"@babel/traverse": "^7.23.9",
|
|
40
52
|
"@babel/types": "^7.23.9",
|
|
41
53
|
"glob": "^10.3.10",
|
|
42
|
-
"ai-localize-shared": "2.0.
|
|
43
|
-
"ai-localize-config": "2.0.
|
|
54
|
+
"ai-localize-shared": "2.0.7",
|
|
55
|
+
"ai-localize-config": "2.0.7"
|
|
44
56
|
},
|
|
45
57
|
"devDependencies": {
|
|
46
58
|
"@types/babel__traverse": "^7.20.5",
|
|
@@ -48,7 +60,6 @@
|
|
|
48
60
|
"typescript": "^5.3.3",
|
|
49
61
|
"vitest": "^1.2.1"
|
|
50
62
|
},
|
|
51
|
-
"license": "MIT",
|
|
52
63
|
"publishConfig": {
|
|
53
64
|
"access": "public"
|
|
54
65
|
},
|