@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/wickuii18n.test.js
DELETED
|
@@ -1,873 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import wickuiI18nPlugin from "./index.js";
|
|
3
|
-
|
|
4
|
-
function transform(code, options = {}) {
|
|
5
|
-
const plugin = wickuiI18nPlugin(options);
|
|
6
|
-
const result = plugin.transform(code, "TestFile.jsx");
|
|
7
|
-
return result ? result.code : code;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function transformTs(code, options = {}) {
|
|
11
|
-
const plugin = wickuiI18nPlugin(options);
|
|
12
|
-
const result = plugin.transform(code, "TestFile.ts");
|
|
13
|
-
return result ? result.code : code;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Run the full plugin lifecycle on `code` and return the emitted dictionary.
|
|
18
|
-
* Needed for wt() tests since those record keys without transforming code.
|
|
19
|
-
*/
|
|
20
|
-
function getDictionary(code, options = {}) {
|
|
21
|
-
const plugin = wickuiI18nPlugin(options);
|
|
22
|
-
plugin.buildStart();
|
|
23
|
-
plugin.transform(code, "TestFile.jsx");
|
|
24
|
-
let dict = {};
|
|
25
|
-
plugin.generateBundle.call({
|
|
26
|
-
emitFile: ({ source }) => {
|
|
27
|
-
dict = JSON.parse(source);
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
return dict;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe("Wick UI i18n Vite Plugin", () => {
|
|
34
|
-
it("1. Translates basic Wu* components and injects import", () => {
|
|
35
|
-
const code = `<WuButton>Submit</WuButton>`;
|
|
36
|
-
const result = transform(code);
|
|
37
|
-
expect(result).toContain(`import { WuTranslate }`);
|
|
38
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Submit"></WuTranslate>`);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("2. Ignores components in the ignoreComponents list", () => {
|
|
42
|
-
const code = `<WuIcon>star</WuIcon>`;
|
|
43
|
-
const result = transform(code);
|
|
44
|
-
expect(result).toBe(code);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("3. Handles smart nested ignored components (Ignored inside Target)", () => {
|
|
48
|
-
const code = `<WuButton>Click <WuIcon>star</WuIcon></WuButton>`;
|
|
49
|
-
const result = transform(code);
|
|
50
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Click"></WuTranslate>`);
|
|
51
|
-
expect(result).toContain(`<WuIcon>star</WuIcon>`);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("4. Handles smart nested target components (Target inside regular HTML)", () => {
|
|
55
|
-
const code = `<WuProvider><div><WuButton>Save</WuButton></div></WuProvider>`;
|
|
56
|
-
const result = transform(code);
|
|
57
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Save"></WuTranslate>`);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("5. Respects data-skip and data-i18n-skip flags", () => {
|
|
61
|
-
const code1 = `<WuButton data-skip>Ignored</WuButton>`;
|
|
62
|
-
const code2 = `<WuButton data-i18n-skip>Ignored</WuButton>`;
|
|
63
|
-
expect(transform(code1)).toBe(code1);
|
|
64
|
-
expect(transform(code2)).toBe(code2);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("6. Uses explicit keys when data-i18n-key is provided", () => {
|
|
68
|
-
const code = `<WuButton data-i18n-key="btn_login">Log In</WuButton>`;
|
|
69
|
-
const result = transform(code);
|
|
70
|
-
expect(result).toContain(
|
|
71
|
-
`<WuTranslate __i18nKey="btn_login"></WuTranslate>`,
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("6b. data-i18n-key propagates through nested elements", () => {
|
|
76
|
-
const code = `<WuButton data-i18n-key="btn_login"><span>Log In</span></WuButton>`;
|
|
77
|
-
const result = transform(code);
|
|
78
|
-
expect(result).toContain(
|
|
79
|
-
`<WuTranslate __i18nKey="btn_login"></WuTranslate>`,
|
|
80
|
-
);
|
|
81
|
-
expect(result).not.toContain(`__i18nKey="Log In"`);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("6c. data-i18n-key on ternary parent is ignored — each branch uses its own text as key", () => {
|
|
85
|
-
const code = `<WuButton data-i18n-key="btn">{flag ? "Yes" : "No"}</WuButton>`;
|
|
86
|
-
const result = transform(code);
|
|
87
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Yes"></WuTranslate>`);
|
|
88
|
-
expect(result).toContain(`<WuTranslate __i18nKey="No"></WuTranslate>`);
|
|
89
|
-
expect(result).not.toContain(`__i18nKey="btn"`);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("7. Translates JSX Expression Containers (Strings in braces)", () => {
|
|
93
|
-
const code = `<WuButton>{"Hello World"}</WuButton>`;
|
|
94
|
-
const result = transform(code);
|
|
95
|
-
expect(result).toContain(
|
|
96
|
-
`<WuTranslate __i18nKey="Hello World"></WuTranslate>`,
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("8. Does not translate JSX attribute string values", () => {
|
|
101
|
-
const code = `<WuButton variant={'secondary'}>Submit</WuButton>`;
|
|
102
|
-
const result = transform(code);
|
|
103
|
-
expect(result).not.toContain(`__i18nKey="secondary"`);
|
|
104
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Submit"></WuTranslate>`);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("9. Supports data-i18n-wrapper on non-target tags", () => {
|
|
108
|
-
const code = `<span data-i18n-wrapper>Wrapped Text</span>`;
|
|
109
|
-
const triggerCode = `import { WuPlaceholder } from 'lib';\n` + code;
|
|
110
|
-
const result = transform(triggerCode);
|
|
111
|
-
expect(result).toContain(
|
|
112
|
-
`<WuTranslate __i18nKey="Wrapped Text"></WuTranslate>`,
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("10. Skips files matching excludeFiles", () => {
|
|
117
|
-
const code = `<WuButton>Submit</WuButton>`;
|
|
118
|
-
const plugin = wickuiI18nPlugin({ excludeFiles: ["**/ignored/**"] });
|
|
119
|
-
const hit = plugin.transform(code, "src/components/Form.jsx");
|
|
120
|
-
const miss = plugin.transform(code, "src/ignored/Form.jsx");
|
|
121
|
-
expect(hit?.code).toContain("WuTranslate");
|
|
122
|
-
expect(miss).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("11. Strips newline from JSX text node", () => {
|
|
126
|
-
const code = `<WuButton>Hello\nWorld</WuButton>`;
|
|
127
|
-
const result = transform(code);
|
|
128
|
-
expect(result).toContain(
|
|
129
|
-
`<WuTranslate __i18nKey="Hello World"></WuTranslate>`,
|
|
130
|
-
);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("11b. Collapses multiple newlines/spaces into a single space", () => {
|
|
134
|
-
const code = `<WuButton>Hello\n\n World</WuButton>`;
|
|
135
|
-
const result = transform(code);
|
|
136
|
-
expect(result).toContain(
|
|
137
|
-
`<WuTranslate __i18nKey="Hello World"></WuTranslate>`,
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("12. Translates both static branches of a ternary", () => {
|
|
142
|
-
const code = `<WuButton>{isActive ? "Deactivate" : "Activate"}</WuButton>`;
|
|
143
|
-
const result = transform(code);
|
|
144
|
-
expect(result).toContain(
|
|
145
|
-
`<WuTranslate __i18nKey="Deactivate"></WuTranslate>`,
|
|
146
|
-
);
|
|
147
|
-
expect(result).toContain(
|
|
148
|
-
`<WuTranslate __i18nKey="Activate"></WuTranslate>`,
|
|
149
|
-
);
|
|
150
|
-
expect(result).toContain(`isActive ?`);
|
|
151
|
-
expect(result).toContain(`import { WuTranslate }`);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("12b. Translates only the static branch when one side is dynamic", () => {
|
|
155
|
-
const code = `<WuButton>{isActive ? "Deactivate" : dynamicLabel}</WuButton>`;
|
|
156
|
-
const result = transform(code);
|
|
157
|
-
expect(result).toContain(
|
|
158
|
-
`<WuTranslate __i18nKey="Deactivate"></WuTranslate>`,
|
|
159
|
-
);
|
|
160
|
-
expect(result).toContain(`dynamicLabel`);
|
|
161
|
-
expect(result).not.toContain(`__i18nKey="dynamicLabel"`);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("12c. Skips ternary when both branches are dynamic", () => {
|
|
165
|
-
const code = `<WuButton>{isActive ? labelA : labelB}</WuButton>`;
|
|
166
|
-
const result = transform(code);
|
|
167
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
168
|
-
expect(result).toContain(`labelA`);
|
|
169
|
-
expect(result).toContain(`labelB`);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("12d. Translates ternary with template literal branches", () => {
|
|
173
|
-
const code = "<WuButton>{flag ? `Yes` : `No`}</WuButton>";
|
|
174
|
-
const result = transform(code);
|
|
175
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Yes"></WuTranslate>`);
|
|
176
|
-
expect(result).toContain(`<WuTranslate __i18nKey="No"></WuTranslate>`);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("12e. Skips ternary inside ignored component", () => {
|
|
180
|
-
const code = `<WuIcon>{flag ? "A" : "B"}</WuIcon>`;
|
|
181
|
-
const result = transform(code);
|
|
182
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("12f. Respects data-skip on parent of ternary", () => {
|
|
186
|
-
const code = `<WuButton data-skip>{flag ? "A" : "B"}</WuButton>`;
|
|
187
|
-
const result = transform(code);
|
|
188
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("12g. Nested ternary: all static branches translated recursively", () => {
|
|
192
|
-
const code = `<WuButton>{a ? "A" : b ? "B" : "C"}</WuButton>`;
|
|
193
|
-
const result = transform(code);
|
|
194
|
-
expect(result).toContain(`<WuTranslate __i18nKey="A"></WuTranslate>`);
|
|
195
|
-
expect(result).toContain(`<WuTranslate __i18nKey="B"></WuTranslate>`);
|
|
196
|
-
expect(result).toContain(`<WuTranslate __i18nKey="C"></WuTranslate>`);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("12g2. Deeply nested ternary: all levels translated", () => {
|
|
200
|
-
const code = `<WuButton>{a ? "A" : b ? "B" : c ? "C" : "D"}</WuButton>`;
|
|
201
|
-
const result = transform(code);
|
|
202
|
-
expect(result).toContain(`<WuTranslate __i18nKey="A"></WuTranslate>`);
|
|
203
|
-
expect(result).toContain(`<WuTranslate __i18nKey="B"></WuTranslate>`);
|
|
204
|
-
expect(result).toContain(`<WuTranslate __i18nKey="C"></WuTranslate>`);
|
|
205
|
-
expect(result).toContain(`<WuTranslate __i18nKey="D"></WuTranslate>`);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("12g3. Nested ternary with dynamic branch: dynamic skipped, statics translated", () => {
|
|
209
|
-
const code = `<WuButton>{a ? "A" : b ? variable : "C"}</WuButton>`;
|
|
210
|
-
const result = transform(code);
|
|
211
|
-
expect(result).toContain(`<WuTranslate __i18nKey="A"></WuTranslate>`);
|
|
212
|
-
expect(result).toContain(`variable`);
|
|
213
|
-
expect(result).not.toContain(`__i18nKey="variable"`);
|
|
214
|
-
expect(result).toContain(`<WuTranslate __i18nKey="C"></WuTranslate>`);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it("12h. Ternary outside any Wu/wrapper component is not translated", () => {
|
|
218
|
-
const code = `<div>{flag ? "A" : "B"}</div>`;
|
|
219
|
-
const result = transform(code);
|
|
220
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("13. Translates dynamic string literals", () => {
|
|
224
|
-
const code = `<WuButton>Submit {name}</WuButton>`;
|
|
225
|
-
const result = transform(code);
|
|
226
|
-
expect(result).toContain(`import { WuTranslate }`);
|
|
227
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Submit"></WuTranslate>`);
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// ─── HTML entities ─────────────────────────────────────────────────────────
|
|
232
|
-
//
|
|
233
|
-
// Any text segment containing an HTML entity must be left completely untouched.
|
|
234
|
-
// Entities are presentational / structural characters ( , &, ©…)
|
|
235
|
-
// that are not translatable and would confuse translation APIs.
|
|
236
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
237
|
-
|
|
238
|
-
describe("HTML entities", () => {
|
|
239
|
-
it("E1. standalone named entity is not wrapped", () => {
|
|
240
|
-
const code = `<WuButton>&</WuButton>`;
|
|
241
|
-
const result = transform(code);
|
|
242
|
-
expect(result).not.toContain("WuTranslate");
|
|
243
|
-
expect(result).toContain("&");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("E2. text mixed with entity — non-entity parts wrapped, entity preserved", () => {
|
|
247
|
-
const code = `<WuButton>Hello & World</WuButton>`;
|
|
248
|
-
const result = transform(code);
|
|
249
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Hello"></WuTranslate>`);
|
|
250
|
-
expect(result).toContain(`<WuTranslate __i18nKey="World"></WuTranslate>`);
|
|
251
|
-
expect(result).toContain("&");
|
|
252
|
-
// must not treat the whole string (decoded or raw) as one key
|
|
253
|
-
expect(result).not.toContain(`__i18nKey="Hello & World"`);
|
|
254
|
-
expect(result).not.toContain(`__i18nKey="Hello & World"`);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("E3. non-breaking space entity is not wrapped", () => {
|
|
258
|
-
const code = `<WuButton> </WuButton>`;
|
|
259
|
-
const result = transform(code);
|
|
260
|
-
expect(result).not.toContain("WuTranslate");
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it("E4. numeric decimal entity is not wrapped", () => {
|
|
264
|
-
// © = ©
|
|
265
|
-
const code = `<WuButton>©</WuButton>`;
|
|
266
|
-
const result = transform(code);
|
|
267
|
-
expect(result).not.toContain("WuTranslate");
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("E5. numeric hex entity is not wrapped", () => {
|
|
271
|
-
// © = ©
|
|
272
|
-
const code = `<WuButton>©</WuButton>`;
|
|
273
|
-
const result = transform(code);
|
|
274
|
-
expect(result).not.toContain("WuTranslate");
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("E6. entity inside static string expression is not wrapped", () => {
|
|
278
|
-
const code = `<WuButton>{"Hello & World"}</WuButton>`;
|
|
279
|
-
const result = transform(code);
|
|
280
|
-
expect(result).not.toContain("WuTranslate");
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("E7. entity branch in ternary not wrapped, clean branch still wrapped", () => {
|
|
284
|
-
const code = `<WuButton>{flag ? "Yes &" : "No"}</WuButton>`;
|
|
285
|
-
const result = transform(code);
|
|
286
|
-
expect(result).not.toContain(`__i18nKey="Yes &"`);
|
|
287
|
-
expect(result).toContain(`<WuTranslate __i18nKey="No"></WuTranslate>`);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("E8. entity JSXText sibling not wrapped, clean siblings still wrapped", () => {
|
|
291
|
-
// <WuButton>Hello {x}&{y} World</WuButton>
|
|
292
|
-
// JSXText nodes: "Hello ", "&", " World"
|
|
293
|
-
// Babel decodes & → "&" in .value, so check both forms are absent
|
|
294
|
-
const code = `<WuButton>Hello {x}&{y} World</WuButton>`;
|
|
295
|
-
const result = transform(code);
|
|
296
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Hello"></WuTranslate>`);
|
|
297
|
-
expect(result).not.toContain(`__i18nKey="&"`);
|
|
298
|
-
expect(result).not.toContain(`__i18nKey="&"`);
|
|
299
|
-
expect(result).toContain(`<WuTranslate __i18nKey="World"></WuTranslate>`);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it("E9. multiple entities — text between them wrapped, both entities kept", () => {
|
|
303
|
-
// <Tag> → <<WuTranslate key="Tag" />>
|
|
304
|
-
const code = `<WuButton><Tag></WuButton>`;
|
|
305
|
-
const result = transform(code);
|
|
306
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Tag"></WuTranslate>`);
|
|
307
|
-
expect(result).toContain("<");
|
|
308
|
-
expect(result).toContain(">");
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("E10. entity at boundaries — text in middle still wrapped", () => {
|
|
312
|
-
// Hello → <WuTranslate key="Hello" />
|
|
313
|
-
const code = `<WuButton> Hello </WuButton>`;
|
|
314
|
-
const result = transform(code);
|
|
315
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Hello"></WuTranslate>`);
|
|
316
|
-
expect(result).toContain(" ");
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it("E11. only entities, no text — nothing wrapped", () => {
|
|
320
|
-
const code = `<WuButton><></WuButton>`;
|
|
321
|
-
const result = transform(code);
|
|
322
|
-
expect(result).not.toContain("WuTranslate");
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// fix 1: whitespace normalisation inside entity-split segments
|
|
326
|
-
it("E12. internal newlines in entity-split segment are normalised", () => {
|
|
327
|
-
const code = "<WuButton>Hello\nWorld & Goodbye\nFriend</WuButton>";
|
|
328
|
-
const result = transform(code);
|
|
329
|
-
expect(result).toContain(`__i18nKey="Hello World"`);
|
|
330
|
-
expect(result).toContain(`__i18nKey="Goodbye Friend"`);
|
|
331
|
-
expect(result).not.toContain("Hello\\nWorld");
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it("E13. multiple spaces in entity-split segment are collapsed", () => {
|
|
335
|
-
const code = "<WuButton>Hello World & Foo</WuButton>";
|
|
336
|
-
const result = transform(code);
|
|
337
|
-
expect(result).toContain(`__i18nKey="Hello World"`);
|
|
338
|
-
expect(result).toContain(`__i18nKey="Foo"`);
|
|
339
|
-
expect(result).not.toContain(`__i18nKey="Hello World"`);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// fix 2: data-i18n-key bypass
|
|
343
|
-
it("E14. data-i18n-key with entity — warns and splits using text as keys", () => {
|
|
344
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
345
|
-
const code = `<WuButton data-i18n-key="btn">Hello & World</WuButton>`;
|
|
346
|
-
const result = transform(code);
|
|
347
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
348
|
-
expect.stringContaining('data-i18n-key="btn"'),
|
|
349
|
-
);
|
|
350
|
-
expect(result).toContain(`__i18nKey="Hello"`);
|
|
351
|
-
expect(result).toContain(`__i18nKey="World"`);
|
|
352
|
-
expect(result).not.toContain(`__i18nKey="btn"`);
|
|
353
|
-
warnSpy.mockRestore();
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
describe("Mixed static + dynamic interpolation", () => {
|
|
358
|
-
describe("A: JSX mixed children", () => {
|
|
359
|
-
it("A1. text before dynamic → wraps leading text, keeps expr", () => {
|
|
360
|
-
// <WuButton>hello {name}</WuButton>
|
|
361
|
-
// children: JSXText("hello ") + JSXExpressionContainer(name)
|
|
362
|
-
const code = `<WuButton>hello {name}</WuButton>`;
|
|
363
|
-
const result = transform(code);
|
|
364
|
-
expect(result).toContain(`<WuTranslate __i18nKey="hello"></WuTranslate>`);
|
|
365
|
-
expect(result).toContain(`{name}`);
|
|
366
|
-
expect(result).not.toContain(`__i18nKey="name"`);
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("A2. dynamic before text → keeps expr, wraps trailing text", () => {
|
|
370
|
-
// <WuButton>{name} how are you</WuButton>
|
|
371
|
-
const code = `<WuButton>{name} how are you</WuButton>`;
|
|
372
|
-
const result = transform(code);
|
|
373
|
-
expect(result).toContain(
|
|
374
|
-
`<WuTranslate __i18nKey="how are you"></WuTranslate>`,
|
|
375
|
-
);
|
|
376
|
-
expect(result).toContain(`{name}`);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it("A3. text + dynamic + text → wraps both text segments independently", () => {
|
|
380
|
-
// <WuButton>hello {name} how are you</WuButton>
|
|
381
|
-
const code = `<WuButton>hello {name} how are you</WuButton>`;
|
|
382
|
-
const result = transform(code);
|
|
383
|
-
expect(result).toContain(`<WuTranslate __i18nKey="hello"></WuTranslate>`);
|
|
384
|
-
expect(result).toContain(
|
|
385
|
-
`<WuTranslate __i18nKey="how are you"></WuTranslate>`,
|
|
386
|
-
);
|
|
387
|
-
expect(result).toContain(`{name}`);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it("A4. multiple dynamics with text between → wraps middle text", () => {
|
|
391
|
-
// <WuButton>{a} and {b}</WuButton>
|
|
392
|
-
const code = `<WuButton>{a} and {b}</WuButton>`;
|
|
393
|
-
const result = transform(code);
|
|
394
|
-
expect(result).toContain(`<WuTranslate __i18nKey="and"></WuTranslate>`);
|
|
395
|
-
expect(result).toContain(`{a}`);
|
|
396
|
-
expect(result).toContain(`{b}`);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it("A5. text + two dynamics + text → wraps outer text, keeps both exprs", () => {
|
|
400
|
-
// <WuButton>hello {a} and {b} end</WuButton>
|
|
401
|
-
const code = `<WuButton>hello {a} and {b} end</WuButton>`;
|
|
402
|
-
const result = transform(code);
|
|
403
|
-
expect(result).toContain(`<WuTranslate __i18nKey="hello"></WuTranslate>`);
|
|
404
|
-
expect(result).toContain(`<WuTranslate __i18nKey="and"></WuTranslate>`);
|
|
405
|
-
expect(result).toContain(`<WuTranslate __i18nKey="end"></WuTranslate>`);
|
|
406
|
-
expect(result).toContain(`{a}`);
|
|
407
|
-
expect(result).toContain(`{b}`);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
it("A6. whitespace-only text between two dynamics → not wrapped", () => {
|
|
411
|
-
// <WuButton>{a} {b}</WuButton> — JSXText between them is only spaces
|
|
412
|
-
const code = `<WuButton>{a} {b}</WuButton>`;
|
|
413
|
-
const result = transform(code);
|
|
414
|
-
// whitespace-only JSXText must not produce a key
|
|
415
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// ── B: Template literal with expressions ──────────────────────────────────
|
|
420
|
-
// Currently the plugin skips any TemplateLiteral whose .expressions.length > 0.
|
|
421
|
-
// The expected output reconstructs the container as a React fragment:
|
|
422
|
-
// {`hello ${name}`} → <><WuTranslate __i18nKey="hello" />{name}</>
|
|
423
|
-
//
|
|
424
|
-
// All tests in this group are expected to FAIL until the feature lands.
|
|
425
|
-
|
|
426
|
-
describe("B: Template literal with expressions", () => {
|
|
427
|
-
it("B1. text before dynamic → wraps leading quasi, keeps expr", () => {
|
|
428
|
-
// {`hello ${name}`} → <><WuTranslate __i18nKey="hello" />{name}</>
|
|
429
|
-
const code = "<WuButton>{`hello ${name}`}</WuButton>";
|
|
430
|
-
const result = transform(code);
|
|
431
|
-
expect(result).toContain(`<WuTranslate __i18nKey="hello"></WuTranslate>`);
|
|
432
|
-
expect(result).toContain(`{name}`);
|
|
433
|
-
expect(result).not.toContain('__i18nKey="name"');
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("B2. dynamic before text → keeps expr, wraps trailing quasi", () => {
|
|
437
|
-
// {`${name} how are you`} → <>{name}<WuTranslate __i18nKey="how are you" /></>
|
|
438
|
-
const code = "<WuButton>{`${name} how are you`}</WuButton>";
|
|
439
|
-
const result = transform(code);
|
|
440
|
-
expect(result).toContain(
|
|
441
|
-
`<WuTranslate __i18nKey="how are you"></WuTranslate>`,
|
|
442
|
-
);
|
|
443
|
-
expect(result).toContain(`{name}`);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it("B3. text + dynamic + text → wraps both quasis, keeps expr in between", () => {
|
|
447
|
-
// {`hello ${name} how are you`}
|
|
448
|
-
// → <><WuTranslate __i18nKey="hello" />{name}<WuTranslate __i18nKey="how are you" /></>
|
|
449
|
-
const code = "<WuButton>{`hello ${name} how are you`}</WuButton>";
|
|
450
|
-
const result = transform(code);
|
|
451
|
-
expect(result).toContain(`<WuTranslate __i18nKey="hello"></WuTranslate>`);
|
|
452
|
-
expect(result).toContain(
|
|
453
|
-
`<WuTranslate __i18nKey="how are you"></WuTranslate>`,
|
|
454
|
-
);
|
|
455
|
-
expect(result).toContain(`{name}`);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it("B4. two dynamics with static text between → wraps middle quasi only", () => {
|
|
459
|
-
// {`${a} and ${b}`} → <>{a}<WuTranslate __i18nKey="and" />{b}</>
|
|
460
|
-
const code = "<WuButton>{`${a} and ${b}`}</WuButton>";
|
|
461
|
-
const result = transform(code);
|
|
462
|
-
expect(result).toContain(`<WuTranslate __i18nKey="and"></WuTranslate>`);
|
|
463
|
-
expect(result).toContain(`{a}`);
|
|
464
|
-
expect(result).toContain(`{b}`);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
it("B5. text + two dynamics + text → wraps outer quasis and middle quasi", () => {
|
|
468
|
-
// {`hello ${a} and ${b} end`}
|
|
469
|
-
// → <><WuTranslate key="hello" />{a}<WuTranslate key="and" />{b}<WuTranslate key="end" /></>
|
|
470
|
-
const code = "<WuButton>{`hello ${a} and ${b} end`}</WuButton>";
|
|
471
|
-
const result = transform(code);
|
|
472
|
-
expect(result).toContain(`<WuTranslate __i18nKey="hello"></WuTranslate>`);
|
|
473
|
-
expect(result).toContain(`<WuTranslate __i18nKey="and"></WuTranslate>`);
|
|
474
|
-
expect(result).toContain(`<WuTranslate __i18nKey="end"></WuTranslate>`);
|
|
475
|
-
expect(result).toContain(`{a}`);
|
|
476
|
-
expect(result).toContain(`{b}`);
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
it("B6. empty quasis between two dynamics → no key emitted for empty segment", () => {
|
|
480
|
-
// {`${a}${b}`} → <>{a}{b}</>
|
|
481
|
-
// quasis: ["", "", ""] — all empty, nothing to wrap
|
|
482
|
-
const code = "<WuButton>{`${a}${b}`}</WuButton>";
|
|
483
|
-
const result = transform(code);
|
|
484
|
-
// no translation key for empty/whitespace quasis
|
|
485
|
-
expect(result).not.toContain(`__i18nKey`);
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
it("B7. whitespace-only quasi between dynamics → not wrapped", () => {
|
|
489
|
-
// {`${a} ${b}`} → <>{a} {b}</>
|
|
490
|
-
const code = "<WuButton>{`${a} ${b}`}</WuButton>";
|
|
491
|
-
const result = transform(code);
|
|
492
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it("B8. ignored component — template literal skipped entirely", () => {
|
|
496
|
-
const code = "<WuIcon>{`hello ${name}`}</WuIcon>";
|
|
497
|
-
const result = transform(code);
|
|
498
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it("B9. data-skip — template literal skipped entirely", () => {
|
|
502
|
-
const code = "<WuButton data-skip>{`hello ${name}`}</WuButton>";
|
|
503
|
-
const result = transform(code);
|
|
504
|
-
expect(result).not.toContain(`WuTranslate`);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it("B10. output is a React fragment wrapping the interleaved nodes", () => {
|
|
508
|
-
// the entire {`...`} container is replaced with <> ... </>
|
|
509
|
-
const code = "<WuButton>{`hello ${name}`}</WuButton>";
|
|
510
|
-
const result = transform(code);
|
|
511
|
-
expect(result).toMatch(/<>.*WuTranslate.*<\/>/s);
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
// ─── wt() call expressions ───────────────────────────────────────────────────────
|
|
517
|
-
//
|
|
518
|
-
// wt("static string") is a runtime lookup helper exposed from useTranslate().
|
|
519
|
-
// The plugin’s job is to record static arguments into wick-ui-i18n.json so
|
|
520
|
-
// they reach the translation API. No code transformation is performed.
|
|
521
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
522
|
-
|
|
523
|
-
describe("wt() call expressions", () => {
|
|
524
|
-
it("W1. string literal arg is recorded in dictionary", () => {
|
|
525
|
-
const dict = getDictionary(`const text = wt("hello");`);
|
|
526
|
-
expect(dict).toHaveProperty("hello", "hello");
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
it("W2. static template literal arg is recorded", () => {
|
|
530
|
-
const dict = getDictionary("const text = wt(`hello`);");
|
|
531
|
-
expect(dict).toHaveProperty("hello", "hello");
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
it("W3. dynamic variable arg is ignored", () => {
|
|
535
|
-
const dict = getDictionary(`const text = wt(variable);`);
|
|
536
|
-
expect(dict).not.toHaveProperty("variable");
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
it("W4. template literal with expressions — static quasis extracted", () => {
|
|
540
|
-
// wt(`hello ${name}`) → `${wt("hello")} ${name}`
|
|
541
|
-
const dict = getDictionary("const text = wt(`hello ${name}`);");
|
|
542
|
-
expect(dict).toHaveProperty("hello", "hello");
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
it("W11. template literal — call is transformed into nested wt() calls", () => {
|
|
546
|
-
const result = transform("const text = wt(`hello ${name}`);");
|
|
547
|
-
expect(result).toContain('wt("hello")');
|
|
548
|
-
expect(result).not.toContain("wt(`hello ${name}`)");
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
it("W12. text before expr → leading quasi wrapped, expr kept", () => {
|
|
552
|
-
const result = transform("const t = wt(`hello ${name}`);");
|
|
553
|
-
expect(result).toContain('wt("hello")');
|
|
554
|
-
expect(result).toContain("${name}");
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it("W13. text after expr → trailing quasi wrapped, expr kept", () => {
|
|
558
|
-
const result = transform("const t = wt(`${name} how are you`);");
|
|
559
|
-
expect(result).toContain('wt("how are you")');
|
|
560
|
-
expect(result).toContain("${name}");
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
it("W14. text + expr + text → both quasis wrapped, expr in between", () => {
|
|
564
|
-
const result = transform("const t = wt(`hello ${name} how are you`);");
|
|
565
|
-
expect(result).toContain('wt("hello")');
|
|
566
|
-
expect(result).toContain('wt("how are you")');
|
|
567
|
-
expect(result).toContain("${name}");
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it("W15. only expressions, no static text → no transform", () => {
|
|
571
|
-
const result = transform("const t = wt(`${a}${b}`);");
|
|
572
|
-
expect(result).not.toContain("wt(\"");
|
|
573
|
-
// original call preserved as-is
|
|
574
|
-
expect(result).toContain("wt(`${a}${b}`)");
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it("W16. whitespace-only quasi between exprs → no transform", () => {
|
|
578
|
-
const result = transform("const t = wt(`${a} ${b}`);");
|
|
579
|
-
expect(result).not.toContain("wt(\"");
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
it("W5. multiple wt() calls in same file all recorded", () => {
|
|
583
|
-
const dict = getDictionary(`const a = wt("hello"); const b = wt("world");`);
|
|
584
|
-
expect(dict).toHaveProperty("hello", "hello");
|
|
585
|
-
expect(dict).toHaveProperty("world", "world");
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it("W6. whitespace in arg is normalised", () => {
|
|
589
|
-
const dict = getDictionary(`const text = wt("hello world");`);
|
|
590
|
-
expect(dict).toHaveProperty("hello world", "hello world");
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
it("W7. wt() call inside JSX expression is recorded", () => {
|
|
594
|
-
const dict = getDictionary(`<WuButton>{wt("hello")}</WuButton>`);
|
|
595
|
-
expect(dict).toHaveProperty("hello", "hello");
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
it("W8. code is not transformed — wt() call left as-is", () => {
|
|
599
|
-
const result = transform(`const text = wt("hello");`);
|
|
600
|
-
expect(result).not.toContain("WuTranslate");
|
|
601
|
-
expect(result).toContain(`wt("hello")`);
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
it("W9. wt() and JSX translations coexist in same file", () => {
|
|
605
|
-
const code = `<WuButton>{wt("save")}</WuButton>`;
|
|
606
|
-
const dict = getDictionary(code);
|
|
607
|
-
// wt() arg recorded
|
|
608
|
-
expect(dict).toHaveProperty("save", "save");
|
|
609
|
-
// code still not transformed (no WuTranslate wrapping the wt call)
|
|
610
|
-
const result = transform(code);
|
|
611
|
-
expect(result).toContain(`wt("save")`);
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
it("W10. empty string arg is ignored", () => {
|
|
615
|
-
const dict = getDictionary(`const text = wt("");`);
|
|
616
|
-
expect(Object.keys(dict)).toHaveLength(0);
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
// ─── translatableProps ────────────────────────────────────────────────────────
|
|
621
|
-
//
|
|
622
|
-
// The `Label` prop on Wu* components is rewritten to
|
|
623
|
-
// `{<WuTranslate __i18nKey="..." />}` and recorded in the dictionary.
|
|
624
|
-
// Custom props can be passed via `translatableProps` option to override the
|
|
625
|
-
// default. Dynamic props, ignored components, and data-skip are untouched.
|
|
626
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
describe("translatableProps", () => {
|
|
630
|
-
it("P1. prop on Wu* component rewritten to wt() and import injected", () => {
|
|
631
|
-
const code = `<WuField Label="First name" />`;
|
|
632
|
-
const result = transform(code);
|
|
633
|
-
expect(result).toContain(`import { wt }`);
|
|
634
|
-
expect(result).toContain(`Label={wt("First name")}`);
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
it("P2. placeholder and title translated by default", () => {
|
|
638
|
-
const r1 = transform(`<WuInput placeholder="Enter name" />`);
|
|
639
|
-
expect(r1).toContain(`placeholder={wt("Enter name")}`);
|
|
640
|
-
const r2 = transform(`<WuDialog title="Confirm?" />`);
|
|
641
|
-
expect(r2).toContain(`title={wt("Confirm?")}`);
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
it("P3. prop not in translatableProps is left untouched", () => {
|
|
645
|
-
const code = `<WuInput variant="secondary" Label="First name" />`;
|
|
646
|
-
const result = transform(code);
|
|
647
|
-
expect(result).toContain(`variant="secondary"`);
|
|
648
|
-
expect(result).not.toContain(`wt("secondary")`);
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
it("P4. prop on non-Wu component is not transformed", () => {
|
|
652
|
-
const code = `<input placeholder="Enter name" />`;
|
|
653
|
-
const result = transform(code);
|
|
654
|
-
expect(result).toBe(code);
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
it("P5. dynamic (expression) prop value is not transformed", () => {
|
|
658
|
-
const code = `<WuField Label={someProp} />`;
|
|
659
|
-
const result = transform(code);
|
|
660
|
-
expect(result).toBe(code);
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
it("P6. empty string prop is skipped", () => {
|
|
664
|
-
const code = `<WuField Label="" />`;
|
|
665
|
-
const result = transform(code);
|
|
666
|
-
expect(result).toBe(code);
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
it("P7. whitespace-only prop is skipped", () => {
|
|
670
|
-
const code = `<WuField Label=" " />`;
|
|
671
|
-
const result = transform(code);
|
|
672
|
-
expect(result).toBe(code);
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it("P8. prop on ignored component (WuIcon) is not transformed", () => {
|
|
676
|
-
const code = `<WuIcon title="star icon" />`;
|
|
677
|
-
const result = transform(code);
|
|
678
|
-
expect(result).toBe(code);
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it("P9. data-skip on element suppresses prop translation", () => {
|
|
682
|
-
const code = `<WuField data-skip Label="First name" />`;
|
|
683
|
-
const result = transform(code);
|
|
684
|
-
expect(result).not.toContain('wt(');
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
it("P10. data-i18n-skip on element suppresses prop translation", () => {
|
|
688
|
-
const code = `<WuField data-i18n-skip Label="First name" />`;
|
|
689
|
-
const result = transform(code);
|
|
690
|
-
expect(result).not.toContain('wt(');
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
it("P11. prop wt() and JSX text WuTranslate coexist — both imports injected", () => {
|
|
694
|
-
const code = `<WuField Label="First name">Submit</WuField>`;
|
|
695
|
-
const result = transform(code);
|
|
696
|
-
expect(result).toContain(`import { wt }`);
|
|
697
|
-
expect(result).toContain(`import { WuTranslate }`);
|
|
698
|
-
expect(result).toContain(`Label={wt("First name")}`);
|
|
699
|
-
expect(result).toContain(`<WuTranslate __i18nKey="Submit">`);
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
it("P12. wt already imported — no duplicate import injected", () => {
|
|
703
|
-
const code = [
|
|
704
|
-
`import { wt } from '@npm-questionpro/wick-ui-lib';`,
|
|
705
|
-
`<WuField Label="First name" />`,
|
|
706
|
-
].join('\n');
|
|
707
|
-
const result = transform(code);
|
|
708
|
-
const matches = (result.match(/import \{ wt \}/g) || []).length;
|
|
709
|
-
expect(matches).toBe(1);
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
it("P13. multiple translatable props on same element are all transformed", () => {
|
|
713
|
-
const code = `<WuInput placeholder="Enter name" title="Name field" />`;
|
|
714
|
-
const result = transform(code);
|
|
715
|
-
expect(result).toContain(`placeholder={wt("Enter name")}`);
|
|
716
|
-
expect(result).toContain(`title={wt("Name field")}`);
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
it("P14. passing translatableProps overrides defaults entirely", () => {
|
|
720
|
-
const code = `<WuField Label="First name" title="Name" />`;
|
|
721
|
-
const result = transform(code, {translatableProps: ['title']});
|
|
722
|
-
expect(result).toContain(`title={wt("Name")}`);
|
|
723
|
-
expect(result).toContain(`Label="First name"`);
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
it("P15. HTML entity in prop value is skipped", () => {
|
|
727
|
-
const code = `<WuField Label="Hello & World" />`;
|
|
728
|
-
const result = transform(code);
|
|
729
|
-
expect(result).not.toContain('wt(');
|
|
730
|
-
expect(result).toContain('&');
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
it("P16. multi-word prop value preserved as-is in the key", () => {
|
|
734
|
-
const code = `<WuField Label="Enter your full name" />`;
|
|
735
|
-
const result = transform(code);
|
|
736
|
-
expect(result).toContain(`wt("Enter your full name")`);
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
it("P17. newlines and extra spaces in prop value are normalised", () => {
|
|
740
|
-
const code = `<WuField Label="Enter\nyour name" />`;
|
|
741
|
-
const result = transform(code);
|
|
742
|
-
expect(result).toContain(`wt("Enter your name")`);
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
it("P18. prop on user-configured custom component is transformed", () => {
|
|
746
|
-
const code = `<MyWidget Label="Enter name" />`;
|
|
747
|
-
const result = transform(code, {components: ['MyWidget']});
|
|
748
|
-
expect(result).toContain(`Label={wt("Enter name")}`);
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
it("P19. prop values are recorded in the translation dictionary", () => {
|
|
752
|
-
const code = `<WuField Label="First name" />`;
|
|
753
|
-
const dict = getDictionary(code);
|
|
754
|
-
expect(dict).toHaveProperty('First name', 'First name');
|
|
755
|
-
});
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
// ─── .ts data-file key extraction ───────────────────────────────────────────────────
|
|
759
|
-
//
|
|
760
|
-
// Standalone wt() in .ts data files (e.g. nav config arrays) lets the plugin
|
|
761
|
-
// extract string literals into wick-ui-i18n.json without any JSX transform.
|
|
762
|
-
// The code is returned unchanged (null); wt() handles the lookup at render time.
|
|
763
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
764
|
-
|
|
765
|
-
// ─── extractFromKeys ────────────────────────────────────────────────────────
|
|
766
|
-
//
|
|
767
|
-
// Object property names configured via `extractFromKeys` have their string
|
|
768
|
-
// values recorded automatically — no wt() wrapping required in data files.
|
|
769
|
-
// Code is never rewritten; wt() handles the lookup at render time.
|
|
770
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
771
|
-
|
|
772
|
-
describe('extractFromKeys', () => {
|
|
773
|
-
it('EK1. string values of a configured key are recorded without code rewrite', () => {
|
|
774
|
-
const code = [
|
|
775
|
-
`export const ITEMS = [`,
|
|
776
|
-
` { label: 'Analytics', icon: 'wm-analytics' },`,
|
|
777
|
-
` { label: 'Engagement', icon: 'wm-trending-up' },`,
|
|
778
|
-
`]`,
|
|
779
|
-
].join('\n');
|
|
780
|
-
const dict = getDictionary(code, {extractFromKeys: ['label']});
|
|
781
|
-
expect(dict).toHaveProperty('Analytics', 'Analytics');
|
|
782
|
-
expect(dict).toHaveProperty('Engagement', 'Engagement');
|
|
783
|
-
// non-listed keys are ignored
|
|
784
|
-
expect(dict).not.toHaveProperty('wm-analytics');
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
it('EK2. transform returns null — data file code is never rewritten', () => {
|
|
788
|
-
const code = `export const ITEMS = [{ label: 'Analytics' }]`;
|
|
789
|
-
const plugin = wickuiI18nPlugin({extractFromKeys: ['label']});
|
|
790
|
-
const result = plugin.transform(code, 'navItems.ts');
|
|
791
|
-
expect(result).toBeNull();
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
it('EK3. works on .ts, .tsx, and .jsx files', () => {
|
|
795
|
-
const code = `export const x = { label: 'Hello' }`;
|
|
796
|
-
const opts = {extractFromKeys: ['label']};
|
|
797
|
-
for (const ext of ['ts', 'tsx', 'jsx']) {
|
|
798
|
-
const plugin = wickuiI18nPlugin(opts);
|
|
799
|
-
plugin.buildStart();
|
|
800
|
-
plugin.transform(code, `file.${ext}`);
|
|
801
|
-
let dict = {};
|
|
802
|
-
plugin.generateBundle.call({emitFile: ({source}) => { dict = JSON.parse(source); }});
|
|
803
|
-
expect(dict).toHaveProperty('Hello', 'Hello');
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
it('EK4. multiple configured keys all extracted', () => {
|
|
808
|
-
const code = `const x = { label: 'Save', title: 'Confirm', key: 'btn-save' }`;
|
|
809
|
-
const dict = getDictionary(code, {extractFromKeys: ['label', 'title']});
|
|
810
|
-
expect(dict).toHaveProperty('Save', 'Save');
|
|
811
|
-
expect(dict).toHaveProperty('Confirm', 'Confirm');
|
|
812
|
-
expect(dict).not.toHaveProperty('btn-save');
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
it('EK5. dynamic (non-literal) values are ignored', () => {
|
|
816
|
-
const code = `const x = { label: someVar }`;
|
|
817
|
-
const dict = getDictionary(code, {extractFromKeys: ['label']});
|
|
818
|
-
expect(Object.keys(dict)).toHaveLength(0);
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
it('EK6. empty string values are ignored', () => {
|
|
822
|
-
const code = `const x = { label: '' }`;
|
|
823
|
-
const dict = getDictionary(code, {extractFromKeys: ['label']});
|
|
824
|
-
expect(Object.keys(dict)).toHaveLength(0);
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
it('EK7. HTML entity in value is ignored', () => {
|
|
828
|
-
const code = `const x = { label: 'Hello & World' }`;
|
|
829
|
-
const dict = getDictionary(code, {extractFromKeys: ['label']});
|
|
830
|
-
expect(Object.keys(dict)).toHaveLength(0);
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
it('EK8. no extractFromKeys configured — object properties are not touched', () => {
|
|
834
|
-
const code = `const x = { label: 'Analytics' }`;
|
|
835
|
-
const dict = getDictionary(code, {});
|
|
836
|
-
expect(Object.keys(dict)).toHaveLength(0);
|
|
837
|
-
});
|
|
838
|
-
|
|
839
|
-
it('EK9. coexists with JSX text-child translation in the same build', () => {
|
|
840
|
-
const plugin = wickuiI18nPlugin({extractFromKeys: ['label']});
|
|
841
|
-
plugin.buildStart();
|
|
842
|
-
plugin.transform(`export const ITEMS = [{ label: 'Analytics' }]`, 'navItems.ts');
|
|
843
|
-
plugin.transform(`<WuButton>Submit</WuButton>`, 'Form.tsx');
|
|
844
|
-
let dict = {};
|
|
845
|
-
plugin.generateBundle.call({emitFile: ({source}) => { dict = JSON.parse(source); }});
|
|
846
|
-
expect(dict).toHaveProperty('Analytics', 'Analytics');
|
|
847
|
-
expect(dict).toHaveProperty('Submit', 'Submit');
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
it('EK10. full SECONDARY_NAVBAR_ITEMS shape — all 8 labels extracted', () => {
|
|
851
|
-
const code = `
|
|
852
|
-
export const SECONDARY_NAVBAR_ITEMS = [
|
|
853
|
-
{ key: 'account-operations', label: 'Analytics', icon: 'wm-analytics' },
|
|
854
|
-
{ key: 'engagement', label: 'Engagement', icon: 'wm-trending-up' },
|
|
855
|
-
{ key: 'system-notifications', label: 'System Notifications', icon: 'wm-notifications' },
|
|
856
|
-
{ key: 'search', label: 'Search', icon: 'wm-search' },
|
|
857
|
-
{ key: 'templates', label: 'Templates', icon: 'wc-static-content' },
|
|
858
|
-
{ key: 'translations', label: 'Translations', icon: 'wm-translate' },
|
|
859
|
-
{ key: 'ai', label: 'AI', icon: 'wc-ai' },
|
|
860
|
-
{ key: 'cpq-pricing', label: 'CPQ Pricing', icon: 'wc-pricing-analysis' },
|
|
861
|
-
]`;
|
|
862
|
-
const dict = getDictionary(code, {extractFromKeys: ['label']});
|
|
863
|
-
expect(Object.keys(dict)).toHaveLength(8);
|
|
864
|
-
expect(dict).toHaveProperty('Analytics');
|
|
865
|
-
expect(dict).toHaveProperty('System Notifications');
|
|
866
|
-
expect(dict).toHaveProperty('CPQ Pricing');
|
|
867
|
-
// non-label keys not extracted
|
|
868
|
-
expect(dict).not.toHaveProperty('account-operations');
|
|
869
|
-
expect(dict).not.toHaveProperty('wm-analytics');
|
|
870
|
-
});
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
|