@npm-questionpro/wick-ui-i18n 0.14.1 → 2.0.0-next.5

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/src/processor.js CHANGED
@@ -4,23 +4,29 @@
4
4
  */
5
5
 
6
6
  /** Prop names translated by default on Wu* components. Pass `translatableProps` to override. */
7
- const DEFAULT_TRANSLATABLE_PROPS = ['Label', 'placeholder', 'title', 'aria-label', 'aria-placeholder']
7
+ const DEFAULT_TRANSLATABLE_PROPS = [
8
+ 'Label',
9
+ 'placeholder',
10
+ 'title',
11
+ 'aria-label',
12
+ 'aria-placeholder',
13
+ ]
8
14
 
9
15
  /** Components always excluded from translation regardless of user config. */
10
16
  const DEFAULT_IGNORE = [
11
- "WuIcon",
12
- "WuTranslateProvider",
13
- "WuHelpButton",
14
- "WuActivityLog",
15
- "WuAppHeader",
16
- "WuAPpHeadeMenu",
17
- "WuCopyToClipboard",
18
- "WuMenuIcon",
19
- "WuScrollArea",
20
- "WuDrawer",
21
- "WuLoader",
22
- "WuContentEditor",
23
- ];
17
+ 'WuIcon',
18
+ 'WuTranslateProvider',
19
+ 'WuHelpButton',
20
+ 'WuActivityLog',
21
+ 'WuAppHeader',
22
+ 'WuAPpHeadeMenu',
23
+ 'WuCopyToClipboard',
24
+ 'WuMenuIcon',
25
+ 'WuScrollArea',
26
+ 'WuDrawer',
27
+ 'WuLoader',
28
+ 'WuContentEditor',
29
+ ]
24
30
 
25
31
  export class TranslationProcessor {
26
32
  /**
@@ -32,21 +38,21 @@ export class TranslationProcessor {
32
38
  * @param {boolean} [options.debug] - Enable verbose logging.
33
39
  */
34
40
  constructor(options) {
35
- this.components = new Set(options.components);
41
+ this.components = new Set(options.components)
36
42
  this.ignoreComponents = new Set(
37
43
  DEFAULT_IGNORE.concat(options.ignoreComponents || []),
38
- );
44
+ )
39
45
  /** @type {Set<string>} JSX prop names that should be translated. */
40
46
  this.translatableProps = new Set(
41
47
  options.translatableProps ?? DEFAULT_TRANSLATABLE_PROPS,
42
- );
48
+ )
43
49
  /** @type {Set<string>} Object property key names whose string values are extracted (e.g. 'label'). */
44
- this.extractFromKeys = new Set(options.extractFromKeys || []);
50
+ this.extractFromKeys = new Set(options.extractFromKeys || [])
45
51
  /** @type {Map<string, string>} key → original text */
46
- this.dictionary = new Map();
52
+ this.dictionary = new Map()
47
53
  /** @type {import('./debug.js').DebugEntry[]} */
48
- this.entries = [];
49
- this.debugEnabled = options.debug || false;
54
+ this.entries = []
55
+ this.debugEnabled = options.debug || false
50
56
  }
51
57
 
52
58
  /**
@@ -54,7 +60,7 @@ export class TranslationProcessor {
54
60
  * @param {...unknown} args
55
61
  */
56
62
  log(...args) {
57
- if (this.debugEnabled) console.log("[wick-i18n]", ...args);
63
+ if (this.debugEnabled) console.log('[wick-i18n]', ...args)
58
64
  }
59
65
 
60
66
  /**
@@ -70,11 +76,11 @@ export class TranslationProcessor {
70
76
  if (this.dictionary.has(key) && this.dictionary.get(key) !== text) {
71
77
  console.warn(
72
78
  `[wick-i18n] Collision in ${file}\nKey: "${key}"\nNew: "${text}"`,
73
- );
74
- return;
79
+ )
80
+ return
75
81
  }
76
- this.dictionary.set(key, text);
77
- this.entries.push({ key, text, file, componentTree });
82
+ this.dictionary.set(key, text)
83
+ this.entries.push({key, text, file, componentTree})
78
84
  }
79
85
 
80
86
  /**
@@ -90,45 +96,41 @@ export class TranslationProcessor {
90
96
  * @returns {boolean}
91
97
  */
