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 ADDED
@@ -0,0 +1,13 @@
1
+ # ai-localize-scanner
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - versoion change
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - ai-localize-config@2.0.0
13
+ - ai-localize-shared@2.0.0
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { DetectedText, AssetReference, LegacyCdnUrl, LocalizationConfig, ScanResult } from '@ai-localize/shared';
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 '@ai-localize/shared';
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 import_shared = require("@ai-localize/shared");
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, import_shared.normalizeText)(nodePath.node.value);
84
- if (!(0, import_shared.isHumanReadableText)(text)) return;
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 (!import_shared.TEXT_ATTRIBUTE_NAMES.has(attrName.toLowerCase())) return;
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, import_shared.normalizeText)(valueNode.value);
100
- if (!(0, import_shared.isHumanReadableText)(text)) return;
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)) return;
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
- if (/^[a-z][a-z0-9_.]+$/.test(nodePath.node.value)) return;
117
- const text = (0, import_shared.normalizeText)(nodePath.node.value);
118
- if (!(0, import_shared.isHumanReadableText)(text)) return;
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, import_shared.normalizeText)(nodePath.node.quasis[0]?.value.cooked ?? "");
131
- if (!(0, import_shared.isHumanReadableText)(text)) return;
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, import_shared.generateLocaleKey)(
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, import_shared.normalizeText)(m[1]);
206
- if (!(0, import_shared.isHumanReadableText)(text)) continue;
207
- const key = (0, import_shared.generateLocaleKey)(this.options.filePath, text, this.options.sourceRoot || "src");
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 import_shared2 = require("@ai-localize/shared");
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 (!import_shared2.ASSET_EXTENSIONS.some((ext) => url.includes(`.${ext}`))) continue;
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 import_shared2.ASSET_EXTENSIONS.includes(ext) ? ext : "other";
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 import_shared3 = require("@ai-localize/shared");
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, import_shared3.ensureDir)(cacheDir);
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, import_shared3.readJsonSafe)(this.cachePath);
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, import_shared3.writeJson)(this.cachePath, this.cache);
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 import_shared4 = require("@ai-localize/shared");
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, import_shared4.collectFiles)(this.sourceRoot, import_shared4.SOURCE_EXTENSIONS, [
395
- ...import_shared4.DEFAULT_IGNORE_DIRS,
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 "@ai-localize/shared";
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)) return;
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
- if (/^[a-z][a-z0-9_.]+$/.test(nodePath.node.value)) return;
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 "@ai-localize/shared";
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 "@ai-localize/shared";
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 "@ai-localize/shared";
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": "1.0.0",
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": "1.0.0",
21
- "ai-localize-config": "1.0.0"
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",
@@ -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 '@ai-localize/shared';
5
- import { ASSET_EXTENSIONS } from '@ai-localize/shared';
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;
@@ -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 '@ai-localize/shared';
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 '@ai-localize/shared';
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
- if (t.isJSXAttribute(nodePath.parent)) return;
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
- if (/^[a-z][a-z0-9_.]+$/.test(nodePath.node.value)) return;
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
- this.addDetected(
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
- 'StringLiteral'
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 '@ai-localize/shared';
6
- import { readJsonSafe, writeJson, ensureDir } from '@ai-localize/shared';
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;
@@ -7,8 +7,8 @@ import type {
7
7
  LegacyCdnUrl,
8
8
  ScanResult,
9
9
  LocalizationConfig,
10
- } from '@ai-localize/shared';
11
- import { collectFiles, DEFAULT_IGNORE_DIRS, SOURCE_EXTENSIONS } from '@ai-localize/shared';
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';