@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.
- package/bin/chaoswise-intl.js +17 -4
- package/bin/scripts/conf/default.js +16 -2
- package/bin/scripts/nozhcn.js +125 -17
- package/bin/scripts/nozhcnGuard.js +444 -0
- package/bin/scripts/util/findZhCnInFile.js +127 -6
- package/bin/scripts/util/fixI18nDefaultInFile.js +102 -4
- package/bin/scripts/util/fixZhCnInFile.js +290 -0
- package/bin/scripts/util/makeVisitorDowngrade.js +82 -0
- package/bin/scripts/util/transformAst.js +11 -2
- package/bin/scripts/verify.js +115 -34
- package/package.json +1 -1
package/bin/chaoswise-intl.js
CHANGED
|
@@ -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
|
|
31
|
-
// chaoswise-intl verify --warn
|
|
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
|
|
34
|
-
|
|
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
|
|
package/bin/scripts/nozhcn.js
CHANGED
|
@@ -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:
|
|
39
|
-
string:
|
|
40
|
-
template:
|
|
41
|
-
jsx:
|
|
42
|
-
'jsx-svg':
|
|
43
|
-
|
|
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:
|
|
65
|
-
|
|
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
|
|
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
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
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
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
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 = [];
|