92
98
  shouldTranslate(path) {
93
- let isIgnored = false;
94
- let targetFound = false;
99
+ let isIgnored = false
100
+ let targetFound = false
95
101
 
96
- path.findParent((p) => {
97
- if (!p.isJSXElement()) return false;
102
+ path.findParent(p => {
103
+ if (!p.isJSXElement()) return false
98
104
 
99
105
  const name =
100
106
  p.node.openingElement.name.name ||
101
- p.node.openingElement.name.property?.name;
102
- const attrs = p.node.openingElement.attributes || [];
107
+ p.node.openingElement.name.property?.name
108
+ const attrs = p.node.openingElement.attributes || []
103
109
 
104
110
  if (
105
- attrs.some((a) =>
106
- ["data-skip", "data-i18n-skip"].includes(a.name?.name),
107
- )
111
+ attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name))
108
112
  ) {
109
- isIgnored = true;
110
- return true;
113
+ isIgnored = true
114
+ return true
111
115
  }
112
116
 
113
117
  if (this.ignoreComponents.has(name)) {
114
- isIgnored = true;
115
- return true;
118
+ isIgnored = true
119
+ return true
116
120
  }
117
121
 
118
- const hasWrapper = attrs.some(
119
- (a) => a.name?.name === "data-i18n-wrapper",
120
- );
121
- const isTarget = this.components.has(name) || name?.startsWith("Wu");
122
+ const hasWrapper = attrs.some(a => a.name?.name === 'data-i18n-wrapper')
123
+ const isTarget = this.components.has(name) || name?.startsWith('Wu')
122
124
 
123
125
  if (hasWrapper || isTarget) {
124
- targetFound = true;
125
- return true;
126
+ targetFound = true
127
+ return true
126
128
  }
127
129
 
128
- return false;
129
- });
130
+ return false
131
+ })
130
132
 
131
- return targetFound && !isIgnored;
133
+ return targetFound && !isIgnored
132
134
  }
133
135
 
134
136
  /**
@@ -140,16 +142,17 @@ export class TranslationProcessor {
140
142
  * @returns {boolean}
141
143
  */
142
144
  shouldTranslateProp(propName, path) {
143
- if (!this.translatableProps.has(propName)) return false;
145
+ if (!this.translatableProps.has(propName)) return false
144
146
  // path.parent is the JSXOpeningElement node
145
- const openingEl = path.parent;
146
- const name = openingEl.name?.name || openingEl.name?.property?.name;
147
- if (!name) return false;
148
- if (this.ignoreComponents.has(name)) return false;
147
+ const openingEl = path.parent
148
+ const name = openingEl.name?.name || openingEl.name?.property?.name
149
+ if (!name) return false
150
+ if (this.ignoreComponents.has(name)) return false
149
151
  // Respect data-skip / data-i18n-skip on the same element
150
- const attrs = openingEl.attributes || [];
151
- if (attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name))) return false;
152
- return name.startsWith('Wu') || this.components.has(name);
152
+ const attrs = openingEl.attributes || []
153
+ if (attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name)))
154
+ return false
155
+ return name.startsWith('Wu') || this.components.has(name)
153
156
  }
154
157
 
155
158
  /**
@@ -160,26 +163,26 @@ export class TranslationProcessor {
160
163
  * @returns {string|null}
161
164
  */
