@npm-questionpro/wick-ui-i18n 0.14.1 → 2.0.0-next.10
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 +22 -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/README.md
CHANGED
|
@@ -7,98 +7,99 @@ translatable props to `{wt("...")}`, and emits `wick-ui-i18n.json`.
|
|
|
7
7
|
|
|
8
8
|
## JSX text
|
|
9
9
|
|
|
10
|
-
| Input
|
|
11
|
-
|
|
12
|
-
| `<WuButton>Hello</WuButton>`
|
|
13
|
-
| `<WuIcon>star</WuIcon>`
|
|
14
|
-
| `<div>Hello</div>`
|
|
15
|
-
| `<WuButton data-skip>Hello</WuButton>`
|
|
16
|
-
| `<span data-i18n-wrapper>Hello</span>`
|
|
17
|
-
| `<WuButton data-i18n-key="k">Hello</WuButton>` | ✅ `<WuTranslate __i18nKey="k" />`
|
|
18
|
-
| `<WuButton>&</WuButton>`
|
|
19
|
-
| `<WuButton>Hello & World</WuButton>`
|
|
10
|
+
| Input | Output |
|
|
11
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
12
|
+
| `<WuButton>Hello</WuButton>` | ✅ `<WuTranslate __i18nKey="Hello" />` |
|
|
13
|
+
| `<WuIcon>star</WuIcon>` | ❌ |
|
|
14
|
+
| `<div>Hello</div>` | ❌ |
|
|
15
|
+
| `<WuButton data-skip>Hello</WuButton>` | ❌ |
|
|
16
|
+
| `<span data-i18n-wrapper>Hello</span>` | ✅ `<WuTranslate __i18nKey="Hello" />` |
|
|
17
|
+
| `<WuButton data-i18n-key="k">Hello</WuButton>` | ✅ `<WuTranslate __i18nKey="k" />` |
|
|
18
|
+
| `<WuButton>&</WuButton>` | ❌ |
|
|
19
|
+
| `<WuButton>Hello & World</WuButton>` | ✅ `<WuTranslate __i18nKey="Hello" /> & <WuTranslate __i18nKey="World" />` |
|
|
20
20
|
|
|
21
21
|
## JSX string expressions
|
|
22
22
|
|
|
23
|
-
| Input
|
|
24
|
-
|
|
25
|
-
| `<WuButton>{"Hello"}</WuButton>`
|
|
26
|
-
|
|
|
27
|
-
| `<WuButton>{variable}</WuButton>`
|
|
23
|
+
| Input | Output |
|
|
24
|
+
| ---------------------------------- | -------------------------------------- |
|
|
25
|
+
| `<WuButton>{"Hello"}</WuButton>` | ✅ `<WuTranslate __i18nKey="Hello" />` |
|
|
26
|
+
| ``<WuButton>{`Hello`}</WuButton>`` | ✅ `<WuTranslate __i18nKey="Hello" />` |
|
|
27
|
+
| `<WuButton>{variable}</WuButton>` | ❌ |
|
|
28
28
|
|
|
29
29
|
## JSX ternaries
|
|
30
30
|
|
|
31
|
-
| Input
|
|
32
|
-
|
|
33
|
-
| `<WuButton>{flag ? "Yes" : "No"}</WuButton>`
|
|
34
|
-
| `<WuButton>{flag ? "Yes" : variable}</WuButton>`
|
|
35
|
-
| `<WuButton>{flag ? variable : variable}</WuButton>` | ❌
|
|
36
|
-
| `<WuButton>{a ? "A" : b ? "B" : "C"}</WuButton>`
|
|
31
|
+
| Input | Output |
|
|
32
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
33
|
+
| `<WuButton>{flag ? "Yes" : "No"}</WuButton>` | ✅ `{flag ? <WuTranslate __i18nKey="Yes" /> : <WuTranslate __i18nKey="No" />}` |
|
|
34
|
+
| `<WuButton>{flag ? "Yes" : variable}</WuButton>` | ✅ `{flag ? <WuTranslate __i18nKey="Yes" /> : variable}` |
|
|
35
|
+
| `<WuButton>{flag ? variable : variable}</WuButton>` | ❌ |
|
|
36
|
+
| `<WuButton>{a ? "A" : b ? "B" : "C"}</WuButton>` | ✅ `{a ? <WuTranslate __i18nKey="A" /> : b ? <WuTranslate __i18nKey="B" /> : <WuTranslate __i18nKey="C" />}` |
|
|
37
37
|
|
|
38
38
|
## JSX template literals with expressions
|
|
39
39
|
|
|
40
|
-
| Input
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
40
|
+
| Input | Output |
|
|
41
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
|
42
|
+
| ``<WuButton>{`Hello ${name}`}</WuButton>`` | ✅ `<><WuTranslate __i18nKey="Hello" /> {name}</>` |
|
|
43
|
+
| ``<WuButton>{`${name} world`}</WuButton>`` | ✅ `<>{name} <WuTranslate __i18nKey="world" /></>` |
|
|
44
|
+
| ``<WuButton>{`Hello ${a} and ${b}`}</WuButton>`` | ✅ `<><WuTranslate __i18nKey="Hello" /> {a} <WuTranslate __i18nKey="and" /> {b}</>` |
|
|
45
|
+
| ``<WuButton>{`${a}${b}`}</WuButton>`` | ❌ |
|
|
46
46
|
|
|
47
47
|
## JSX mixed children
|
|
48
48
|
|
|
49
|
-
| Input
|
|
50
|
-
|
|
49
|
+
| Input | Output |
|
|
50
|
+
| ----------------------------------- | --------------------------------------------- |
|
|
51
51
|
| `<WuButton>Hello {name}</WuButton>` | ✅ `<WuTranslate __i18nKey="Hello" /> {name}` |
|
|
52
|
-
| `<WuButton>{a} and {b}</WuButton>`
|
|
53
|
-
| `<WuButton>{a} {b}</WuButton>`
|
|
52
|
+
| `<WuButton>{a} and {b}</WuButton>` | ✅ `{a} <WuTranslate __i18nKey="and" /> {b}` |
|
|
53
|
+
| `<WuButton>{a} {b}</WuButton>` | ❌ |
|
|
54
54
|
|
|
55
55
|
## JSX props
|
|
56
56
|
|
|
57
57
|
Defaults: `Label`, `placeholder`, `title`, `aria-label`, `aria-placeholder`.
|
|
58
58
|
|
|
59
|
-
| Input
|
|
60
|
-
|
|
61
|
-
| `<WuField Label="First name" />`
|
|
59
|
+
| Input | Output |
|
|
60
|
+
| -------------------------------------- | ----------------------------------- |
|
|
61
|
+
| `<WuField Label="First name" />` | ✅ `Label={wt("First name")}` |
|
|
62
62
|
| `<WuInput placeholder="Enter name" />` | ✅ `placeholder={wt("Enter name")}` |
|
|
63
|
-
| `<WuDialog title="Confirm?" />`
|
|
64
|
-
| `<WuField Label={variable} />`
|
|
65
|
-
| `<WuField Label="" />`
|
|
66
|
-
| `<WuIcon Label="x" />`
|
|
67
|
-
| `<input placeholder="x" />`
|
|
68
|
-
| `<WuField data-skip Label="x" />`
|
|
63
|
+
| `<WuDialog title="Confirm?" />` | ✅ `title={wt("Confirm?")}` |
|
|
64
|
+
| `<WuField Label={variable} />` | ❌ |
|
|
65
|
+
| `<WuField Label="" />` | ❌ |
|
|
66
|
+
| `<WuIcon Label="x" />` | ❌ |
|
|
67
|
+
| `<input placeholder="x" />` | ❌ |
|
|
68
|
+
| `<WuField data-skip Label="x" />` | ❌ |
|
|
69
69
|
|
|
70
70
|
## `wt()` calls
|
|
71
71
|
|
|
72
|
-
Plugin records static args into `wick-ui-i18n.json`. No code rewrite unless
|
|
72
|
+
Plugin records static args into `wick-ui-i18n.json`. No code rewrite unless
|
|
73
|
+
template literal with expressions.
|
|
73
74
|
|
|
74
|
-
| Input
|
|
75
|
-
|
|
76
|
-
| `wt("Hello")`
|
|
77
|
-
| ``
|
|
78
|
-
| ``
|
|
79
|
-
| ``
|
|
80
|
-
| `wt(variable)`
|
|
81
|
-
| ``
|
|
75
|
+
| Input | Dictionary | Code output |
|
|
76
|
+
| ----------------------------- | --------------------- | --------------------------------------------- |
|
|
77
|
+
| `wt("Hello")` | ✅ `"Hello"` | `wt("Hello")` |
|
|
78
|
+
| ``wt(`Hello`)`` | ✅ `"Hello"` | ``wt(`Hello`)`` |
|
|
79
|
+
| ``wt(`Hello ${name}`)`` | ✅ `"Hello"` | `` `${wt("Hello")} ${name}` `` |
|
|
80
|
+
| ``wt(`Hello ${a} and ${b}`)`` | ✅ `"Hello"`, `"and"` | `` `${wt("Hello")} ${a} ${wt("and")} ${b}` `` |
|
|
81
|
+
| `wt(variable)` | ❌ | — |
|
|
82
|
+
| ``wt(`${a}${b}`)`` | ❌ | — |
|
|
82
83
|
|
|
83
84
|
## Data files (`extractFromKeys` option)
|
|
84
85
|
|
|
85
86
|
No code rewrite — keys only recorded in `wick-ui-i18n.json`.
|
|
86
87
|
|
|
87
|
-
| Input
|
|
88
|
-
|
|
88
|
+
| Input | Dictionary |
|
|
89
|
+
| ------------------------------------------------------- | ---------------- |
|
|
89
90
|
| `{ label: 'Analytics' }` + `extractFromKeys: ['label']` | ✅ `"Analytics"` |
|
|
90
|
-
| `{ label: variable }`
|
|
91
|
-
| `{ label: '' }`
|
|
91
|
+
| `{ label: variable }` | ❌ |
|
|
92
|
+
| `{ label: '' }` | ❌ |
|
|
92
93
|
|
|
93
94
|
---
|
|
94
95
|
|
|
95
96
|
## Options
|
|
96
97
|
|
|
97
|
-
| Option
|
|
98
|
-
|
|
99
|
-
| `components`
|
|
100
|
-
| `ignoreComponents`
|
|
101
|
-
| `translatableProps` | `['Label','placeholder','title','aria-label','aria-placeholder']` | Props rewritten to `wt()`
|
|
102
|
-
| `extractFromKeys`
|
|
103
|
-
| `excludeFiles`
|
|
104
|
-
| `debug`
|
|
98
|
+
| Option | Default | Description |
|
|
99
|
+
| ------------------- | ----------------------------------------------------------------- | ------------------------------------- |
|
|
100
|
+
| `components` | `[]` | Extra components treated like Wu\* |
|
|
101
|
+
| `ignoreComponents` | `[]` | Extra components never translated |
|
|
102
|
+
| `translatableProps` | `['Label','placeholder','title','aria-label','aria-placeholder']` | Props rewritten to `wt()` |
|
|
103
|
+
| `extractFromKeys` | `[]` | Object keys extracted into dictionary |
|
|
104
|
+
| `excludeFiles` | — | Files skipped entirely |
|
|
105
|
+
| `debug` | `false` | Log transforms to console |
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* };
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
21
|
+
import {createFilter} from 'vite'
|
|
22
|
+
import {TranslationProcessor} from './src/processor.js'
|
|
23
|
+
import {transformFile} from './src/transform.js'
|
|
24
|
+
import {printReport} from './src/debug.js'
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* @typedef {object} WickI18nOptions
|
|
@@ -46,15 +46,15 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
46
46
|
translatableProps: options.translatableProps,
|
|
47
47
|
extractFromKeys: options.extractFromKeys,
|
|
48
48
|
debug: options.debug,
|
|
49
|
-
})
|
|
49
|
+
})
|
|
50
50
|
|
|
51
|
-
const filter = createFilter([/\.(jsx|tsx|ts)$/], options.excludeFiles)
|
|
51
|
+
const filter = createFilter([/\.(jsx|tsx|ts)$/], options.excludeFiles)
|
|
52
52
|
|
|
53
|
-
let base =
|
|
53
|
+
let base = '/'
|
|
54
54
|
|
|
55
55
|
return {
|
|
56
|
-
name:
|
|
57
|
-
enforce:
|
|
56
|
+
name: 'wick-ui-i18n',
|
|
57
|
+
enforce: 'pre',
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Capture the resolved base so the dev-server middleware path matches
|
|
@@ -63,7 +63,7 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
63
63
|
* @param {import('vite').ResolvedConfig} resolvedConfig
|
|
64
64
|
*/
|
|
65
65
|
configResolved(resolvedConfig) {
|
|
66
|
-
base = resolvedConfig.base
|
|
66
|
+
base = resolvedConfig.base
|
|
67
67
|
},
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -71,8 +71,8 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
71
71
|
* watch-mode rebuilds.
|
|
72
72
|
*/
|
|
73
73
|
buildStart() {
|
|
74
|
-
processor.dictionary.clear()
|
|
75
|
-
processor.entries = []
|
|
74
|
+
processor.dictionary.clear()
|
|
75
|
+
processor.entries = []
|
|
76
76
|
},
|
|
77
77
|
|
|
78
78
|
/**
|
|
@@ -84,14 +84,14 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
84
84
|
*/
|
|
85
85
|
transform(code, id) {
|
|
86
86
|
const hasAnyTarget =
|
|
87
|
-
code.includes(
|
|
87
|
+
code.includes('Wu') ||
|
|
88
88
|
/\bwt\(/.test(code) ||
|
|
89
89
|
(processor.components.size > 0 &&
|
|
90
90
|
[...processor.components].some(c => code.includes(c))) ||
|
|
91
91
|
(processor.extractFromKeys.size > 0 &&
|
|
92
|
-
[...processor.extractFromKeys].some(k => code.includes(k)))
|
|
93
|
-
if (!filter(id) || !hasAnyTarget) return null
|
|
94
|
-
return transformFile(code, id, processor)
|
|
92
|
+
[...processor.extractFromKeys].some(k => code.includes(k)))
|
|
93
|
+
if (!filter(id) || !hasAnyTarget) return null
|
|
94
|
+
return transformFile(code, id, processor)
|
|
95
95
|
},
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -101,26 +101,26 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
101
101
|
*/
|
|
102
102
|
configureServer(server) {
|
|
103
103
|
server.middlewares.use(`${base}wick-ui-i18n.json`, (_req, res) => {
|
|
104
|
-
res.setHeader(
|
|
104
|
+
res.setHeader('Content-Type', 'application/json')
|
|
105
105
|
res.end(
|
|
106
106
|
JSON.stringify(Object.fromEntries(processor.dictionary), null, 2),
|
|
107
|
-
)
|
|
108
|
-
})
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
109
|
},
|
|
110
110
|
|
|
111
111
|
/** Emit the translation dictionary as a build asset and print debug table. */
|
|
112
112
|
generateBundle() {
|
|
113
113
|
this.emitFile({
|
|
114
|
-
type:
|
|
115
|
-
fileName:
|
|
114
|
+
type: 'asset',
|
|
115
|
+
fileName: 'wick-ui-i18n.json',
|
|
116
116
|
source: JSON.stringify(
|
|
117
117
|
Object.fromEntries(processor.dictionary),
|
|
118
118
|
null,
|
|
119
119
|
2,
|
|
120
120
|
),
|
|
121
|
-
})
|
|
121
|
+
})
|
|
122
122
|
|
|
123
|
-
printReport(processor.entries)
|
|
123
|
+
printReport(processor.entries)
|
|
124
124
|
},
|
|
125
|
-
}
|
|
125
|
+
}
|
|
126
126
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npm-questionpro/wick-ui-i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0-next.10",
|
|
4
|
+
"private": false,
|
|
4
5
|
"license": "ISC",
|
|
5
6
|
"description": "Auto-translation AST wrapper for Wick UI",
|
|
6
7
|
"type": "module",
|
|
7
|
-
"main": "index.js",
|
|
8
8
|
"types": "index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
@@ -13,19 +13,35 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"vite": "^
|
|
16
|
+
"vite": "^8.0.15"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/babel__traverse": "^7.28.0",
|
|
20
|
-
"vite": "^
|
|
21
|
-
"vitest": "^
|
|
20
|
+
"vite": "^8.0.15",
|
|
21
|
+
"vitest": "^4.1.8",
|
|
22
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
23
|
+
"eslint": "^10.4.1",
|
|
24
|
+
"@npm-questionpro/wick-ui-eslint-config": "1.0.0",
|
|
25
|
+
"@wick-ui/tsconfig": "1.0.0"
|
|
22
26
|
},
|
|
23
27
|
"dependencies": {
|
|
24
28
|
"@babel/parser": "^7.29.0",
|
|
25
29
|
"@babel/traverse": "^7.29.0",
|
|
26
30
|
"magic-string": "^0.30.21"
|
|
27
31
|
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"index.d.ts",
|
|
35
|
+
"src"
|
|
36
|
+
],
|
|
37
|
+
"prettier": "@npm-questionpro/wick-ui-prettier-config",
|
|
28
38
|
"scripts": {
|
|
29
|
-
"test": "vitest run"
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"test:ci": "vitest run --coverage",
|
|
42
|
+
"format": "prettier --write .",
|
|
43
|
+
"format:ci": "prettier --check .",
|
|
44
|
+
"lint": "eslint . --fix",
|
|
45
|
+
"lint:ci": "eslint --max-warnings 0 ."
|
|
30
46
|
}
|
|
31
47
|
}
|
package/src/debug.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* after the bundle is generated.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {basename} from 'node:path'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Walk up the Babel path and collect JSX element names from outermost inward.
|
|
@@ -13,17 +13,17 @@ import { basename } from "node:path";
|
|
|
13
13
|
* @returns {string} e.g. `"WuProvider > div > WuButton"`
|
|
14
14
|
*/
|
|
15
15
|
export function getComponentTree(path) {
|
|
16
|
-
const parts = []
|
|
17
|
-
path.findParent(
|
|
16
|
+
const parts = []
|
|
17
|
+
path.findParent(p => {
|
|
18
18
|
if (p.isJSXElement()) {
|
|
19
19
|
const name =
|
|
20
20
|
p.node.openingElement.name.name ||
|
|
21
|
-
p.node.openingElement.name.property?.name
|
|
22
|
-
if (name) parts.unshift(name)
|
|
21
|
+
p.node.openingElement.name.property?.name
|
|
22
|
+
if (name) parts.unshift(name)
|
|
23
23
|
}
|
|
24
|
-
return false
|
|
25
|
-
})
|
|
26
|
-
return parts.join(
|
|
24
|
+
return false
|
|
25
|
+
})
|
|
26
|
+
return parts.join(' > ') || '(root)'
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -42,39 +42,38 @@ export function getComponentTree(path) {
|
|
|
42
42
|
*/
|
|
43
43
|
export function printReport(entries) {
|
|
44
44
|
if (!entries.length) {
|
|
45
|
-
console.log(
|
|
46
|
-
return
|
|
45
|
+
console.log('\n[wick-i18n] No translations captured.\n')
|
|
46
|
+
return
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const headers = [
|
|
49
|
+
const headers = ['Text', 'File', 'Component Tree']
|
|
50
50
|
|
|
51
|
-
const rows = entries.map(
|
|
51
|
+
const rows = entries.map(e => [e.text, basename(e.file), e.componentTree])
|
|
52
52
|
|
|
53
|
-
const cols = headers.length;
|
|
54
53
|
const widths = headers.map((h, i) =>
|
|
55
|
-
Math.max(h.length, ...rows.map(
|
|
56
|
-
)
|
|
54
|
+
Math.max(h.length, ...rows.map(r => r[i].length)),
|
|
55
|
+
)
|
|
57
56
|
|
|
58
|
-
const pad = (str, w) => str.padEnd(w)
|
|
57
|
+
const pad = (str, w) => str.padEnd(w)
|
|
59
58
|
const sep = (l, m, r, fill) =>
|
|
60
|
-
l + widths.map(
|
|
59
|
+
l + widths.map(w => fill.repeat(w + 2)).join(m) + r
|
|
61
60
|
|
|
62
|
-
const top = sep(
|
|
63
|
-
const mid = sep(
|
|
64
|
-
const bottom = sep(
|
|
65
|
-
const row =
|
|
66
|
-
|
|
61
|
+
const top = sep('┌', '┬', '┐', '─')
|
|
62
|
+
const mid = sep('├', '┼', '┤', '─')
|
|
63
|
+
const bottom = sep('└', '┴', '┘', '─')
|
|
64
|
+
const row = cells =>
|
|
65
|
+
'│' + cells.map((c, i) => ` ${pad(c, widths[i])} `).join('│') + '│'
|
|
67
66
|
|
|
68
67
|
const lines = [
|
|
69
|
-
|
|
68
|
+
'',
|
|
70
69
|
`[wick-i18n] Build report — ${entries.length} translation(s) captured`,
|
|
71
70
|
top,
|
|
72
71
|
row(headers),
|
|
73
72
|
mid,
|
|
74
73
|
...rows.map(row),
|
|
75
74
|
bottom,
|
|
76
|
-
|
|
77
|
-
]
|
|
75
|
+
'',
|
|
76
|
+
]
|
|
78
77
|
|
|
79
|
-
console.log(lines.join(
|
|
78
|
+
console.log(lines.join('\n'))
|
|
80
79
|
}
|
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
|
}
|