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.js
CHANGED
|
@@ -49,9 +49,239 @@ var BUILTIN_TRANSLATION_IMPORT_SOURCES = /* @__PURE__ */ new Set([
|
|
|
49
49
|
"vue-i18n",
|
|
50
50
|
"@ngx-translate/core"
|
|
51
51
|
]);
|
|
52
|
+
var NON_TRANSLATABLE_ATTR_NAMES = /* @__PURE__ */ new Set([
|
|
53
|
+
"classname",
|
|
54
|
+
"class",
|
|
55
|
+
"id",
|
|
56
|
+
"name",
|
|
57
|
+
"key",
|
|
58
|
+
"ref",
|
|
59
|
+
"type",
|
|
60
|
+
"method",
|
|
61
|
+
"action",
|
|
62
|
+
"enctype",
|
|
63
|
+
"href",
|
|
64
|
+
"src",
|
|
65
|
+
"srcset",
|
|
66
|
+
"action",
|
|
67
|
+
"to",
|
|
68
|
+
"as",
|
|
69
|
+
"path",
|
|
70
|
+
"url",
|
|
71
|
+
"rel",
|
|
72
|
+
"target",
|
|
73
|
+
"referrerpolicy",
|
|
74
|
+
"crossorigin",
|
|
75
|
+
"fetchpriority",
|
|
76
|
+
"loading",
|
|
77
|
+
"decoding",
|
|
78
|
+
"sizes",
|
|
79
|
+
"media",
|
|
80
|
+
"role",
|
|
81
|
+
"tabindex",
|
|
82
|
+
"autocomplete",
|
|
83
|
+
"inputmode",
|
|
84
|
+
"dir",
|
|
85
|
+
"lang",
|
|
86
|
+
"charset",
|
|
87
|
+
"for",
|
|
88
|
+
"htmlfor",
|
|
89
|
+
"htmlFor",
|
|
90
|
+
"accept",
|
|
91
|
+
"capture",
|
|
92
|
+
"pattern",
|
|
93
|
+
"autocorrect",
|
|
94
|
+
"autocapitalize",
|
|
95
|
+
"spellcheck",
|
|
96
|
+
"style",
|
|
97
|
+
"css",
|
|
98
|
+
"data-testid",
|
|
99
|
+
"data-cy",
|
|
100
|
+
"data-test",
|
|
101
|
+
"data-id",
|
|
102
|
+
"variant",
|
|
103
|
+
"color",
|
|
104
|
+
"size",
|
|
105
|
+
"shape",
|
|
106
|
+
"align",
|
|
107
|
+
"valign",
|
|
108
|
+
"justify",
|
|
109
|
+
"orientation",
|
|
110
|
+
"direction",
|
|
111
|
+
"placement",
|
|
112
|
+
"position",
|
|
113
|
+
"icon",
|
|
114
|
+
"iconname",
|
|
115
|
+
"lefticon",
|
|
116
|
+
"righticon"
|
|
117
|
+
]);
|
|
118
|
+
var NON_TRANSLATABLE_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
119
|
+
// HTML/JSX structural
|
|
120
|
+
"className",
|
|
121
|
+
"class",
|
|
122
|
+
"id",
|
|
123
|
+
"name",
|
|
124
|
+
"key",
|
|
125
|
+
"ref",
|
|
126
|
+
"type",
|
|
127
|
+
"method",
|
|
128
|
+
"action",
|
|
129
|
+
"href",
|
|
130
|
+
"src",
|
|
131
|
+
"rel",
|
|
132
|
+
"target",
|
|
133
|
+
"role",
|
|
134
|
+
"style",
|
|
135
|
+
"css",
|
|
136
|
+
"to",
|
|
137
|
+
"as",
|
|
138
|
+
"path",
|
|
139
|
+
"url",
|
|
140
|
+
"link",
|
|
141
|
+
"icon",
|
|
142
|
+
"variant",
|
|
143
|
+
"color",
|
|
144
|
+
"size",
|
|
145
|
+
// CSS-in-JS style properties
|
|
146
|
+
"fontFamily",
|
|
147
|
+
"fontWeight",
|
|
148
|
+
"fontStyle",
|
|
149
|
+
"fontVariant",
|
|
150
|
+
"fontSize",
|
|
151
|
+
"fontStretch",
|
|
152
|
+
"color",
|
|
153
|
+
"backgroundColor",
|
|
154
|
+
"borderColor",
|
|
155
|
+
"outlineColor",
|
|
156
|
+
"textDecorationColor",
|
|
157
|
+
"display",
|
|
158
|
+
"position",
|
|
159
|
+
"visibility",
|
|
160
|
+
"overflow",
|
|
161
|
+
"overflowX",
|
|
162
|
+
"overflowY",
|
|
163
|
+
"cursor",
|
|
164
|
+
"pointerEvents",
|
|
165
|
+
"userSelect",
|
|
166
|
+
"appearance",
|
|
167
|
+
"resize",
|
|
168
|
+
"textAlign",
|
|
169
|
+
"verticalAlign",
|
|
170
|
+
"textDecoration",
|
|
171
|
+
"textTransform",
|
|
172
|
+
"textOverflow",
|
|
173
|
+
"whiteSpace",
|
|
174
|
+
"wordBreak",
|
|
175
|
+
"wordWrap",
|
|
176
|
+
"overflowWrap",
|
|
177
|
+
"lineBreak",
|
|
178
|
+
"hyphens",
|
|
179
|
+
"flexDirection",
|
|
180
|
+
"flexWrap",
|
|
181
|
+
"alignItems",
|
|
182
|
+
"alignContent",
|
|
183
|
+
"alignSelf",
|
|
184
|
+
"justifyContent",
|
|
185
|
+
"justifyItems",
|
|
186
|
+
"justifySelf",
|
|
187
|
+
"flexFlow",
|
|
188
|
+
"gridAutoFlow",
|
|
189
|
+
"gridAutoColumns",
|
|
190
|
+
"gridAutoRows",
|
|
191
|
+
"float",
|
|
192
|
+
"clear",
|
|
193
|
+
"objectFit",
|
|
194
|
+
"objectPosition",
|
|
195
|
+
"listStyle",
|
|
196
|
+
"listStyleType",
|
|
197
|
+
"borderStyle",
|
|
198
|
+
"outlineStyle",
|
|
199
|
+
"backgroundRepeat",
|
|
200
|
+
"backgroundAttachment",
|
|
201
|
+
"backgroundPosition",
|
|
202
|
+
"backgroundSize",
|
|
203
|
+
"backgroundBlendMode",
|
|
204
|
+
"mixBlendMode",
|
|
205
|
+
"isolation",
|
|
206
|
+
"boxSizing",
|
|
207
|
+
"tableLayout",
|
|
208
|
+
"captionSide",
|
|
209
|
+
"borderCollapse",
|
|
210
|
+
"imageRendering",
|
|
211
|
+
"shapeOutside",
|
|
212
|
+
"shapeBox",
|
|
213
|
+
"writingMode",
|
|
214
|
+
"direction",
|
|
215
|
+
"speak",
|
|
216
|
+
"contentVisibility",
|
|
217
|
+
// Animation / transition
|
|
218
|
+
"animationName",
|
|
219
|
+
"animationTimingFunction",
|
|
220
|
+
"animationFillMode",
|
|
221
|
+
"animationDirection",
|
|
222
|
+
"animationPlayState",
|
|
223
|
+
"transitionTimingFunction",
|
|
224
|
+
"transitionProperty",
|
|
225
|
+
"transformOrigin",
|
|
226
|
+
"transformBox",
|
|
227
|
+
"transformStyle",
|
|
228
|
+
// Event handler patterns
|
|
229
|
+
"onChange",
|
|
230
|
+
"onClick",
|
|
231
|
+
"onSubmit",
|
|
232
|
+
"onFocus",
|
|
233
|
+
"onBlur",
|
|
234
|
+
"onKeyDown",
|
|
235
|
+
"onKeyUp",
|
|
236
|
+
"onKeyPress",
|
|
237
|
+
"onMouseEnter",
|
|
238
|
+
"onMouseLeave",
|
|
239
|
+
"onMouseOver",
|
|
240
|
+
"onMouseOut",
|
|
241
|
+
"onMouseDown",
|
|
242
|
+
"onMouseUp",
|
|
243
|
+
"onDragStart",
|
|
244
|
+
"onDrop",
|
|
245
|
+
"onTouchStart",
|
|
246
|
+
"onTouchEnd",
|
|
247
|
+
"onTouchMove",
|
|
248
|
+
"onScroll",
|
|
249
|
+
"onResize",
|
|
250
|
+
"onLoad",
|
|
251
|
+
"onError",
|
|
252
|
+
"onAbort",
|
|
253
|
+
"onContextMenu",
|
|
254
|
+
"onDoubleClick",
|
|
255
|
+
"onSelect",
|
|
256
|
+
"onInput",
|
|
257
|
+
"onPaste",
|
|
258
|
+
"onCopy",
|
|
259
|
+
"onCut",
|
|
260
|
+
"onWheel"
|
|
261
|
+
]);
|
|
262
|
+
var CSS_UTILITY_FN_NAMES = /* @__PURE__ */ new Set([
|
|
263
|
+
"clsx",
|
|
264
|
+
"cx",
|
|
265
|
+
"classnames",
|
|
266
|
+
"classNames",
|
|
267
|
+
"cn",
|
|
268
|
+
"cc",
|
|
269
|
+
"twMerge",
|
|
270
|
+
"twJoin",
|
|
271
|
+
"tw",
|
|
272
|
+
"cva",
|
|
273
|
+
"ctl",
|
|
274
|
+
"classes",
|
|
275
|
+
"makeClasses",
|
|
276
|
+
"styled",
|
|
277
|
+
// styled-components / @emotion tag
|
|
278
|
+
"css"
|
|
279
|
+
// @emotion/css or linaria
|
|
280
|
+
]);
|
|
52
281
|
var AstScanner = class {
|
|
53
282
|
options;
|
|
54
283
|
detectedTexts = [];
|
|
284
|
+
compiledIgnorePatterns = [];
|
|
55
285
|
/** Identifiers whose call/bracket expressions contain already-translated strings. */
|
|
56
286
|
translationFunctionNames;
|
|
57
287
|
/**
|
|
@@ -62,6 +292,13 @@ var AstScanner = class {
|
|
|
62
292
|
importSourceMatchers;
|
|
63
293
|
constructor(options) {
|
|
64
294
|
this.options = options;
|
|
295
|
+
this.compiledIgnorePatterns = (options.ignoreTextPatterns ?? []).flatMap((p) => {
|
|
296
|
+
try {
|
|
297
|
+
return [new RegExp(p)];
|
|
298
|
+
} catch {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
});
|
|
65
302
|
this.translationFunctionNames = /* @__PURE__ */ new Set(["t", "$t", "i18n", "translate"]);
|
|
66
303
|
this.importSourceMatchers = Array.from(BUILTIN_TRANSLATION_IMPORT_SOURCES).map(
|
|
67
304
|
(pkg) => (src) => src === pkg
|
|
@@ -102,9 +339,10 @@ var AstScanner = class {
|
|
|
102
339
|
}
|
|
103
340
|
this.collectTranslationImports(ast);
|
|
104
341
|
(0, import_traverse.default)(ast, {
|
|
342
|
+
// ── JSX text nodes: <h1>Welcome</h1> ───────────────────────────────────
|
|
105
343
|
JSXText: (nodePath) => {
|
|
106
344
|
const text = (0, import_ai_localize_shared.normalizeText)(nodePath.node.value);
|
|
107
|
-
if (!
|
|
345
|
+
if (!this.isTranslatableText(text)) return;
|
|
108
346
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
109
347
|
this.addDetected(
|
|
110
348
|
text,
|
|
@@ -114,13 +352,15 @@ var AstScanner = class {
|
|
|
114
352
|
"JSXText"
|
|
115
353
|
);
|
|
116
354
|
},
|
|
355
|
+
// ── JSX text-content attributes: placeholder="...", alt="..." ──────────
|
|
117
356
|
JSXAttribute: (nodePath) => {
|
|
118
357
|
const attrName = t.isJSXIdentifier(nodePath.node.name) ? nodePath.node.name.name : "";
|
|
119
358
|
if (!import_ai_localize_shared.TEXT_ATTRIBUTE_NAMES.has(attrName.toLowerCase())) return;
|
|
359
|
+
if (NON_TRANSLATABLE_ATTR_NAMES.has(attrName.toLowerCase())) return;
|
|
120
360
|
const valueNode = nodePath.node.value;
|
|
121
361
|
if (!t.isStringLiteral(valueNode)) return;
|
|
122
362
|
const text = (0, import_ai_localize_shared.normalizeText)(valueNode.value);
|
|
123
|
-
if (!
|
|
363
|
+
if (!this.isTranslatableText(text)) return;
|
|
124
364
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
125
365
|
const context = this.mapAttrToContext(attrName);
|
|
126
366
|
this.addDetected(
|
|
@@ -131,21 +371,23 @@ var AstScanner = class {
|
|
|
131
371
|
"JSXAttribute"
|
|
132
372
|
);
|
|
133
373
|
},
|
|
374
|
+
// ── StringLiteral in JS/TS expressions ─────────────────────────────────
|
|
134
375
|
StringLiteral: (nodePath) => {
|
|
135
376
|
if (t.isImportDeclaration(nodePath.parent)) return;
|
|
136
377
|
if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
|
|
137
|
-
if (t.isJSXAttribute(nodePath.parent))
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
return;
|
|
378
|
+
if (t.isJSXAttribute(nodePath.parent)) return;
|
|
379
|
+
if (t.isObjectProperty(nodePath.parent) && t.isIdentifier(nodePath.parent.key)) {
|
|
380
|
+
if (NON_TRANSLATABLE_PROP_KEYS.has(nodePath.parent.key.name)) return;
|
|
141
381
|
}
|
|
382
|
+
if (this.isInsideCssUtilityCall(nodePath)) return;
|
|
142
383
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
143
384
|
const val = nodePath.node.value;
|
|
144
|
-
if (/^[a-z][a-z0-9_
|
|
385
|
+
if (/^[a-z][a-z0-9_.\-]*$/.test(val) || // all-lowercase token (CSS class / id / key)
|
|
386
|
+
/^#?[0-9a-fA-F]+$/.test(val)) {
|
|
145
387
|
return;
|
|
146
388
|
}
|
|
147
|
-
const text = (0, import_ai_localize_shared.normalizeText)(
|
|
148
|
-
if (!
|
|
389
|
+
const text = (0, import_ai_localize_shared.normalizeText)(val);
|
|
390
|
+
if (!this.isTranslatableText(text)) return;
|
|
149
391
|
this.addDetected(
|
|
150
392
|
text,
|
|
151
393
|
nodePath.node.loc?.start.line ?? 0,
|
|
@@ -154,11 +396,12 @@ var AstScanner = class {
|
|
|
154
396
|
"StringLiteral"
|
|
155
397
|
);
|
|
156
398
|
},
|
|
399
|
+
// ── Template literals with no expressions: `Hello world` ───────────────
|
|
157
400
|
TemplateLiteral: (nodePath) => {
|
|
158
401
|
if (nodePath.node.expressions.length > 0) return;
|
|
159
402
|
if (this.isInsideTranslationCall(nodePath)) return;
|
|
160
403
|
const text = (0, import_ai_localize_shared.normalizeText)(nodePath.node.quasis[0]?.value.cooked ?? "");
|
|
161
|
-
if (!
|
|
404
|
+
if (!this.isTranslatableText(text)) return;
|
|
162
405
|
this.addDetected(
|
|
163
406
|
text,
|
|
164
407
|
nodePath.node.loc?.start.line ?? 0,
|
|
@@ -170,6 +413,39 @@ var AstScanner = class {
|
|
|
170
413
|
});
|
|
171
414
|
return this.detectedTexts;
|
|
172
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Central check: is this text worth extracting as a locale key?
|
|
418
|
+
* Applies isHumanReadableText(), isCssClassString(), and user ignoreTextPatterns.
|
|
419
|
+
*/
|
|
420
|
+
isTranslatableText(text) {
|
|
421
|
+
if (!(0, import_ai_localize_shared.isHumanReadableText)(text)) return false;
|
|
422
|
+
if ((0, import_ai_localize_shared.isCssClassString)(text)) return false;
|
|
423
|
+
if (this.compiledIgnorePatterns.some((re) => re.test(text))) return false;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Returns true when the node path is inside a CSS utility function call:
|
|
428
|
+
* clsx("a", "b"), cn("x"), twMerge("foo", "bar"), styled("div"), etc.
|
|
429
|
+
*/
|
|
430
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
431
|
+
isInsideCssUtilityCall(nodePath) {
|
|
432
|
+
let current = nodePath.parentPath;
|
|
433
|
+
while (current) {
|
|
434
|
+
const node = current.node;
|
|
435
|
+
if (t.isCallExpression(node)) {
|
|
436
|
+
const callee = node.callee;
|
|
437
|
+
if (t.isIdentifier(callee) && CSS_UTILITY_FN_NAMES.has(callee.name)) return true;
|
|
438
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property) && CSS_UTILITY_FN_NAMES.has(callee.property.name)) return true;
|
|
439
|
+
}
|
|
440
|
+
if (t.isTaggedTemplateExpression(node)) {
|
|
441
|
+
const tag = node.tag;
|
|
442
|
+
if (t.isIdentifier(tag) && CSS_UTILITY_FN_NAMES.has(tag.name)) return true;
|
|
443
|
+
if (t.isMemberExpression(tag) && t.isIdentifier(tag.object) && CSS_UTILITY_FN_NAMES.has(tag.object.name)) return true;
|
|
444
|
+
}
|
|
445
|
+
current = current.parentPath;
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
173
449
|
/**
|
|
174
450
|
* Walk import declarations; when the source matches a known translation
|
|
175
451
|
* import, collect all named/default imports as translation function names.
|
|
@@ -255,7 +531,7 @@ var AstScanner = class {
|
|
|
255
531
|
jsxTextRegex.lastIndex = 0;
|
|
256
532
|
while ((m = jsxTextRegex.exec(line)) !== null) {
|
|
257
533
|
const text = (0, import_ai_localize_shared.normalizeText)(m[1]);
|
|
258
|
-
if (!
|
|
534
|
+
if (!this.isTranslatableText(text)) continue;
|
|
259
535
|
const key = (0, import_ai_localize_shared.generateKeyByStyle)(
|
|
260
536
|
this.options.filePath,
|
|
261
537
|
text,
|
|
@@ -278,22 +554,22 @@ var AstScanner = class {
|
|
|
278
554
|
}
|
|
279
555
|
};
|
|
280
556
|
function buildImportMatcher(importPackage) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
const
|
|
291
|
-
|
|
557
|
+
if (!importPackage.startsWith(".") && !importPackage.startsWith("/") && !importPackage.startsWith("@/")) {
|
|
558
|
+
return (src) => src === importPackage;
|
|
559
|
+
}
|
|
560
|
+
const normalise = (s) => s.replace(/^[@./]+/, "").replace(/\\/g, "/").toLowerCase();
|
|
561
|
+
const normTarget = normalise(importPackage);
|
|
562
|
+
const targetSegments = normTarget.split("/").filter(Boolean);
|
|
563
|
+
const segCount = targetSegments.length;
|
|
564
|
+
return (src) => {
|
|
565
|
+
if (src === importPackage) return true;
|
|
566
|
+
const normSrc = normalise(src);
|
|
567
|
+
const srcSegments = normSrc.split("/").filter(Boolean);
|
|
568
|
+
if (srcSegments.length < segCount) return false;
|
|
569
|
+
const tail = srcSegments.slice(-segCount);
|
|
570
|
+
return tail.join("/") === targetSegments.join("/");
|
|
292
571
|
};
|
|
293
572
|
}
|
|
294
|
-
function normalisePath(p) {
|
|
295
|
-
return p.replace(/\\/g, "/").replace(/^(@\/|\.{1,2}\/)+/, "").replace(/^@/, "");
|
|
296
|
-
}
|
|
297
573
|
|
|
298
574
|
// src/asset-scanner.ts
|
|
299
575
|
var fs = __toESM(require("fs"));
|
|
@@ -403,15 +679,27 @@ var import_ai_localize_shared3 = require("ai-localize-shared");
|
|
|
403
679
|
var IncrementalScanCache = class {
|
|
404
680
|
cachePath;
|
|
405
681
|
cache;
|
|
406
|
-
|
|
682
|
+
/**
|
|
683
|
+
* @param cacheDir Directory where `scan-cache.json` is stored.
|
|
684
|
+
* @param configHash SHA-256 hash of the resolved config object.
|
|
685
|
+
* When this differs from the persisted value the entire
|
|
686
|
+
* cache is invalidated so that config changes (keyStyle,
|
|
687
|
+
* ignoreTextPatterns, codemods, etc.) are always reflected.
|
|
688
|
+
*/
|
|
689
|
+
constructor(cacheDir, configHash) {
|
|
407
690
|
(0, import_ai_localize_shared3.ensureDir)(cacheDir);
|
|
408
691
|
this.cachePath = path2.join(cacheDir, "scan-cache.json");
|
|
409
|
-
this.cache = this.load();
|
|
692
|
+
this.cache = this.load(configHash);
|
|
410
693
|
}
|
|
411
|
-
load() {
|
|
694
|
+
load(configHash) {
|
|
412
695
|
const existing = (0, import_ai_localize_shared3.readJsonSafe)(this.cachePath);
|
|
413
|
-
if (existing?.version === "1")
|
|
414
|
-
|
|
696
|
+
if (existing?.version === "1") {
|
|
697
|
+
if (configHash && existing.configHash !== configHash) {
|
|
698
|
+
return { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), configHash, fileHashes: {}, processedFiles: {} };
|
|
699
|
+
}
|
|
700
|
+
return existing;
|
|
701
|
+
}
|
|
702
|
+
return { version: "1", lastRun: (/* @__PURE__ */ new Date()).toISOString(), configHash, fileHashes: {}, processedFiles: {} };
|
|
415
703
|
}
|
|
416
704
|
isFileChanged(filePath) {
|
|
417
705
|
return this.hashFile(filePath) !== this.cache.fileHashes[filePath];
|
|
@@ -439,7 +727,13 @@ var IncrementalScanCache = class {
|
|
|
439
727
|
}
|
|
440
728
|
}
|
|
441
729
|
clear() {
|
|
442
|
-
this.cache = {
|
|
730
|
+
this.cache = {
|
|
731
|
+
version: "1",
|
|
732
|
+
lastRun: (/* @__PURE__ */ new Date()).toISOString(),
|
|
733
|
+
configHash: this.cache.configHash,
|
|
734
|
+
fileHashes: {},
|
|
735
|
+
processedFiles: {}
|
|
736
|
+
};
|
|
443
737
|
this.persist();
|
|
444
738
|
}
|
|
445
739
|
};
|
|
@@ -447,6 +741,7 @@ var IncrementalScanCache = class {
|
|
|
447
741
|
// src/project-scanner.ts
|
|
448
742
|
var path3 = __toESM(require("path"));
|
|
449
743
|
var os = __toESM(require("os"));
|
|
744
|
+
var crypto2 = __toESM(require("crypto"));
|
|
450
745
|
var import_ai_localize_shared4 = require("ai-localize-shared");
|
|
451
746
|
var ProjectScanner = class {
|
|
452
747
|
config;
|
|
@@ -459,10 +754,39 @@ var ProjectScanner = class {
|
|
|
459
754
|
this.assetScanner = new AssetScanner(config.aws?.legacyCdnPattern);
|
|
460
755
|
if (config.incrementalCache) {
|
|
461
756
|
this.cache = new IncrementalScanCache(
|
|
462
|
-
path3.join(process.cwd(), config.cacheDir || ".ai-localize-cache")
|
|
757
|
+
path3.join(process.cwd(), config.cacheDir || ".ai-localize-cache"),
|
|
758
|
+
this.hashConfig(config)
|
|
463
759
|
);
|
|
464
760
|
}
|
|
465
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Produces a stable SHA-256 fingerprint of the config fields that affect
|
|
764
|
+
* scan output. When any of these change the incremental cache is fully
|
|
765
|
+
* invalidated so the next run re-scans every file with the new settings.
|
|
766
|
+
*
|
|
767
|
+
* Fields intentionally excluded: `incrementalCache`, `cacheDir`, `aws`,
|
|
768
|
+
* `plugins` — none of those influence what text the AST scanner detects
|
|
769
|
+
* or how keys are generated.
|
|
770
|
+
*/
|
|
771
|
+
hashConfig(config) {
|
|
772
|
+
const relevant = {
|
|
773
|
+
framework: config.framework,
|
|
774
|
+
defaultLanguage: config.defaultLanguage,
|
|
775
|
+
targetLanguages: config.targetLanguages,
|
|
776
|
+
sourceDir: config.sourceDir,
|
|
777
|
+
localesDir: config.localesDir,
|
|
778
|
+
keyPrefix: config.keyPrefix,
|
|
779
|
+
namespaces: config.namespaces,
|
|
780
|
+
ignorePatterns: config.ignorePatterns,
|
|
781
|
+
includePatterns: config.includePatterns,
|
|
782
|
+
localeStructure: config.localeStructure,
|
|
783
|
+
keyStyle: config.keyStyle,
|
|
784
|
+
staticKeys: config.staticKeys,
|
|
785
|
+
ignoreTextPatterns: config.ignoreTextPatterns,
|
|
786
|
+
codemods: config.codemods
|
|
787
|
+
};
|
|
788
|
+
return crypto2.createHash("sha256").update(JSON.stringify(relevant)).digest("hex");
|
|
789
|
+
}
|
|
466
790
|
async scan(options = {}) {
|
|
467
791
|
const startTime = Date.now();
|
|
468
792
|
let filesToScan = [];
|
|
@@ -528,7 +852,8 @@ var ProjectScanner = class {
|
|
|
528
852
|
content,
|
|
529
853
|
sourceRoot: this.sourceRoot,
|
|
530
854
|
codemodConfig: this.config.codemods,
|
|
531
|
-
keyStyle: this.config.keyStyle ?? "path"
|
|
855
|
+
keyStyle: this.config.keyStyle ?? "path",
|
|
856
|
+
ignoreTextPatterns: this.config.ignoreTextPatterns ?? []
|
|
532
857
|
});
|
|
533
858
|
const texts = scanner.scan();
|
|
534
859
|
const { assets, legacyCdnUrls } = this.assetScanner.scanFile(filePath);
|