162
165
  getExplicitKey(path) {
163
- let result = null;
164
- path.findParent((p) => {
165
- if (!p.isJSXElement()) return false;
166
+ let result = null
167
+ path.findParent(p => {
168
+ if (!p.isJSXElement()) return false
166
169
  const attr = p.node.openingElement.attributes.find(
167
- (a) => a.name?.name === "data-i18n-key",
168
- );
169
- if (!attr) return false;
170
+ a => a.name?.name === 'data-i18n-key',
171
+ )
172
+ if (!attr) return false
170
173
  const val =
171
- attr.value.type === "StringLiteral"
174
+ attr.value.type === 'StringLiteral'
172
175
  ? attr.value.value
173
- : attr.value.expression?.value;
176
+ : attr.value.expression?.value
174
177
  if (!val) {
175
178
  console.warn(
176
179
  `[wick-i18n] data-i18n-key on <${p.node.openingElement.name.name}> is dynamic or empty — falling back to text content.`,
177
- );
178
- return true;
180
+ )
181
+ return true
179
182
  }
180
- result = val;
181
- return true;
182
- });
183
- return result;
183
+ result = val
184
+ return true
185
+ })
186
+ return result
184
187
  }
185
188
  }
package/src/transform.js CHANGED
@@ -5,21 +5,18 @@
5
5
  * - Prepends the necessary imports when added.
6
6
  */
7
7
 
8
- import MagicString from "magic-string";
9
- import { parse } from "@babel/parser";
10
- import _traverse from "@babel/traverse";
11
- import { getComponentTree } from "./debug.js";
12
- import { transformTemplateLiteralExpression } from "./transformTemplateLiteral.js";
13
- import { transformJSXTextWithEntities } from "./transformJSXTextWithEntities.js";
14
- import {
15
- recordWtCall,
16
- transformWtTemplateLiteral,
17
- } from "./transformWtCalls.js";
8
+ import MagicString from 'magic-string'
9
+ import {parse} from '@babel/parser'
10
+ import _traverse from '@babel/traverse'
11
+ import {getComponentTree} from './debug.js'
12
+ import {transformTemplateLiteralExpression} from './transformTemplateLiteral.js'
13
+ import {transformJSXTextWithEntities} from './transformJSXTextWithEntities.js'
14
+ import {recordWtCall, transformWtTemplateLiteral} from './transformWtCalls.js'
18
15
 
19
- const traverse = _traverse.default || _traverse;
16
+ const traverse = _traverse.default || _traverse
20
17
 
21
18
  /** Babel parser plugins applied to every file. */
22
- const BABEL_PLUGINS = ["jsx", "typescript"];
19
+ const BABEL_PLUGINS = ['jsx', 'typescript']
23
20
 
24
21
  /**
25
22
  * Matches any HTML entity: named (&amp;), decimal (&#169;), or hex (&#x00A9;).
@@ -27,14 +24,14 @@ const BABEL_PLUGINS = ["jsx", "typescript"];
27
24
  * Note: for JSXText this must be tested against the RAW source, not the Babel
28
25
  * decoded .value (Babel turns &amp; → "&", &nbsp; → "\u00a0", etc.).
29
26
  */
30
- const HTML_ENTITY_RE = /&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);/;
27
+ const HTML_ENTITY_RE = /&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);/
31
28
 
32
29
  /** @param {import('@babel/types').Node} node @returns {string|null} */
33
30
  function getStaticString(node) {
34
- if (node.type === "StringLiteral") return node.value;
35
- if (node.type === "TemplateLiteral" && !node.expressions.length)
36
- return node.quasis[0].value.cooked;
37
- return null;
31
+ if (node.type === 'StringLiteral') return node.value
32
+ if (node.type === 'TemplateLiteral' && !node.expressions.length)
33
+ return node.quasis[0].value.cooked
34
+ return null
38
35
  }
39
36
 
40
37
  /**
@@ -49,20 +46,28 @@ function getStaticString(node) {
49
46
  * @returns {boolean} true when at least one branch was captured
50
47
  */
51
48
  function handleConditional(expr, path, ms, processor, id) {
52
- let changed = false;
49
+ let changed = false
53
50
 
54
51
  for (const branch of [expr.consequent, expr.alternate]) {
55
- const text = getStaticString(branch);
52
+ const text = getStaticString(branch)
56
53
  if (text !== null) {
57
54
  changed =
58
- handleCapture(path, text, branch.start, branch.end, ms, processor, id, true) ||
59
- changed;
60
- } else if (branch.type === "ConditionalExpression") {
61
- changed = handleConditional(branch, path, ms, processor, id) || changed;
55
+ handleCapture(
56
+ path,
57
+ text,
58
+ branch.start,
59
+ branch.end,
60
+ ms,
61
+ processor,
62
+ id,
63
+ true,
64
+ ) || changed
65
+ } else if (branch.type === 'ConditionalExpression') {
66
+ changed = handleConditional(branch, path, ms, processor, id) || changed
62
67
  }
63
68
  }
64
69
 
65
- return changed;
70
+ return changed
66
71
  }
