@chaoswise/intl 2.1.10 → 3.1.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.
@@ -0,0 +1,217 @@
1
+ /**
2
+ * fixZhCnInFile.js
3
+ *
4
+ * Fixes Chinese characters found in COMMENT nodes by patching the raw source
5
+ * using byte-offset positions from AST tokens.
6
+ *
7
+ * Strategy options:
8
+ * 'clean' - Remove only Chinese characters from comment value.
9
+ * If the cleaned value is whitespace-only, remove the entire
10
+ * comment token (including delimiters // or /* ... *\/).
11
+ * 'remove' - Remove the entire comment token unconditionally.
12
+ *
13
+ * Important: This function operates on raw source bytes:
14
+ * - It sorts findings by `start` in descending order so that later
15
+ * replacements do not invalidate earlier offsets.
16
+ * - Only `comment` type findings (which carry `start` and `end` byte
17
+ * offsets) are accepted.
18
+ *
19
+ * Returns the number of comment tokens patched (0 if nothing was modified).
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const log = require('./log');
24
+
25
+ // Chinese character range, same as primaryRegx default
26
+ const ZH_REGEX = /[\u4e00-\u9fa5]/g;
27
+
28
+ /**
29
+ * Compute the replacement string for a single comment token.
30
+ *
31
+ * In Babel AST (with tokens:true):
32
+ * CommentLine : value = text after '//' (no newline)
33
+ * CommentBlock : value = text between '/*' and '*\/'
34
+ *
35
+ * source.slice(start, end) gives the full token including delimiters.
36
+ *
37
+ * @param {{ type: string, value: string, start: number, end: number }} comment
38
+ * @param {'clean'|'remove'} strategy
39
+ * @returns {string|null} null means "delete the token entirely"
40
+ */
41
+ function computeReplacement(comment, strategy) {
42
+ const { type, value } = comment;
43
+
44
+ if (strategy === 'remove') {
45
+ return null;
46
+ }
47
+
48
+ // strategy === 'clean': strip Chinese characters from value
49
+ const cleanedValue = value.replace(ZH_REGEX, '');
50
+
51
+ if (!cleanedValue.trim()) {
52
+ // Comment content is now empty → remove the token
53
+ return null;
54
+ }
55
+
56
+ if (type === 'CommentLine') {
57
+ return `//${cleanedValue}`;
58
+ }
59
+ // CommentBlock
60
+ return `/*${cleanedValue}*/`;
61
+ }
62
+
63
+ /**
64
+ * After removing a comment token, if the rest of the line is blank
65
+ * (only whitespace between the last newline and the token start),
66
+ * also remove the trailing newline so we don't leave empty lines.
67
+ *
68
+ * Special case – JSX expression-container comments { /‌* ... *‌/ } (where
69
+ * the comment is the sole content of a JSX expression): the surrounding
70
+ * { } braces must also be removed, otherwise an orphan {} expression is left.
71
+ * We detect this by checking whether the characters directly flanking the
72
+ * comment token (past optional spaces) are { and }, and if so extend the
73
+ * deletion range to cover them as well.
74
+ *
75
+ * @param {string} source
76
+ * @param {number} tokenStart - char index of the comment token start
77
+ * @param {number} tokenEnd - char index just past the comment token end
78
+ * @returns {{ patchStart: number, patchEnd: number }}
79
+ */
80
+ function calcDeletionRange(source, tokenStart, tokenEnd) {
81
+ // ── Determine the effective deletion boundaries ───────────────────────────
82
+ // For JSX expression-container comments `{ /* ... */ }`, we want to delete
83
+ // the wrapping braces as well, otherwise we leave an orphan `{}` expression.
84
+ let effectiveStart = tokenStart;
85
+ let effectiveEnd = tokenEnd;
86
+
87
+ // Walk backwards past optional spaces to find a potential `{`
88
+ let braceOpen = tokenStart - 1;
89
+ while (braceOpen >= 0 && source[braceOpen] === ' ') braceOpen--;
90
+
91
+ // Walk forwards past optional spaces to find a potential `}`
92
+ let braceClose = tokenEnd;
93
+ while (braceClose < source.length && source[braceClose] === ' ') braceClose++;
94
+
95
+ if (
96
+ braceOpen >= 0 && source[braceOpen] === '{' &&
97
+ braceClose < source.length && source[braceClose] === '}'
98
+ ) {
99
+ // The comment is the sole content of a JSX `{...}` expression — include the braces.
100
+ effectiveStart = braceOpen;
101
+ effectiveEnd = braceClose + 1;
102
+ }
103
+
104
+ // ── Guard: if tokenEnd somehow overshot a newline, clamp back ────────────
105
+ // Babel's comment.end should never include the trailing \n of a CommentLine,
106
+ // but be defensive: if the character just before effectiveEnd is a newline,
107
+ // we are already at the start of the next line — step back to the \n itself
108
+ // so the lineEnd walk below stays on the correct line.
109
+ let adjustedEnd = effectiveEnd;
110
+ if (adjustedEnd > effectiveStart && adjustedEnd > 0 &&
111
+ source[adjustedEnd - 1] === '\n' &&
112
+ (adjustedEnd >= source.length || source[adjustedEnd] !== '\n')) {
113
+ adjustedEnd--;
114
+ }
115
+
116
+ // ── Decide whether to remove the whole line or just the token ────────────
117
+ let lineStart = effectiveStart;
118
+ while (lineStart > 0 && source[lineStart - 1] !== '\n') {
119
+ lineStart--;
120
+ }
121
+ const before = source.slice(lineStart, effectiveStart);
122
+ const onlyWhitespaceBefore = /^\s*$/.test(before);
123
+
124
+ let lineEnd = adjustedEnd;
125
+ while (lineEnd < source.length && source[lineEnd] !== '\n') {
126
+ lineEnd++;
127
+ }
128
+ const after = source.slice(adjustedEnd, lineEnd);
129
+ const onlyWhitespaceAfter = /^\s*$/.test(after);
130
+
131
+ if (onlyWhitespaceBefore && onlyWhitespaceAfter) {
132
+ // Remove the entire line including the trailing newline (if present)
133
+ const end = lineEnd < source.length ? lineEnd + 1 : lineEnd;
134
+ return { patchStart: lineStart, patchEnd: end };
135
+ }
136
+
137
+ // Comment is inline on a code line — just remove the effective range
138
+ return { patchStart: effectiveStart, patchEnd: effectiveEnd };
139
+ }
140
+
141
+ /**
142
+ * Fix all comment findings in a single file.
143
+ *
144
+ * @param {string} filePath - absolute file path
145
+ * @param {Finding[]} findings - array of { type:'comment', start, end, commentType, ... }
146
+ * @param {'clean'|'remove'} strategy
147
+ * @returns {number} count of comment findings actually patched (0 if none)
148
+ */
149
+ module.exports = function fixZhCnInFile(filePath, findings, strategy = 'clean') {
150
+ // Filter to comment-type findings that have byte offsets
151
+ const commentFindings = findings.filter(
152
+ (f) => f.type === 'comment' && typeof f.start === 'number' && typeof f.end === 'number'
153
+ );
154
+
155
+ if (!commentFindings.length) return 0;
156
+
157
+ let source;
158
+ try {
159
+ source = fs.readFileSync(filePath, 'utf8');
160
+ } catch (err) {
161
+ log.error(`[nozhcn] Cannot read file for fixing: ${filePath} — ${err.message}`);
162
+ return 0;
163
+ }
164
+
165
+ // Sort descending by start so we patch from the end backwards,
166
+ // preserving the validity of earlier byte offsets.
167
+ const sorted = [...commentFindings].sort((a, b) => b.start - a.start);
168
+
169
+ let patchedCount = 0;
170
+
171
+ for (const finding of sorted) {
172
+ const { start, end, commentType } = finding;
173
+
174
+ // Reconstruct the original comment object expected by computeReplacement
175
+ const originalSource = source.slice(start, end);
176
+
177
+ // Extract the raw value (without delimiters) from the source token
178
+ // so that computeReplacement works even if the finding.content is
179
+ // already formatted with delimiters.
180
+ let rawValue;
181
+ if (commentType === 'CommentLine') {
182
+ // source token = '//' + value
183
+ rawValue = originalSource.startsWith('//') ? originalSource.slice(2) : originalSource;
184
+ } else {
185
+ // source token = '/*' + value + '*/'
186
+ if (originalSource.startsWith('/*') && originalSource.endsWith('*/')) {
187
+ rawValue = originalSource.slice(2, -2);
188
+ } else {
189
+ rawValue = originalSource;
190
+ }
191
+ }
192
+
193
+ const pseudoComment = { type: commentType, value: rawValue, start, end };
194
+ const replacement = computeReplacement(pseudoComment, strategy);
195
+
196
+ if (replacement === null) {
197
+ // Delete the token (and possibly the whole line if it becomes empty)
198
+ const { patchStart, patchEnd } = calcDeletionRange(source, start, end);
199
+ source = source.slice(0, patchStart) + source.slice(patchEnd);
200
+ } else {
201
+ source = source.slice(0, start) + replacement + source.slice(end);
202
+ }
203
+
204
+ patchedCount++;
205
+ }
206
+
207
+ if (patchedCount > 0) {
208
+ try {
209
+ fs.writeFileSync(filePath, source, { encoding: 'utf-8' });
210
+ } catch (err) {
211
+ log.error(`[nozhcn] Cannot write file: ${filePath} — ${err.message}`);
212
+ return 0;
213
+ }
214
+ }
215
+
216
+ return patchedCount;
217
+ };
@@ -0,0 +1,206 @@
1
+ /**
2
+ * fixZhCnInSvgFile.js
3
+ *
4
+ * Auto-fixes Chinese characters in standalone SVG files.
5
+ *
6
+ * Fixable types (from findZhCnInSvgFile.js):
7
+ * svg-comment ─ auto-fixable via comment clean/remove strategy
8
+ * svg-metadata ─ auto-fixable: remove element or empty its content
9
+ *
10
+ * Conditionally fixable types:
11
+ * svg-attr ─ attribute values (e.g. id="矩形", id="编组-11")
12
+ * typically design-tool layer-name artifacts; fixable
13
+ * when attrStrategy is set to 'clean' or 'remove'
14
+ *
15
+ * Non-fixable types (reported but not touched):
16
+ * svg-text ─ visible rendered text, requires design/i18n decision
17
+ *
18
+ * Strategies
19
+ * ──────────────────────────────────────────────────────────────────────
20
+ * commentStrategy ('clean' | 'remove')
21
+ * 'clean' : Remove only Chinese characters from the comment value.
22
+ * If the cleaned value is whitespace-only, delete the comment.
23
+ * 'remove' : Delete the entire comment unconditionally.
24
+ *
25
+ * metadataStrategy ('remove' | 'empty')
26
+ * 'remove' : Delete the entire <title>/<desc>/<metadata> element.
27
+ * 'empty' : Keep the element but empty its contents:
28
+ * <title>搜索</title> → <title></title>
29
+ *
30
+ * attrStrategy ('clean' | 'remove' | false)
31
+ * 'clean' : Strip Chinese (and CJK punctuation) from attribute values.
32
+ * If the cleaned value is empty, remove the attribute entirely.
33
+ * Renamed id attributes update internal url(#…) / href refs.
34
+ * 'remove' : Remove the entire attribute.
35
+ * false : Do not auto-fix attributes (default, backward-compat).
36
+ *
37
+ * Returns { fixed: number } (count of SVG elements/comments fixed)
38
+ */
39
+
40
+ const fs = require('fs');
41
+ const log = require('./log');
42
+
43
+ const ZH_REGEX = /[\u4e00-\u9fa5]/g;
44
+ const ZH_TEST = /[\u4e00-\u9fa5]/;
45
+ // Chinese chars + CJK symbols/punctuation + fullwidth forms (design-tool junk)
46
+ const ZH_CLEAN_REGEX = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/g;
47
+
48
+ function escapeRegExp(str) {
49
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
50
+ }
51
+
52
+ /**
53
+ * Apply attribute fix strategy to the full source string.
54
+ * Handles id renames with internal SVG reference updates.
55
+ */
56
+ function fixAttrs(source, strategy) {
57
+ const idRenames = new Map(); // oldValue → newValue | null
58
+
59
+ // Pass 1: Process ONLY id attributes with Chinese values
60
+ source = source.replace(
61
+ /(\s)(id)=(["'])([^"']*[\u4e00-\u9fa5][^"']*)\3/g,
62
+ (match, ws, attr, quote, value) => {
63
+ if (strategy === 'remove') {
64
+ idRenames.set(value, null);
65
+ return '';
66
+ }
67
+ // 'clean': strip Chinese + CJK punctuation
68
+ const cleaned = value.replace(ZH_CLEAN_REGEX, '').trim();
69
+ if (!cleaned) {
70
+ idRenames.set(value, null);
71
+ return '';
72
+ }
73
+ idRenames.set(value, cleaned);
74
+ return `${ws}id=${quote}${cleaned}${quote}`;
75
+ }
76
+ );
77
+
78
+ // Pass 2: Update internal references for renamed / removed ids
79
+ idRenames.forEach((newId, oldId) => {
80
+ const esc = escapeRegExp(oldId);
81
+ if (newId) {
82
+ source = source.replace(new RegExp(`url\\(#${esc}\\)`, 'g'), `url(#${newId})`);
83
+ source = source.replace(
84
+ new RegExp(`(xlink:href|href)=(["'])#${esc}\\2`, 'g'),
85
+ `$1=$2#${newId}$2`
86
+ );
87
+ } else {
88
+ // id was removed — neutralise dangling url() references
89
+ source = source.replace(new RegExp(`url\\(#${esc}\\)`, 'g'), 'none');
90
+ }
91
+ });
92
+
93
+ // Pass 3: Process remaining (non-id) attributes with Chinese values
94
+ source = source.replace(
95
+ /(\s)([\w:_-]+)=(["'])([^"']*[\u4e00-\u9fa5][^"']*)\3/g,
96
+ (match, ws, attr, quote, value) => {
97
+ if (attr === 'id') return match; // already handled
98
+ if (strategy === 'remove') return '';
99
+ const cleaned = value.replace(ZH_CLEAN_REGEX, '').trim();
100
+ if (!cleaned) return '';
101
+ return `${ws}${attr}=${quote}${cleaned}${quote}`;
102
+ }
103
+ );
104
+
105
+ return source;
106
+ }
107
+
108
+ /**
109
+ * Apply comment fix strategy to the full source string.
110
+ */
111
+ function fixComments(source, strategy) {
112
+ return source.replace(/<!--([\s\S]*?)-->/g, (match, content) => {
113
+ if (!ZH_TEST.test(content)) return match;
114
+ if (strategy === 'remove') return '';
115
+ const cleaned = content.replace(ZH_REGEX, '');
116
+ if (!cleaned.trim()) return '';
117
+ return `<!--${cleaned}-->`;
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Apply metadata element fix strategy to the full source string.
123
+ * Handles <title>, <desc>, <metadata> (case-insensitive, with attributes).
124
+ */
125
+ function fixMetadata(source, strategy) {
126
+ return source.replace(/<(title|desc|metadata)(\s[^>]*)?>[\s\S]*?<\/\1>/gi, (match, tag, attrs) => {
127
+ if (!ZH_TEST.test(match)) return match;
128
+ if (strategy === 'remove') return '';
129
+ // 'empty': keep the opening/closing tags, remove content
130
+ const openTag = attrs ? `<${tag}${attrs}>` : `<${tag}>`;
131
+ return `${openTag}</${tag}>`;
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Fix Chinese in SVG comments and metadata elements.
137
+ *
138
+ * @param {string} filePath - absolute path to the .svg file
139
+ * @param {Finding[]} findings - from findZhCnInSvgFile (all types)
140
+ * @param {object} opts
141
+ * - commentStrategy 'clean' | 'remove' (default: 'clean')
142
+ * - metadataStrategy 'remove' | 'empty' (default: 'remove')
143
+ * - attrStrategy 'clean' | 'remove' | false (default: false)
144
+ * @returns {{ fixed: number }}
145
+ */
146
+ module.exports = function fixZhCnInSvgFile(filePath, findings, opts) {
147
+ const {
148
+ commentStrategy = 'clean',
149
+ metadataStrategy = 'remove',
150
+ attrStrategy = false,
151
+ } = opts || {};
152
+
153
+ const hasFixableFindings = findings.some(
154
+ (f) => f.type === 'svg-comment' || f.type === 'svg-metadata' ||
155
+ (f.type === 'svg-attr' && attrStrategy)
156
+ );
157
+ if (!hasFixableFindings) return { fixed: 0 };
158
+
159
+ let source;
160
+ try {
161
+ source = fs.readFileSync(filePath, 'utf8');
162
+ } catch (err) {
163
+ log.error(`[nozhcn] Cannot read SVG file: ${filePath} — ${err.message}`);
164
+ return { fixed: 0 };
165
+ }
166
+
167
+ const before = source;
168
+
169
+ // Fix comments
170
+ const hasCommentFindings = findings.some((f) => f.type === 'svg-comment');
171
+ if (hasCommentFindings) {
172
+ source = fixComments(source, commentStrategy);
173
+ }
174
+
175
+ // Fix metadata elements
176
+ const hasMetadataFindings = findings.some((f) => f.type === 'svg-metadata');
177
+ if (hasMetadataFindings) {
178
+ source = fixMetadata(source, metadataStrategy);
179
+ }
180
+
181
+ // Fix attributes (opt-in)
182
+ const hasAttrFindings = findings.some((f) => f.type === 'svg-attr');
183
+ if (hasAttrFindings && attrStrategy) {
184
+ source = fixAttrs(source, attrStrategy);
185
+ }
186
+
187
+ if (source === before) return { fixed: 0 };
188
+
189
+ // Clean up blank lines left behind by removed elements/comments
190
+ source = source.replace(/\n[ \t]*\n/g, '\n');
191
+
192
+ // Count how many fixable findings were addressed
193
+ const fixed = findings.filter(
194
+ (f) => f.type === 'svg-comment' || f.type === 'svg-metadata' ||
195
+ (f.type === 'svg-attr' && attrStrategy)
196
+ ).length;
197
+
198
+ try {
199
+ fs.writeFileSync(filePath, source, { encoding: 'utf-8' });
200
+ } catch (err) {
201
+ log.error(`[nozhcn] Cannot write SVG file: ${filePath} — ${err.message}`);
202
+ return { fixed: 0 };
203
+ }
204
+
205
+ return { fixed };
206
+ };
@@ -159,19 +159,27 @@ module.exports = function (
159
159
  }
160
160
 
161
161
  // XXX: [TRICKY] 防止中文转码为 unicode
162
+ // 注意:recast 打印 StringLiteral 时直接使用 node.value,而不是 extra.raw
163
+ // 所以需要直接创建值为 id 的字符串节点,同时保留原始中文在 extra 中
162
164
  function hackValue(value, id) {
163
- if (id) hacked[id] = true;
165
+ // 如果 id 未定义,使用 value 作为回退(用于第一次收集阶段)
166
+ const actualId = id || value;
167
+ if (actualId) hacked[actualId] = true;
164
168
 
165
169
  // 字符串默认是单引号,如果模板字符串中出现单引号,需要转义,把 ' 换成 \'
166
170
  // 就算用户自定配置字符串为双引号,也没事,默认会转义双引号,但是默认不会处理单引号,所以自己手动处理
167
- if (/\'/.test(id)) {
168
- id = id.replace(/\'/g, "\\'");
171
+ let rawValue = actualId;
172
+ if (/\'/.test(actualId)) {
173
+ rawValue = actualId.replace(/\'/g, "\\'");
169
174
  }
170
175
 
171
- return Object.assign(t.StringLiteral(value), {
176
+ // 直接创建值为 actualId 的字符串节点,recast 会使用 node.value 作为输出
177
+ return Object.assign(t.StringLiteral(actualId), {
172
178
  extra: {
173
- raw: `'${id}'`, // id
174
- rawValue: value, // word
179
+ raw: `'${rawValue}'`,
180
+ rawValue: actualId,
181
+ // 保存原始中文,用于后续需要时使用
182
+ originalValue: value,
175
183
  },
176
184
  });
177
185
  }
@@ -1,8 +1,7 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const prettier = require("prettier");
3
+ const recast = require("recast");
4
4
  const babel = require("@babel/core");
5
- const generate = require("@babel/generator").default;
6
5
  const traverse = require("@babel/traverse").default;
7
6
  const pluginSyntaxJSX = require("@babel/plugin-syntax-jsx");
8
7
  const pluginSyntaxProposalOptionalChaining = require("@babel/plugin-proposal-optional-chaining");
@@ -23,7 +22,9 @@ const log = require("./log");
23
22
  // 获取文件中需要忽略转化通用国际化API规范的所有行号
24
23
  function getIgnoreLines(ast) {
25
24
  const ignoreBlocks = [];
26
- for (const comment of ast.comments) {
25
+
26
+ // 收集注释的辅助函数
27
+ function processComment(comment) {
27
28
  const { type, value, loc } = comment;
28
29
  const last = ignoreBlocks.length - 1;
29
30
 
@@ -50,9 +51,25 @@ function getIgnoreLines(ast) {
50
51
  }
51
52
  }
52
53
 
54
+ // 从 ast.comments 获取注释(Babel 格式)
55
+ if (ast.comments && Array.isArray(ast.comments)) {
56
+ for (const comment of ast.comments) {
57
+ processComment(comment);
58
+ }
59
+ }
60
+
61
+ // 从 ast.tokens 获取注释(recast 格式)
62
+ if (ast.tokens && Array.isArray(ast.tokens)) {
63
+ for (const token of ast.tokens) {
64
+ if (token.type === "CommentLine" || token.type === "CommentBlock") {
65
+ processComment(token);
66
+ }
67
+ }
68
+ }
69
+
53
70
  // 如果缺少 disable-enable,直接作用到最后一行
54
71
  const len = ignoreBlocks.length;
55
- if (len > 0 && !ignoreBlocks[len - 1].end) {
72
+ if (len > 0 && !ignoreBlocks[len - 1].end && ast.loc && ast.loc.end) {
56
73
  ignoreBlocks[len - 1].end = ast.loc.end.line;
57
74
  }
58
75
 
@@ -89,6 +106,7 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
89
106
  sourceType: "module",
90
107
  ast: true,
91
108
  configFile: false,
109
+ parserOpts: { tokens: true },
92
110
  presets: [
93
111
  ...babelPresets,
94
112
  [presetTypescript, { isTSX: true, allExtensions: true }],
@@ -116,14 +134,14 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
116
134
  specialFileReg.some((item) => {
117
135
  if (typeof item === "string") {
118
136
  return item === filePath;
119
- } else {
137
+ } else if (item instanceof RegExp) {
120
138
  return item.test(filePath);
121
139
  }
140
+ return false;
122
141
  })
123
142
  ) {
124
143
  file.special = true;
125
144
  }
126
- const isTSX = [".ts", ".tsx"].includes(path.extname(filePath));
127
145
 
128
146
  const r = {
129
147
  allWords,
@@ -137,8 +155,17 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
137
155
  const sourceCode = fs.readFileSync(filePath, "utf8");
138
156
 
139
157
  let ast;
158
+ let recastAst;
140
159
  try {
141
- ast = babel.parseSync(sourceCode, transformOptions);
160
+ // 使用 recast 解析,内部委托给 babel,保留原始格式信息
161
+ recastAst = recast.parse(sourceCode, {
162
+ parser: {
163
+ parse(source) {
164
+ return babel.parseSync(source, transformOptions);
165
+ },
166
+ },
167
+ });
168
+ ast = recastAst.program;
142
169
  } catch (error) {
143
170
  replaceWords &&
144
171
  log.error(`文件解析出错:${file.filePath}
@@ -146,7 +173,7 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
146
173
  return;
147
174
  }
148
175
 
149
- opts.ignoreLines = getIgnoreLines(ast);
176
+ opts.ignoreLines = getIgnoreLines(recastAst);
150
177
 
151
178
  let makeVisitor = makeVisitorCollect;
152
179
  if (type === "update") {
@@ -155,7 +182,7 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
155
182
 
156
183
  try {
157
184
  const visitor = makeVisitor(opts, r, file);
158
- traverse(ast, visitor);
185
+ traverse(recastAst, visitor);
159
186
  } catch (e) {
160
187
  replaceWords &&
161
188
  log.error(`文件解析出错:${file.filePath}
@@ -167,11 +194,8 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
167
194
  // 在只需要提取中文词条的情况下不传入replaceWords
168
195
  if (!replaceWords) return;
169
196
 
170
- // https://stackoverflow.com/a/55478641
171
- let { code } = generate(ast, {
172
- retainLines: true,
173
- decoratorsBeforeExport: true,
174
- });
197
+ // 使用 recast 输出代码,仅重新打印被修改的 AST 节点,保留原始格式
198
+ let code = recast.print(recastAst).code;
175
199
 
176
200
  if (!r.hasTouch) {
177
201
  code = sourceCode;
@@ -184,13 +208,7 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
184
208
  code = `${importCode}\n${code}`;
185
209
  }
186
210
 
187
- // 自定义格式化代码
188
211
  if (r.hasTouch) {
189
- if (conf.prettier) {
190
- const parser = isTSX ? "typescript" : "babel";
191
- code = prettier.format(code, { ...conf.prettier, parser });
192
- }
193
-
194
212
  const target = file.currentOutput
195
213
  ? filePath.replace(file.currentEntry, file.currentOutput)
196
214
  : filePath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaoswise/intl",
3
- "version": "2.1.10",
3
+ "version": "3.1.0",
4
4
  "author": "cloudwiser",
5
5
  "description": "intl",
6
6
  "main": "lib/index.js",
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "scripts": {
26
26
  "init": "npm i",
27
- "build": "gulp build --gulpfile ./scripts/gulpfile.js"
27
+ "build": "gulp build --gulpfile ./scripts/gulpfile.js",
28
+ "test:nozhcn": "node --test --test-reporter=spec '__tests__/nozhcn/*.test.js'"
28
29
  },
29
30
  "keywords": [
30
31
  "intl"
@@ -77,6 +78,7 @@
77
78
  "object-keys": "^1.0.11",
78
79
  "prettier": "^2.8.1",
79
80
  "react-intl-universal": "^2.6.11",
81
+ "recast": "^0.23.11",
80
82
  "uuid": "^9.0.0",
81
83
  "xlsx": "^0.18.5"
82
84
  },