@npm-questionpro/wick-ui-i18n 2.0.0-next.18 → 2.0.0-next.20
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/README.md +3 -4
- package/index.js +4 -12
- package/package.json +1 -1
- package/src/debug.js +4 -10
- package/src/processor.js +9 -31
- package/src/transform.js +7 -55
- package/src/transformReactTextWithEntities.js +4 -14
- package/src/transformTemplateLiteral.js +2 -10
- package/src/transformWtCalls.js +1 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# wick-ui-i18n
|
|
2
2
|
|
|
3
|
-
Vite plugin — wraps JSX text in Wu components with `<WuTranslate>`, rewrites
|
|
4
|
-
|
|
3
|
+
Vite plugin — wraps JSX text in Wu components with `<WuTranslate>`, rewrites translatable props to `{wt("...")}`, and
|
|
4
|
+
emits `wick-ui-i18n.json`.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -69,8 +69,7 @@ Defaults: `Label`, `placeholder`, `title`, `aria-label`, `aria-placeholder`.
|
|
|
69
69
|
|
|
70
70
|
## `wt()` calls
|
|
71
71
|
|
|
72
|
-
Plugin records static args into `wick-ui-i18n.json`. No code rewrite unless
|
|
73
|
-
template literal with expressions.
|
|
72
|
+
Plugin records static args into `wick-ui-i18n.json`. No code rewrite unless template literal with expressions.
|
|
74
73
|
|
|
75
74
|
| Input | Dictionary | Code output |
|
|
76
75
|
| ----------------------------- | --------------------- | --------------------------------------------- |
|
package/index.js
CHANGED
|
@@ -86,10 +86,8 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
86
86
|
const hasAnyTarget =
|
|
87
87
|
code.includes('Wu') ||
|
|
88
88
|
/\bwt\(/.test(code) ||
|
|
89
|
-
(processor.components.size > 0 &&
|
|
90
|
-
|
|
91
|
-
(processor.extractFromKeys.size > 0 &&
|
|
92
|
-
[...processor.extractFromKeys].some(k => code.includes(k)))
|
|
89
|
+
(processor.components.size > 0 && [...processor.components].some(c => code.includes(c))) ||
|
|
90
|
+
(processor.extractFromKeys.size > 0 && [...processor.extractFromKeys].some(k => code.includes(k)))
|
|
93
91
|
if (!filter(id) || !hasAnyTarget) return null
|
|
94
92
|
return transformFile(code, id, processor)
|
|
95
93
|
},
|
|
@@ -102,9 +100,7 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
102
100
|
configureServer(server) {
|
|
103
101
|
server.middlewares.use(`${base}wick-ui-i18n.json`, (_req, res) => {
|
|
104
102
|
res.setHeader('Content-Type', 'application/json')
|
|
105
|
-
res.end(
|
|
106
|
-
JSON.stringify(Object.fromEntries(processor.dictionary), null, 2),
|
|
107
|
-
)
|
|
103
|
+
res.end(JSON.stringify(Object.fromEntries(processor.dictionary), null, 2))
|
|
108
104
|
})
|
|
109
105
|
},
|
|
110
106
|
|
|
@@ -113,11 +109,7 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
113
109
|
this.emitFile({
|
|
114
110
|
type: 'asset',
|
|
115
111
|
fileName: 'wick-ui-i18n.json',
|
|
116
|
-
source: JSON.stringify(
|
|
117
|
-
Object.fromEntries(processor.dictionary),
|
|
118
|
-
null,
|
|
119
|
-
2,
|
|
120
|
-
),
|
|
112
|
+
source: JSON.stringify(Object.fromEntries(processor.dictionary), null, 2),
|
|
121
113
|
})
|
|
122
114
|
|
|
123
115
|
printReport(processor.entries)
|
package/package.json
CHANGED
package/src/debug.js
CHANGED
|
@@ -16,9 +16,7 @@ export function getComponentTree(path) {
|
|
|
16
16
|
const parts = []
|
|
17
17
|
path.findParent(p => {
|
|
18
18
|
if (p.isJSXElement()) {
|
|
19
|
-
const name =
|
|
20
|
-
p.node.openingElement.name.name ||
|
|
21
|
-
p.node.openingElement.name.property?.name
|
|
19
|
+
const name = p.node.openingElement.name.name || p.node.openingElement.name.property?.name
|
|
22
20
|
if (name) parts.unshift(name)
|
|
23
21
|
}
|
|
24
22
|
return false
|
|
@@ -50,19 +48,15 @@ export function printReport(entries) {
|
|
|
50
48
|
|
|
51
49
|
const rows = entries.map(e => [e.text, basename(e.file), e.componentTree])
|
|
52
50
|
|
|
53
|
-
const widths = headers.map((h, i) =>
|
|
54
|
-
Math.max(h.length, ...rows.map(r => r[i].length)),
|
|
55
|
-
)
|
|
51
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map(r => r[i].length)))
|
|
56
52
|
|
|
57
53
|
const pad = (str, w) => str.padEnd(w)
|
|
58
|
-
const sep = (l, m, r, fill) =>
|
|
59
|
-
l + widths.map(w => fill.repeat(w + 2)).join(m) + r
|
|
54
|
+
const sep = (l, m, r, fill) => l + widths.map(w => fill.repeat(w + 2)).join(m) + r
|
|
60
55
|
|
|
61
56
|
const top = sep('┌', '┬', '┐', '─')
|
|
62
57
|
const mid = sep('├', '┼', '┤', '─')
|
|
63
58
|
const bottom = sep('└', '┴', '┘', '─')
|
|
64
|
-
const row = cells =>
|
|
65
|
-
'│' + cells.map((c, i) => ` ${pad(c, widths[i])} `).join('│') + '│'
|
|
59
|
+
const row = cells => '│' + cells.map((c, i) => ` ${pad(c, widths[i])} `).join('│') + '│'
|
|
66
60
|
|
|
67
61
|
const lines = [
|
|
68
62
|
'',
|
package/src/processor.js
CHANGED
|
@@ -4,13 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/** Prop names translated by default on Wu* components. Pass `translatableProps` to override. */
|
|
7
|
-
const DEFAULT_TRANSLATABLE_PROPS = [
|
|
8
|
-
'Label',
|
|
9
|
-
'placeholder',
|
|
10
|
-
'title',
|
|
11
|
-
'aria-label',
|
|
12
|
-
'aria-placeholder',
|
|
13
|
-
]
|
|
7
|
+
const DEFAULT_TRANSLATABLE_PROPS = ['Label', 'placeholder', 'title', 'aria-label', 'aria-placeholder']
|
|
14
8
|
|
|
15
9
|
/** Components always excluded from translation regardless of user config. */
|
|
16
10
|
const DEFAULT_IGNORE = [
|
|
@@ -39,13 +33,9 @@ export class TranslationProcessor {
|
|
|
39
33
|
*/
|
|
40
34
|
constructor(options) {
|
|
41
35
|
this.components = new Set(options.components)
|
|
42
|
-
this.ignoreComponents = new Set(
|
|
43
|
-
DEFAULT_IGNORE.concat(options.ignoreComponents || []),
|
|
44
|
-
)
|
|
36
|
+
this.ignoreComponents = new Set(DEFAULT_IGNORE.concat(options.ignoreComponents || []))
|
|
45
37
|
/** @type {Set<string>} JSX prop names that should be translated. */
|
|
46
|
-
this.translatableProps = new Set(
|
|
47
|
-
options.translatableProps ?? DEFAULT_TRANSLATABLE_PROPS,
|
|
48
|
-
)
|
|
38
|
+
this.translatableProps = new Set(options.translatableProps ?? DEFAULT_TRANSLATABLE_PROPS)
|
|
49
39
|
/** @type {Set<string>} Object property key names whose string values are extracted (e.g. 'label'). */
|
|
50
40
|
this.extractFromKeys = new Set(options.extractFromKeys || [])
|
|
51
41
|
/** @type {Map<string, string>} key → original text */
|
|
@@ -74,9 +64,7 @@ export class TranslationProcessor {
|
|
|
74
64
|
*/
|
|
75
65
|
record(key, text, file, componentTree) {
|
|
76
66
|
if (this.dictionary.has(key) && this.dictionary.get(key) !== text) {
|
|
77
|
-
console.warn(
|
|
78
|
-
`[wick-i18n] Collision in ${file}\nKey: "${key}"\nNew: "${text}"`,
|
|
79
|
-
)
|
|
67
|
+
console.warn(`[wick-i18n] Collision in ${file}\nKey: "${key}"\nNew: "${text}"`)
|
|
80
68
|
return
|
|
81
69
|
}
|
|
82
70
|
this.dictionary.set(key, text)
|
|
@@ -102,14 +90,10 @@ export class TranslationProcessor {
|
|
|
102
90
|
path.findParent(p => {
|
|
103
91
|
if (!p.isJSXElement()) return false
|
|
104
92
|
|
|
105
|
-
const name =
|
|
106
|
-
p.node.openingElement.name.name ||
|
|
107
|
-
p.node.openingElement.name.property?.name
|
|
93
|
+
const name = p.node.openingElement.name.name || p.node.openingElement.name.property?.name
|
|
108
94
|
const attrs = p.node.openingElement.attributes || []
|
|
109
95
|
|
|
110
|
-
if (
|
|
111
|
-
attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name))
|
|
112
|
-
) {
|
|
96
|
+
if (attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name))) {
|
|
113
97
|
isIgnored = true
|
|
114
98
|
return true
|
|
115
99
|
}
|
|
@@ -150,8 +134,7 @@ export class TranslationProcessor {
|
|
|
150
134
|
if (this.ignoreComponents.has(name)) return false
|
|
151
135
|
// Respect data-skip / data-i18n-skip on the same element
|
|
152
136
|
const attrs = openingEl.attributes || []
|
|
153
|
-
if (attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name)))
|
|
154
|
-
return false
|
|
137
|
+
if (attrs.some(a => ['data-skip', 'data-i18n-skip'].includes(a.name?.name))) return false
|
|
155
138
|
return name.startsWith('Wu') || this.components.has(name)
|
|
156
139
|
}
|
|
157
140
|
|
|
@@ -166,14 +149,9 @@ export class TranslationProcessor {
|
|
|
166
149
|
let result = null
|
|
167
150
|
path.findParent(p => {
|
|
168
151
|
if (!p.isJSXElement()) return false
|
|
169
|
-
const attr = p.node.openingElement.attributes.find(
|
|
170
|
-
a => a.name?.name === 'data-i18n-key',
|
|
171
|
-
)
|
|
152
|
+
const attr = p.node.openingElement.attributes.find(a => a.name?.name === 'data-i18n-key')
|
|
172
153
|
if (!attr) return false
|
|
173
|
-
const val =
|
|
174
|
-
attr.value.type === 'StringLiteral'
|
|
175
|
-
? attr.value.value
|
|
176
|
-
: attr.value.expression?.value
|
|
154
|
+
const val = attr.value.type === 'StringLiteral' ? attr.value.value : attr.value.expression?.value
|
|
177
155
|
if (!val) {
|
|
178
156
|
console.warn(
|
|
179
157
|
`[wick-i18n] data-i18n-key on <${p.node.openingElement.name.name}> is dynamic or empty — falling back to text content.`,
|
package/src/transform.js
CHANGED
|
@@ -29,8 +29,7 @@ const HTML_ENTITY_RE = /&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);/
|
|
|
29
29
|
/** @param {import('@babel/types').Node} node @returns {string|null} */
|
|
30
30
|
function getStaticString(node) {
|
|
31
31
|
if (node.type === 'StringLiteral') return node.value
|
|
32
|
-
if (node.type === 'TemplateLiteral' && !node.expressions.length)
|
|
33
|
-
return node.quasis[0].value.cooked
|
|
32
|
+
if (node.type === 'TemplateLiteral' && !node.expressions.length) return node.quasis[0].value.cooked
|
|
34
33
|
return null
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -51,17 +50,7 @@ function handleConditional(expr, path, ms, processor, id) {
|
|
|
51
50
|
for (const branch of [expr.consequent, expr.alternate]) {
|
|
52
51
|
const text = getStaticString(branch)
|
|
53
52
|
if (text !== null) {
|
|
54
|
-
changed =
|
|
55
|
-
handleCapture(
|
|
56
|
-
path,
|
|
57
|
-
text,
|
|
58
|
-
branch.start,
|
|
59
|
-
branch.end,
|
|
60
|
-
ms,
|
|
61
|
-
processor,
|
|
62
|
-
id,
|
|
63
|
-
true,
|
|
64
|
-
) || changed
|
|
53
|
+
changed = handleCapture(path, text, branch.start, branch.end, ms, processor, id, true) || changed
|
|
65
54
|
} else if (branch.type === 'ConditionalExpression') {
|
|
66
55
|
changed = handleConditional(branch, path, ms, processor, id) || changed
|
|
67
56
|
}
|
|
@@ -83,16 +72,7 @@ function handleConditional(expr, path, ms, processor, id) {
|
|
|
83
72
|
* @param {string} id - File path (for collision warnings).
|
|
84
73
|
* @returns {boolean} `true` when replacement was made.
|
|
85
74
|
*/
|
|
86
|
-
function handleCapture(
|
|
87
|
-
path,
|
|
88
|
-
text,
|
|
89
|
-
start,
|
|
90
|
-
end,
|
|
91
|
-
ms,
|
|
92
|
-
processor,
|
|
93
|
-
id,
|
|
94
|
-
skipExplicitKey = false,
|
|
95
|
-
) {
|
|
75
|
+
function handleCapture(path, text, start, end, ms, processor, id, skipExplicitKey = false) {
|
|
96
76
|
const cleanText = text
|
|
97
77
|
.trim()
|
|
98
78
|
.replace(/\n/g, ' ')
|
|
@@ -105,11 +85,7 @@ function handleCapture(
|
|
|
105
85
|
const key = (!skipExplicitKey && processor.getExplicitKey(path)) || cleanText
|
|
106
86
|
processor.record(key, cleanText, id, getComponentTree(path))
|
|
107
87
|
|
|
108
|
-
ms.overwrite(
|
|
109
|
-
start,
|
|
110
|
-
end,
|
|
111
|
-
`<WuTranslate __i18nKey=${JSON.stringify(key)}></WuTranslate>`,
|
|
112
|
-
)
|
|
88
|
+
ms.overwrite(start, end, `<WuTranslate __i18nKey=${JSON.stringify(key)}></WuTranslate>`)
|
|
113
89
|
return true
|
|
114
90
|
}
|
|
115
91
|
|
|
@@ -219,17 +195,7 @@ export function transformFile(code, id, processor) {
|
|
|
219
195
|
return
|
|
220
196
|
}
|
|
221
197
|
const start = path.node.start + text.indexOf(trimmed)
|
|
222
|
-
if (
|
|
223
|
-
handleCapture(
|
|
224
|
-
path,
|
|
225
|
-
trimmed,
|
|
226
|
-
start,
|
|
227
|
-
start + trimmed.length,
|
|
228
|
-
ms,
|
|
229
|
-
processor,
|
|
230
|
-
id,
|
|
231
|
-
)
|
|
232
|
-
) {
|
|
198
|
+
if (handleCapture(path, trimmed, start, start + trimmed.length, ms, processor, id)) {
|
|
233
199
|
needsImport = true
|
|
234
200
|
}
|
|
235
201
|
},
|
|
@@ -249,10 +215,7 @@ export function transformFile(code, id, processor) {
|
|
|
249
215
|
|
|
250
216
|
if (expr.type === 'StringLiteral') {
|
|
251
217
|
text = expr.value
|
|
252
|
-
} else if (
|
|
253
|
-
expr.type === 'TemplateLiteral' &&
|
|
254
|
-
expr.expressions.length > 0
|
|
255
|
-
) {
|
|
218
|
+
} else if (expr.type === 'TemplateLiteral' && expr.expressions.length > 0) {
|
|
256
219
|
if (transformTemplateLiteralExpression(path, code, ms, processor, id)) {
|
|
257
220
|
needsImport = true
|
|
258
221
|
}
|
|
@@ -264,18 +227,7 @@ export function transformFile(code, id, processor) {
|
|
|
264
227
|
return
|
|
265
228
|
}
|
|
266
229
|
|
|
267
|
-
if (
|
|
268
|
-
text &&
|
|
269
|
-
handleCapture(
|
|
270
|
-
path,
|
|
271
|
-
text,
|
|
272
|
-
path.node.start,
|
|
273
|
-
path.node.end,
|
|
274
|
-
ms,
|
|
275
|
-
processor,
|
|
276
|
-
id,
|
|
277
|
-
)
|
|
278
|
-
) {
|
|
230
|
+
if (text && handleCapture(path, text, path.node.start, path.node.end, ms, processor, id)) {
|
|
279
231
|
needsImport = true
|
|
280
232
|
}
|
|
281
233
|
},
|
|
@@ -26,14 +26,12 @@ import {getComponentTree} from './debug.js'
|
|
|
26
26
|
* "Hello & World" → ["Hello ", "&", " World"]
|
|
27
27
|
* "<Tag>" → ["", "<", "Tag", ">", ""]
|
|
28
28
|
*/
|
|
29
|
-
const HTML_ENTITY_SPLIT_RE =
|
|
30
|
-
/(&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);)/g
|
|
29
|
+
const HTML_ENTITY_SPLIT_RE = /(&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);)/g
|
|
31
30
|
|
|
32
31
|
/**
|
|
33
32
|
* Tests whether a single segment (from the split above) is itself an entity.
|
|
34
33
|
*/
|
|
35
|
-
const HTML_ENTITY_SEGMENT_RE =
|
|
36
|
-
/^&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);$/
|
|
34
|
+
const HTML_ENTITY_SEGMENT_RE = /^&(?:[a-zA-Z][a-zA-Z0-9]*|#[0-9]+|#x[0-9a-fA-F]+);$/
|
|
37
35
|
|
|
38
36
|
/**
|
|
39
37
|
* Given a text segment, extract leading whitespace, the translatable core,
|
|
@@ -73,13 +71,7 @@ function splitSegment(raw) {
|
|
|
73
71
|
* @param {string} id - Source file path (for collision warnings).
|
|
74
72
|
* @returns {boolean} `true` when a replacement was written.
|
|
75
73
|
*/
|
|
76
|
-
export function transformJsxTextWithEntities(
|
|
77
|
-
path,
|
|
78
|
-
rawSource,
|
|
79
|
-
ms,
|
|
80
|
-
processor,
|
|
81
|
-
id,
|
|
82
|
-
) {
|
|
74
|
+
export function transformJsxTextWithEntities(path, rawSource, ms, processor, id) {
|
|
83
75
|
if (!processor.shouldTranslate(path)) return false
|
|
84
76
|
|
|
85
77
|
// data-i18n-key cannot span multiple split segments — warn and ignore it.
|
|
@@ -109,9 +101,7 @@ export function transformJsxTextWithEntities(
|
|
|
109
101
|
|
|
110
102
|
if (text) {
|
|
111
103
|
processor.record(text, text, id, componentTree)
|
|
112
|
-
parts.push(
|
|
113
|
-
`${leading}<WuTranslate __i18nKey=${JSON.stringify(text)}></WuTranslate>${trailing}`,
|
|
114
|
-
)
|
|
104
|
+
parts.push(`${leading}<WuTranslate __i18nKey=${JSON.stringify(text)}></WuTranslate>${trailing}`)
|
|
115
105
|
hasTranslatable = true
|
|
116
106
|
} else {
|
|
117
107
|
// Pure whitespace between / around entities — preserve as JSX text
|
|
@@ -45,13 +45,7 @@ function splitQuasi(raw) {
|
|
|
45
45
|
* @param {string} id - Source file path (for collision warnings).
|
|
46
46
|
* @returns {boolean} `true` when a replacement was written.
|
|
47
47
|
*/
|
|
48
|
-
export function transformTemplateLiteralExpression(
|
|
49
|
-
path,
|
|
50
|
-
code,
|
|
51
|
-
ms,
|
|
52
|
-
processor,
|
|
53
|
-
id,
|
|
54
|
-
) {
|
|
48
|
+
export function transformTemplateLiteralExpression(path, code, ms, processor, id) {
|
|
55
49
|
if (!processor.shouldTranslate(path)) return false
|
|
56
50
|
|
|
57
51
|
const container = path.node
|
|
@@ -68,9 +62,7 @@ export function transformTemplateLiteralExpression(
|
|
|
68
62
|
|
|
69
63
|
if (text) {
|
|
70
64
|
processor.record(text, text, id, componentTree)
|
|
71
|
-
parts.push(
|
|
72
|
-
`${leading}<WuTranslate __i18nKey=${JSON.stringify(text)}></WuTranslate>${trailing}`,
|
|
73
|
-
)
|
|
65
|
+
parts.push(`${leading}<WuTranslate __i18nKey=${JSON.stringify(text)}></WuTranslate>${trailing}`)
|
|
74
66
|
hasTranslatable = true
|
|
75
67
|
}
|
|
76
68
|
// whitespace-only or empty quasi — emit nothing (no key, no node)
|
package/src/transformWtCalls.js
CHANGED
|
@@ -101,8 +101,7 @@ export function transformWtTemplateLiteral(path, code, ms, processor, id) {
|
|
|
101
101
|
if (args.length !== 1) return false
|
|
102
102
|
|
|
103
103
|
const arg = args[0]
|
|
104
|
-
if (arg.type !== 'TemplateLiteral' || arg.expressions.length === 0)
|
|
105
|
-
return false
|
|
104
|
+
if (arg.type !== 'TemplateLiteral' || arg.expressions.length === 0) return false
|
|
106
105
|
|
|
107
106
|
const {quasis, expressions} = arg
|
|
108
107
|
const parts = ['`']
|