67
72
 
68
73
  /**
@@ -90,18 +95,22 @@ function handleCapture(
90
95
  ) {
91
96
  const cleanText = text
92
97
  .trim()
93
- .replace(/\n/g, " ")
94
- .replace(/\s{2,}/g, " ");
95
- if (!cleanText || !processor.shouldTranslate(path)) return false;
98
+ .replace(/\n/g, ' ')
99
+ .replace(/\s{2,}/g, ' ')
100
+ if (!cleanText || !processor.shouldTranslate(path)) return false
96
101
  // For StringLiteral / TemplateLiteral quasis / ternary branches: entities are
97
102
  // not decoded by the JS parser, so cleanText still contains "&amp;" etc.
98
- if (HTML_ENTITY_RE.test(cleanText)) return false;
103
+ if (HTML_ENTITY_RE.test(cleanText)) return false
99
104
 
100
- const key = (!skipExplicitKey && processor.getExplicitKey(path)) || cleanText;
101
- processor.record(key, cleanText, id, getComponentTree(path));
105
+ const key = (!skipExplicitKey && processor.getExplicitKey(path)) || cleanText
106
+ processor.record(key, cleanText, id, getComponentTree(path))
102
107
 
103
- ms.overwrite(start, end, `<WuTranslate __i18nKey=${JSON.stringify(key)}></WuTranslate>`);
104
- return true;
108
+ ms.overwrite(
109
+ start,
110
+ end,
111
+ `<WuTranslate __i18nKey=${JSON.stringify(key)}></WuTranslate>`,
112
+ )
113
+ return true
105
114
  }
106
115
 
107
116
  /**
@@ -116,34 +125,34 @@ function handleCapture(
116
125
  */
117
126
  export function transformFile(code, id, processor) {
118
127
  const ast = parse(code, {
119
- sourceType: "module",
128
+ sourceType: 'module',
120
129
  plugins: BABEL_PLUGINS,
121
- });
130
+ })
122
131
 
123
- const ms = new MagicString(code);
124
- let needsImport = false;
125
- let hasImport = false;
126
- let hasWtTransform = false;
127
- let needsWtImport = false;
128
- let hasWtImport = false;
132
+ const ms = new MagicString(code)
133
+ let needsImport = false
134
+ let hasImport = false
135
+ let hasWtTransform = false
136
+ let needsWtImport = false
137
+ let hasWtImport = false
129
138
 
