@chaoswise/intl 3.1.1 → 3.1.3

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.
@@ -4,6 +4,7 @@ const runUpdate = require('./scripts/update');
4
4
  const runInitConfig = require('./scripts/initConfig');
5
5
  const runAddLocale = require('./scripts/addLocale');
6
6
  const runNoZhCn = require('./scripts/nozhcn');
7
+ const runNoZhCnGuard = require('./scripts/nozhcnGuard');
7
8
  const runVerify = require('./scripts/verify');
8
9
 
9
10
  const SCRIPTS = ['intl', 'collect', 'update', 'nozhcn', 'addLocale', 'init', 'verify'];
@@ -27,11 +28,17 @@ if (script === 'addLocale') {
27
28
  }
28
29
  // verify: 检测代码中所有 intl.get('id') 是否都在 relationKey.json 或本地 locale 文件中
29
30
  // Usage:
30
- // chaoswise-intl verify → 发现孤立 id 时 exit(1)(CI 门禁)
31
- // chaoswise-intl verify --warn → 仅警告,不 exit(1)
31
+ // chaoswise-intl verify → 发现孤立 id 时 exit(1)(CI 门禁)
32
+ // chaoswise-intl verify --warn → 仅警告,不 exit(1)
33
+ // chaoswise-intl verify fix → 将孤立 id 的 intl 调用降级回字符串字面量(downgrade 策略)
32
34
  if (script === 'verify') {
33
- const exitOnMissing = !args.includes('--warn');
34
- runVerify({ exitOnMissing });
35
+ const subCommand = args[scriptIndex + 1];
36
+ if (subCommand === 'fix') {
37
+ runVerify.fix();
38
+ } else {
39
+ const exitOnMissing = !args.includes('--warn');
40
+ runVerify({ exitOnMissing });
41
+ }
35
42
  }
36
43
  // nozhcn: detect (and optionally fix) Chinese characters in source files
37
44
  // Usage:
@@ -39,9 +46,15 @@ if (script === 'verify') {
39
46
  // chaoswise-intl nozhcn check → same as above
40
47
  // chaoswise-intl nozhcn fix → auto-fix comments, report remaining issues
41
48
  // chaoswise-intl nozhcn report → non-blocking scan, output JSON report
49
+ // chaoswise-intl nozhcn guard → repository-level zero-Chinese guard (supports --report/--config/...)
42
50
  if (script === 'nozhcn') {
43
51
  // The sub-mode is the argument after 'nozhcn', default to 'check'
44
52
  const modeArg = args[scriptIndex + 1];
53
+ if (modeArg === 'guard') {
54
+ const guardArgs = args.slice(scriptIndex + 2);
55
+ runNoZhCnGuard(guardArgs);
56
+ return;
57
+ }
45
58
  const validModes = ['check', 'fix', 'report'];
46
59
  const mode = validModes.includes(modeArg) ? modeArg : 'check';
47
60
  runNoZhCn(mode);
@@ -104,8 +104,9 @@ module.exports = function (excludes = []) {
104
104
  // 'string' - 字符串字面量
105
105
  // 'template' - 模板字符串
106
106
  // 'jsx' - JSX 文本内容
107
+ // 'console' - console.* 日志中的中文(可自动清理)
107
108
  // 'identifier' - 标识符名称(极少出现,需手动修复)
108
- noZhCnCheckTypes: ['comment', 'string', 'template', 'jsx'],
109
+ noZhCnCheckTypes: ['comment', 'string', 'template', 'jsx', 'console'],
109
110
 
110
111
  // 注释中文修复策略(fix 模式生效):
111
112
  // 'clean' - 仅删除中文字符,保留非中文内容;若注释全为中文则整条删除
@@ -137,12 +138,25 @@ module.exports = function (excludes = []) {
137
138
  // 'empty' - 保留元素但清空内容:<title>搜索</title> → <title></title>
138
139
  noZhCnSvgMetadataStrategy: 'remove',
139
140
 
141
+ // JSX 内联 <svg> 的元数据元素(<title>、<desc>、<metadata>)修复策略(fix 模式生效):
142
+ // 'remove' - 删除整个元素
143
+ // 'empty' - 保留元素但清空内容
144
+ // false - 不自动修复 JSX 内联 SVG metadata(仅报告)
145
+ // undefined/null - 跟随 noZhCnSvgMetadataStrategy(向后兼容)
146
+ noZhCnJsxSvgMetadataStrategy: null,
147
+
140
148
  // SVG 属性中文修复策略(fix 模式生效):
141
149
  // 'clean' - 删除属性值中的中文字符(及 CJK 标点),清理后为空则移除整个属性;
142
150
  // 若修改了 id 属性,同步更新 SVG 内部引用(url(#…), href)
143
151
  // 'remove' - 移除包含中文的属性
144
152
  // false - 不自动修复属性中的中文
145
- noZhCnSvgAttrStrategy: 'clean', };
153
+ noZhCnSvgAttrStrategy: 'clean',
154
+
155
+ // console 中文日志修复策略(fix 模式生效):
156
+ // 'remove' - 删除包含中文的 console 语句(仅处理可安全定位的表达式语句)
157
+ // false - 不自动修复 console 中文日志(仅报告)
158
+ noZhCnConsoleStrategy: 'remove',
159
+ };
146
160
 
147
161
  excludes.forEach((key) => delete config[key]);
148
162
 
@@ -26,6 +26,9 @@ const getConf = require('./conf');
26
26
  const { targetEntryFiles } = require('./util/getTargetFiles');
27
27
  const findZhCnInFile = require('./util/findZhCnInFile');
28
28
  const fixZhCnInFile = require('./util/fixZhCnInFile');
29
+ const { fixJsxSvgAttrInFile } = require('./util/fixZhCnInFile');
30
+ const { fixJsxSvgMetadataInFile } = require('./util/fixZhCnInFile');
31
+ const { fixConsoleZhCnInFile } = require('./util/fixZhCnInFile');
29
32
  const fixI18nDefaultInFile = require('./util/fixI18nDefaultInFile');
30
33
  const findZhCnInSvgFile = require('./util/findZhCnInSvgFile');
31
34
  const fixZhCnInSvgFile = require('./util/fixZhCnInSvgFile');
@@ -35,12 +38,15 @@ const file = require('./util/file');
35
38
  // ─── Display labels ──────────────────────────────────────────────────────────
36
39
 
37
40
  const TYPE_LABEL = {
38
- comment: 'Comment ',
39
- string: 'String ',
40
- template: 'Template ',
41
- jsx: 'JSX Text ',
42
- 'jsx-svg': 'JSX SVG ',
43
- identifier: 'Identifier',
41
+ comment: 'Comment ',
42
+ string: 'String ',
43
+ template: 'Template ',
44
+ jsx: 'JSX Text ',
45
+ 'jsx-svg': 'JSX SVG ',
46
+ 'jsx-svg-attr': 'JSX SVG Attr',
47
+ 'jsx-svg-metadata': 'JSX SVG Meta',
48
+ console: 'Console Log ',
49
+ identifier: 'Identifier ',
44
50
  'svg-comment': 'SVG Comment ',
45
51
  'svg-metadata': 'SVG Metadata',
46
52
  'svg-text': 'SVG Text ',
@@ -53,6 +59,9 @@ const TYPE_COLOR = {
53
59
  template: chalk.yellow,
54
60
  jsx: chalk.cyan,
55
61
  'jsx-svg': chalk.magenta,
62
+ 'jsx-svg-attr': chalk.magenta,
63
+ 'jsx-svg-metadata': chalk.gray,
64
+ console: chalk.blue,
56
65
  identifier: chalk.red,
57
66
  'svg-comment': chalk.gray,
58
67
  'svg-metadata': chalk.gray,
@@ -61,8 +70,18 @@ const TYPE_COLOR = {
61
70
  };
62
71
 
63
72
  // Types that can be auto-fixed by nozhcn fix
64
- // Note: 'svg-attr' is conditionally fixable when noZhCnSvgAttrStrategy is set
65
- const AUTO_FIXABLE_TYPES = new Set(['comment', 'svg-comment', 'svg-metadata', 'svg-attr']);
73
+ // Note:
74
+ // - 'svg-attr' / 'jsx-svg-attr' are conditionally fixable when noZhCnSvgAttrStrategy is set
75
+ // - 'svg-metadata' / 'jsx-svg-metadata' are conditionally fixable when noZhCnSvgMetadataStrategy is set
76
+ const AUTO_FIXABLE_TYPES = new Set([
77
+ 'comment',
78
+ 'svg-comment',
79
+ 'svg-metadata',
80
+ 'svg-attr',
81
+ 'jsx-svg-attr',
82
+ 'jsx-svg-metadata',
83
+ 'console',
84
+ ]);
66
85
 
67
86
  // Types that `collect` script can handle
68
87
  const COLLECT_TYPES = new Set(['string', 'template', 'jsx']);
@@ -163,13 +182,23 @@ function printSummary(findings, mode, autoFixedCount, i18nDefaultFixed) {
163
182
  if (svgManualNeeded) {
164
183
  console.log(
165
184
  chalk.dim(
166
- ' Tip: SVG text/attribute occurrences require manual handling.\n' +
167
- ' For standalone .svg files, run fix to clean comments/metadata;\n' +
185
+ ' Tip: SVG visible text occurrences require manual handling.\n' +
186
+ ' For standalone .svg files, run fix to clean comments/metadata/attrs;\n' +
168
187
  ' for <text> content, consider replacing with a JS-driven label.'
169
188
  )
170
189
  );
171
190
  }
172
191
 
192
+ const jsxSvgAttrNeeded = findings.some((f) => f.type === 'jsx-svg-attr');
193
+ if (jsxSvgAttrNeeded) {
194
+ console.log(
195
+ chalk.dim(
196
+ " Tip: JSX SVG attribute values (e.g. id=\"编组1\") are design-tool layer name artifacts.\n" +
197
+ " Run 'chaoswise-intl nozhcn fix' to auto-strip Chinese characters from them."
198
+ )
199
+ );
200
+ }
201
+
173
202
  const identifierFound = findings.some((f) => f.type === 'identifier');
174
203
  if (identifierFound) {
175
204
  console.log(
@@ -201,7 +230,9 @@ module.exports = function noZhCn(mode) {
201
230
  noZhCnDefaultLang = null,
202
231
  noZhCnIncludeSvg = false,
203
232
  noZhCnSvgMetadataStrategy = 'remove',
233
+ noZhCnJsxSvgMetadataStrategy = null,
204
234
  noZhCnSvgAttrStrategy = 'clean',
235
+ noZhCnConsoleStrategy = 'remove',
205
236
  localeOutput = 'src/public',
206
237
  primaryRegx,
207
238
  babelPresets,
@@ -285,9 +316,9 @@ module.exports = function noZhCn(mode) {
285
316
 
286
317
  // ── Phase 2: Auto-fix (fix mode only) ────────────────────────────────────
287
318
  //
288
- // All fix sub-phases (2 / 2b / 2c) run first WITHOUT intermediate re-scans.
289
- // A single final re-scan at the end produces accurate remaining findings.
290
- // This avoids up to 3 redundant full-project AST parses on large codebases.
319
+ // Most fix sub-phases run without full-project intermediate re-scans.
320
+ // For offset-based JS/TS fixers (console / jsx-svg-attr / jsx-svg-metadata),
321
+ // we re-scan only affected files right before patching to avoid stale ranges.
291
322
 
292
323
  let autoFixedCount = 0;
293
324
  let i18nDefaultFixed = 0;
@@ -296,6 +327,15 @@ module.exports = function noZhCn(mode) {
296
327
 
297
328
  if (mode === 'fix') {
298
329
  let anyFixApplied = false;
330
+ function getLatestFindingsByType(filePath, type) {
331
+ const result = findZhCnInFile(filePath, scanOpts);
332
+ if (result.error) {
333
+ errorFiles.push({ filePath, error: result.error });
334
+ return [];
335
+ }
336
+ return result.findings.filter((f) => f.type === type);
337
+ }
338
+
299
339
  // ── Phase 2a: Fix comments in JS/TS files ─────────────────────────────
300
340
  const commentByFile = new Map();
301
341
  allFindings.forEach((f) => {
@@ -309,6 +349,24 @@ module.exports = function noZhCn(mode) {
309
349
  });
310
350
  if (autoFixedCount > 0) anyFixApplied = true;
311
351
 
352
+ // ── Phase 2a2: Remove Chinese console logs in JS/TS files ───────────
353
+ if (noZhCnConsoleStrategy) {
354
+ const consoleFiles = new Set();
355
+ allFindings.forEach((f) => {
356
+ if (f.type !== 'console') return;
357
+ consoleFiles.add(f.filePath);
358
+ });
359
+
360
+ let consoleFixedCount = 0;
361
+ consoleFiles.forEach((fp) => {
362
+ const findings = getLatestFindingsByType(fp, 'console');
363
+ if (!findings.length) return;
364
+ consoleFixedCount += fixConsoleZhCnInFile(fp, findings, noZhCnConsoleStrategy);
365
+ });
366
+ autoFixedCount += consoleFixedCount;
367
+ if (consoleFixedCount > 0) anyFixApplied = true;
368
+ }
369
+
312
370
  // ── Phase 2b: Replace .d('Chinese') with translations ─────────────────
313
371
  if (noZhCnDefaultLang) {
314
372
  const localePath = path.resolve(
@@ -333,10 +391,13 @@ module.exports = function noZhCn(mode) {
333
391
 
334
392
  if (localeMap) {
335
393
  const fixOpts = { i18nObject, i18nMethod, i18nDefaultFunctionKey, babelPresets, babelPlugins };
336
- // Collect JS/TS file paths from initial findings (exclude svg-* types)
337
- const filesToFix = new Set(
338
- allFindings.filter((f) => !f.type.startsWith('svg-')).map((f) => f.filePath)
339
- );
394
+ // Use ALL scanned files rather than only those with findings.
395
+ // When noZhCnIgnoreI18nDefault:true (the default), the initial Phase-1 scan
396
+ // blanks out .d('中文') before checking, so files whose ONLY Chinese text is
397
+ // inside .d() calls produce zero findings and would be silently skipped.
398
+ // fixI18nDefaultInFile has a cheap fast-path (no Chinese → early return),
399
+ // so scanning all files here causes no meaningful performance cost.
400
+ const filesToFix = new Set(files.map(({ filePath: fp }) => fp));
340
401
  filesToFix.forEach((fp) => {
341
402
  const result = fixI18nDefaultInFile(fp, localeMap, fixOpts);
342
403
  i18nDefaultFixed += result.fixed;
@@ -380,6 +441,53 @@ module.exports = function noZhCn(mode) {
380
441
  if (svgFixedCount > 0) anyFixApplied = true;
381
442
  }
382
443
 
444
+ // ── Phase 2d: Fix JSX SVG attribute strings in JS/TS files ───────────
445
+ // e.g. <svg id="编组1"> → <svg id="1"> (strip Chinese layer-name artifacts)
446
+ if (noZhCnSvgAttrStrategy) {
447
+ const jsxSvgAttrFiles = new Set();
448
+ allFindings.forEach((f) => {
449
+ if (f.type !== 'jsx-svg-attr') return;
450
+ jsxSvgAttrFiles.add(f.filePath);
451
+ });
452
+
453
+ jsxSvgAttrFiles.forEach((fp) => {
454
+ const findings = getLatestFindingsByType(fp, 'jsx-svg-attr');
455
+ if (!findings.length) return;
456
+ const fixed = fixJsxSvgAttrInFile(fp, findings, noZhCnSvgAttrStrategy);
457
+ svgFixedCount += fixed;
458
+ });
459
+ if (svgFixedCount > 0) anyFixApplied = true;
460
+ }
461
+
462
+ // ── Phase 2e: Fix JSX SVG metadata text in JS/TS files ───────────────
463
+ // e.g. <title>添加icon</title> (inside JSX <svg>)
464
+ // Strategy precedence:
465
+ // 1) noZhCnJsxSvgMetadataStrategy (if explicitly set, including false)
466
+ // 2) fallback to noZhCnSvgMetadataStrategy (backward-compat)
467
+ const hasExplicitJsxMetaStrategy = Object.prototype.hasOwnProperty.call(
468
+ conf,
469
+ 'noZhCnJsxSvgMetadataStrategy'
470
+ );
471
+ const jsxMetaStrategy = hasExplicitJsxMetaStrategy
472
+ ? noZhCnJsxSvgMetadataStrategy
473
+ : noZhCnSvgMetadataStrategy;
474
+
475
+ if (jsxMetaStrategy) {
476
+ const jsxSvgMetadataFiles = new Set();
477
+ allFindings.forEach((f) => {
478
+ if (f.type !== 'jsx-svg-metadata') return;
479
+ jsxSvgMetadataFiles.add(f.filePath);
480
+ });
481
+
482
+ jsxSvgMetadataFiles.forEach((fp) => {
483
+ const findings = getLatestFindingsByType(fp, 'jsx-svg-metadata');
484
+ if (!findings.length) return;
485
+ const fixed = fixJsxSvgMetadataInFile(fp, findings, jsxMetaStrategy);
486
+ svgFixedCount += fixed;
487
+ });
488
+ if (svgFixedCount > 0) anyFixApplied = true;
489
+ }
490
+
383
491
  // ── Single final re-scan after all fixes ──────────────────────────────
384
492
  if (anyFixApplied) {
385
493
  allFindings = [];