@npm-questionpro/wick-ui-i18n 0.14.0 → 2.0.0-next.4
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 +62 -61
- package/index.d.ts +1 -0
- package/index.js +24 -24
- package/package.json +23 -6
- package/src/debug.js +25 -26
- package/src/processor.js +73 -70
- package/src/transform.js +104 -95
- package/src/transformJSXTextWithEntities.js +25 -25
- package/src/transformTemplateLiteral.js +22 -22
- package/src/transformWtCalls.js +41 -41
- package/wickuii18n.test.js +0 -873
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 = [
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
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({
|
|
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(
|
|
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
|
-
|
|
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)))
|
|
152
|
-
|
|
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(
|
|
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
|
-
|
|
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 ===
|
|
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
|
|
9
|
-
import {
|
|
10
|
-
import _traverse from
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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 = [
|
|
19
|
+
const BABEL_PLUGINS = ['jsx', 'typescript']
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
22
|
* Matches any HTML entity: named (&), decimal (©), or hex (©).
|
|
@@ -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 & → "&", → "\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 ===
|
|
35
|
-
if (node.type ===
|
|
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(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 "&" 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(
|
|
104
|
-
|
|
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:
|
|
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(
|
|
152
|
+
if (path.node.source.value.includes('wick-ui-lib')) {
|
|
144
153
|
for (const s of path.node.specifiers) {
|
|
145
|
-
if (s.imported?.name ===
|
|
146
|
-
if (s.imported?.name ===
|
|
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 !==
|
|
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,
|
|
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 !==
|
|
179
|
-
const value = path.node.value
|
|
180
|
-
if (!value || value.type !==
|
|
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 (& → "&", © → "©").
|
|
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 ===
|
|
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 ===
|
|
242
|
-
text = expr.value
|
|
250
|
+
if (expr.type === 'StringLiteral') {
|
|
251
|
+
text = expr.value
|
|
243
252
|
} else if (
|
|
244
|
-
expr.type ===
|
|
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 ===
|
|
252
|
-
text = expr.quasis[0].value.cooked
|
|
253
|
-
} else if (expr.type ===
|
|
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 {
|
|
294
|
+
return {code: ms.toString(), map: ms.generateMap({hires: true})}
|
|
286
295
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* <WuButton><<WuTranslate __i18nKey="Tag" />></WuButton>
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
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
|
* "<Tag>" → ["", "<", "Tag", ">", ""]
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
}
|