130
139
  traverse(ast, {
131
140
  /** wt("static string") call — record the key in the dictionary.
132
141
  * No code transformation; wt() handles runtime lookup.
133
142
  */
134
143
  CallExpression(path) {
135
- if (recordWtCall(path, processor, id)) return;
144
+ if (recordWtCall(path, processor, id)) return
136
145
  if (transformWtTemplateLiteral(path, code, ms, processor, id)) {
137
- hasWtTransform = true;
146
+ hasWtTransform = true
138
147
  }
139
148
  },
140
149
 
141
150
  /** Track whether WuTranslate / wt are already imported so we don't duplicate them. */
142
151
  ImportDeclaration(path) {
143
- if (path.node.source.value.includes("wick-ui-lib")) {
152
+ if (path.node.source.value.includes('wick-ui-lib')) {
144
153
  for (const s of path.node.specifiers) {
145
- if (s.imported?.name === "WuTranslate") hasImport = true;
146
- if (s.imported?.name === "wt") hasWtImport = true;
154
+ if (s.imported?.name === 'WuTranslate') hasImport = true
155
+ if (s.imported?.name === 'wt') hasWtImport = true
147
156
  }
148
157
  }
149
158
  },
@@ -154,19 +163,19 @@ export function transformFile(code, id, processor) {
154
163
  * Enabled via `extractFromKeys` option.
155
164
  */
156
165
  ObjectProperty(path) {
157
- if (!processor.extractFromKeys.size) return;
158
- const keyNode = path.node.key;
159
- const keyName = keyNode.name ?? keyNode.value;
160
- if (!keyName || !processor.extractFromKeys.has(keyName)) return;
161
- const value = path.node.value;
162
- if (value.type !== "StringLiteral") return;
166
+ if (!processor.extractFromKeys.size) return
167
+ const keyNode = path.node.key
168
+ const keyName = keyNode.name ?? keyNode.value
169
+ if (!keyName || !processor.extractFromKeys.has(keyName)) return
170
+ const value = path.node.value
171
+ if (value.type !== 'StringLiteral') return
163
172
  const text = value.value
164
173
  .trim()
165
- .replace(/\n/g, " ")
166
- .replace(/\s{2,}/g, " ");
167
- if (!text) return;
168
- if (HTML_ENTITY_RE.test(text)) return;
169
- processor.record(text, text, id, "(data)");
174
+ .replace(/\n/g, ' ')
175
+ .replace(/\s{2,}/g, ' ')
176
+ if (!text) return
177
+ if (HTML_ENTITY_RE.test(text)) return
178
+ processor.record(text, text, id, '(data)')
170
179
  },
171
180
 
172
181
  /**
@@ -174,42 +183,42 @@ export function transformFile(code, id, processor) {
174
183
  * rewritten to `{wt("foo")}` on Wu* components.
175
184
  */
176
185
  JSXAttribute(path) {
177
- const propName = path.node.name.name;
178
- if (typeof propName !== "string") return;
179
- const value = path.node.value;
180
- if (!value || value.type !== "StringLiteral") return;
186
+ const propName = path.node.name.name
187
+ if (typeof propName !== 'string') return
188
+ const value = path.node.value
189
+ if (!value || value.type !== 'StringLiteral') return
181
190
 
182
- const rawValue = code.slice(value.start, value.end);
183
- if (HTML_ENTITY_RE.test(rawValue)) return;
191
+ const rawValue = code.slice(value.start, value.end)
192
+ if (HTML_ENTITY_RE.test(rawValue)) return
184
193
 
185
194
  const text = value.value
186
195
  .trim()
187
- .replace(/\n/g, " ")
188
- .replace(/\s{2,}/g, " ");
189
- if (!text) return;
196
+ .replace(/\n/g, ' ')
197
+ .replace(/\s{2,}/g, ' ')
198
+ if (!text) return
190
199
 
191
- if (!processor.shouldTranslateProp(propName, path)) return;
200
+ if (!processor.shouldTranslateProp(propName, path)) return
192
201
 
193
- processor.record(text, text, id, getComponentTree(path));
194
- ms.overwrite(value.start, value.end, `{wt(${JSON.stringify(text)})}`);
195
- needsWtImport = true;
202
+ processor.record(text, text, id, getComponentTree(path))
203
+ ms.overwrite(value.start, value.end, `{wt(${JSON.stringify(text)})}`)
204
+ needsWtImport = true
196
205
  },
197
206
 
198
207
  /** Plain JSX text: `<Foo>Hello world</Foo>` */
199
208
  JSXText(path) {
200
- const text = path.node.value;
201
- const trimmed = text.trim();
209
+ const text = path.node.value
210
+ const trimmed = text.trim()
202
211
  // Babel decodes entities in JSXText.value (&amp; → "&", &#169; → "©").
203
212
  // Check the raw source slice — if entities found, split around them so
204
213
  // translatable text segments are still wrapped while entities stay put.
205
- const rawSource = code.slice(path.node.start, path.node.end);
214
+ const rawSource = code.slice(path.node.start, path.node.end)
206
215
  if (HTML_ENTITY_RE.test(rawSource)) {
207
216
  if (transformJSXTextWithEntities(path, rawSource, ms, processor, id)) {
208
- needsImport = true;
217
+ needsImport = true
209
218
  }
210
- return;
219
+ return
211
220
  }
212
- const start = path.node.start + text.indexOf(trimmed);
221
+ const start = path.node.start + text.indexOf(trimmed)
213
222
  if (
214
223
  handleCapture(
215
224
  path,
@@ -221,7 +230,7 @@ export function transformFile(code, id, processor) {
221
230
  id,
222
231
  )
223
232
  ) {
224
- needsImport = true;
233
+ needsImport = true
225
234
  }
226
235
  },
227
236
 
@@ -233,26 +242,26 @@ export function transformFile(code, id, processor) {
233
242
  * a JSXAttribute, not a JSXElement child position.
234
243
  */
235
244
  JSXExpressionContainer(path) {
236
- if (path.parent.type === "JSXAttribute") return;
245
+ if (path.parent.type === 'JSXAttribute') return
237
246
 
238
- const expr = path.node.expression;
239
- let text = null;
247
+ const expr = path.node.expression
248
+ let text = null
240
249
 
241
- if (expr.type === "StringLiteral") {
242
- text = expr.value;
250
+ if (expr.type === 'StringLiteral') {
251
+ text = expr.value
243
252
  } else if (
244
- expr.type === "TemplateLiteral" &&
253
+ expr.type === 'TemplateLiteral' &&
245
254
  expr.expressions.length > 0
246
255
  ) {
247
256
  if (transformTemplateLiteralExpression(path, code, ms, processor, id)) {
248
- needsImport = true;
257
+ needsImport = true
249
258
  }
250
- return;
251
- } else if (expr.type === "TemplateLiteral" && !expr.expressions.length) {
252
- text = expr.quasis[0].value.cooked;
253
- } else if (expr.type === "ConditionalExpression") {
254
- if (handleConditional(expr, path, ms, processor, id)) needsImport = true;
255
- return;
259
+ return
260
+ } else if (expr.type === 'TemplateLiteral' && !expr.expressions.length) {
261
+ text = expr.quasis[0].value.cooked
262
+ } else if (expr.type === 'ConditionalExpression') {
263
+ if (handleConditional(expr, path, ms, processor, id)) needsImport = true
264
+ return
256
265
  }
257
266
 
258
267
  if (
@@ -267,20 +276,20 @@ export function transformFile(code, id, processor) {
267
276
  id,
268
277
  )
269
278
  ) {
270
- needsImport = true;
279
+ needsImport = true
271
280
  }
272
281
  },
273
- });
282
+ })
274
283
 
275
- if (!needsImport && !hasWtTransform && !needsWtImport) return null;
284
+ if (!needsImport && !hasWtTransform && !needsWtImport) return null
276
285
 
277
286
  if (needsWtImport && !hasWtImport) {
278
- ms.prepend(`import { wt } from '@npm-questionpro/wick-ui-lib';\n`);
287
+ ms.prepend(`import { wt } from '@npm-questionpro/wick-ui-lib';\n`)
279
288
  }
280
289
 
281
290
  if (needsImport && !hasImport) {
282
- ms.prepend(`import { WuTranslate } from '@npm-questionpro/wick-ui-lib';\n`);
291
+ ms.prepend(`import { WuTranslate } from '@npm-questionpro/wick-ui-lib';\n`)
283
292
  }
284
293
 
285
- return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
294
+ return {code: ms.toString(), map: ms.generateMap({hires: true})}
286
295
  }
@@ -17,7 +17,7 @@
17
17
  * <WuButton>&lt;<WuTranslate __i18nKey="Tag" />&gt;</WuButton>
18
18
  */
19
19
 
20
- import { getComponentTree } from "./debug.js";
20
+ import {getComponentTree} from './debug.js'
21
21
 
22
22
  /**
23
23
  * Splits the raw source around HTML entities (capturing group keeps entities
@@ -27,13 +27,13 @@ import { getComponentTree } from "./debug.js";
27
27
  * "&lt;Tag&gt;" → ["", "&lt;", "Tag", "&gt;", ""]
28
28
  */
29
29
  const HTML_ENTITY_SPLIT_RE =
30
- /(&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);)/g;
30
+ /(&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);)/g
31
31
 
32
32
  /**
33
33
  * Tests whether a single segment (from the split above) is itself an entity.
34
34
  */
35
35
  const HTML_ENTITY_SEGMENT_RE =
36
- /^&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);$/;
36
+ /^&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);$/
37
37
 
38
38
  /**
39
39
  * Given a text segment, extract leading whitespace, the translatable core,
@@ -43,15 +43,15 @@ const HTML_ENTITY_SEGMENT_RE =
43
43
  * @returns {{ leading: string, text: string, trailing: string }}
44
44
  */
45
45
  function splitSegment(raw) {
46
- const leading = raw.match(/^\s*/)[0];
47
- const trailing = raw.match(/\s*$/)[0];
46
+ const leading = raw.match(/^\s*/)[0]
47
+ const trailing = raw.match(/\s*$/)[0]
48
48
  // Normalise internal whitespace to match handleCapture behaviour:
49
49
  // newlines → single space, consecutive spaces → single space.
50
50
  const text = raw
51
51
  .slice(leading.length, raw.length - trailing.length)
52
- .replace(/\n/g, " ")
53
- .replace(/\s{2,}/g, " ");
54
- return { leading, text, trailing };
52
+ .replace(/\n/g, ' ')
53
+ .replace(/\s{2,}/g, ' ')
54
+ return {leading, text, trailing}
55
55
  }
56
56
 
57
57
  /**
@@ -80,48 +80,48 @@ export function transformJSXTextWithEntities(
80
80
  processor,
81
81
  id,
82
82
  ) {
83
- if (!processor.shouldTranslate(path)) return false;
83
+ if (!processor.shouldTranslate(path)) return false
84
84
 
85
85
  // data-i18n-key cannot span multiple split segments — warn and ignore it.
86
- const explicitKey = processor.getExplicitKey(path);
86
+ const explicitKey = processor.getExplicitKey(path)
87
87
  if (explicitKey) {
88
88
  console.warn(
89
89
  `[wick-i18n] data-i18n-key="${explicitKey}" is set on a node whose text ` +
90
90
  `contains HTML entities. The key cannot apply across multiple segments ` +
91
91
  `— splitting without it.`,
92
- );
92
+ )
93
93
  }
94
94
 
95
- const segments = rawSource.split(HTML_ENTITY_SPLIT_RE);
96
- const componentTree = getComponentTree(path);
95
+ const segments = rawSource.split(HTML_ENTITY_SPLIT_RE)
96
+ const componentTree = getComponentTree(path)
97
97
 
98
- const parts = [];
99
- let hasTranslatable = false;
98
+ const parts = []
99
+ let hasTranslatable = false
100
100
 
101
101
  for (const seg of segments) {
102
- if (!seg) continue; // empty strings produced by split at boundaries
102
+ if (!seg) continue // empty strings produced by split at boundaries
103
103
 
104
104
  if (HTML_ENTITY_SEGMENT_RE.test(seg)) {
105
105
  // Raw entity — emit as-is, no translation key
106
- parts.push(seg);
106
+ parts.push(seg)
107
107
  } else {
108
- const { leading, text, trailing } = splitSegment(seg);
108
+ const {leading, text, trailing} = splitSegment(seg)
109
109
 
110
110
  if (text) {
111
- processor.record(text, text, id, componentTree);
111
+ processor.record(text, text, id, componentTree)
112
112
  parts.push(
113
113
  `${leading}<WuTranslate __i18nKey=${JSON.stringify(text)}></WuTranslate>${trailing}`,
114
- );
115
- hasTranslatable = true;
114
+ )
115
+ hasTranslatable = true
116
116
  } else {
117
117
  // Pure whitespace between / around entities — preserve as JSX text
118
- parts.push(seg);
118
+ parts.push(seg)
119
119
  }
120
120
  }
121
121
  }
122
122
 
123
- if (!hasTranslatable) return false;
123
+ if (!hasTranslatable) return false
124
124
 
125
- ms.overwrite(path.node.start, path.node.end, parts.join(""));
126
- return true;
125
+ ms.overwrite(path.node.start, path.node.end, parts.join(''))
126
+ return true
127
127
  }