@menapo/vi18n 0.0.0-stage → 0.1.1

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.
Files changed (100) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +110 -2
  3. package/dist/apply-Bre-lY9J.js +228 -0
  4. package/dist/captured-Bj8bO6vd.js +1034 -0
  5. package/dist/check-BIKEuu6B.js +201 -0
  6. package/dist/check-HsmmpJZg.js +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +41 -0
  9. package/dist/codec-DuvU2VQy.js +207 -0
  10. package/dist/collect-Cx4_SKOe.js +13 -0
  11. package/dist/collisions-CfpZU8e4.js +217 -0
  12. package/dist/compile-BaDh0cHG.js +245 -0
  13. package/dist/compile-Cc97JaW2.js +2502 -0
  14. package/dist/concurrency-C9UUWuDC.js +96 -0
  15. package/dist/concurrency-DONL7t4V.js +1 -0
  16. package/dist/config.d.ts +21 -0
  17. package/dist/config.js +39 -0
  18. package/dist/dir-zw6bGMKQ.js +290 -0
  19. package/dist/duplicates-yuRljQ6h.js +185 -0
  20. package/dist/engine-C2ENBRJe.js +531 -0
  21. package/dist/engine.d.ts +43 -0
  22. package/dist/engine.js +1241 -0
  23. package/dist/exclude-Du-Zd_qI.js +26 -0
  24. package/dist/export-C9Sy6UWv.js +103 -0
  25. package/dist/extract-C3S8btx0.js +298 -0
  26. package/dist/formats-9unqj_fZ.js +781 -0
  27. package/dist/i18next-9HAoxwUI.js +1 -0
  28. package/dist/i18next-BEJgv_S8.js +1596 -0
  29. package/dist/import-nANvQutw.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-C2ScuW6a.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui-zUiCl9Um.js +833 -0
  35. package/dist/lint.d.ts +105 -0
  36. package/dist/lint.js +971 -0
  37. package/dist/load-CQRz4PpM.js +2950 -0
  38. package/dist/load-gW-oJt1Q.js +747 -0
  39. package/dist/locales-DX4MOBFE.js +827 -0
  40. package/dist/lsp-RMMMc88P.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-CHVwBoRP.js +28 -0
  43. package/dist/merge-driver-BTX87aVm.js +422 -0
  44. package/dist/messages-BfjIExyB.js +166 -0
  45. package/dist/migrate-IpLDMXmq.js +1484 -0
  46. package/dist/migrate-rCQuOGsf.js +1 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DWE3SIUA.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C9Dbotkh.js +2031 -0
  52. package/dist/pipeline-DbX6rlo5.js +1 -0
  53. package/dist/project-Bw9UPIdx.js +1046 -0
  54. package/dist/project-C4GpaN-n.js +381 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-BP0u-D5m.js +519 -0
  58. package/dist/restore-CiYv-jxS.js +175 -0
  59. package/dist/rewrite-EsIw_lsq.js +803 -0
  60. package/dist/rich-DCUKl5HM.js +97 -0
  61. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  62. package/dist/run-BEcnBbu-.js +694 -0
  63. package/dist/runtime-DTMmliT1.js +64 -0
  64. package/dist/scan-worker.d.ts +62 -0
  65. package/dist/scan-worker.js +1 -0
  66. package/dist/scoped-BMYpSthj.js +44 -0
  67. package/dist/server-Da1Ot9IN.js +10485 -0
  68. package/dist/server-deBOq2vq.js +11823 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DJXzKoza.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-zr4jY59Y.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-B9V2KFwb.js +658 -0
  77. package/dist/t-DC-c9zE5.js +454 -0
  78. package/dist/test.d.ts +116 -0
  79. package/dist/test.js +319 -0
  80. package/dist/translate-CQnJfLjO.js +299 -0
  81. package/dist/ts-BaU5yOf0.js +4863 -0
  82. package/dist/types-CgUCkFU1.d.ts +707 -0
  83. package/dist/types-D3QGM8XO.js +146 -0
  84. package/dist/types-DsdWBG0b.d.ts +558 -0
  85. package/dist/types-DtvQ7UdD.js +1948 -0
  86. package/dist/types-wSh9fCBY.js +11 -0
  87. package/dist/units-CQ5Q99RC.js +150 -0
  88. package/dist/url-CRfCOhUI.js +172 -0
  89. package/dist/url-CsBkRaJy.js +225 -0
  90. package/dist/validate-BxpupdAf.js +2615 -0
  91. package/dist/validate-D6DJepZv.js +365 -0
  92. package/dist/version-Byqi2guX.js +28 -0
  93. package/dist/vite-scan-CT_vsp4g.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-g0iGy6CZ.js +651 -0
  97. package/dist/which-BOemZ5Q8.js +116 -0
  98. package/dist/worker-84U6uYB3.js +1656 -0
  99. package/package.json +97 -3
  100. package/schemas/catalog.json +57 -0
@@ -0,0 +1,1596 @@
1
+ import { o as isDiagnosticError } from "./types-D3QGM8XO.js";
2
+ import { t as pluralCategories } from "./validate-BxpupdAf.js";
3
+ import { n as printICU } from "./codec-DuvU2VQy.js";
4
+ import { l as langFromId, u as parseModule } from "./worker-84U6uYB3.js";
5
+ import { _ as keyName, a as attributeExpression, b as stringValue, c as cleanJsxText, d as isNode, f as jsString, g as jsxNameNodes, h as jsxName, i as analyzeModule, l as identifierName, m as jsxChildren, o as attributeString, p as jsxAttributes, s as childEntries, t as ModuleBuilder, u as inJsxChildren, v as memberParts, x as unwrap, y as objectProperties } from "./rewrite-EsIw_lsq.js";
6
+ import { extname } from "node:path";
7
+ import { createHash } from "node:crypto";
8
+ import { readFileSync } from "node:fs";
9
+ //#region src/cli/commands/migrate/formats.ts
10
+ /**
11
+ * Catalog formats of the libraries `vi18n migrate` converts from (INTERNALS
12
+ * §2.14): nested JSON flattened to keys, a YAML subset for vue-i18n (vi18n
13
+ * depends on no YAML parser, §6 D1), FormatJS's compiled message AST, and the
14
+ * id schemes of FormatJS and Lingui, so translations keyed by a generated id
15
+ * are found from the message text a call site holds. Every reader reports
16
+ * what it cannot read instead of guessing; nothing here executes a catalog.
17
+ */
18
+ /** 1-based line of an offset. */
19
+ function lineAt(text, offset) {
20
+ let line = 1;
21
+ for (let i = text.indexOf("\n"); i !== -1 && i < offset; i = text.indexOf("\n", i + 1)) line++;
22
+ return line;
23
+ }
24
+ /** `JSON.parse`, with the position of a syntax error turned into a line. */
25
+ function parseJson(text) {
26
+ const body = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
27
+ try {
28
+ return {
29
+ value: JSON.parse(body),
30
+ problem: null
31
+ };
32
+ } catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ const at = /position (\d+)/.exec(message);
35
+ return {
36
+ value: void 0,
37
+ problem: {
38
+ message,
39
+ line: at === null ? 0 : lineAt(body, Number(at[1]))
40
+ }
41
+ };
42
+ }
43
+ }
44
+ /** The line of each key's first occurrence in a JSON text (`"key":`), for diagnostics. */
45
+ function keyLines(text) {
46
+ const lines = /* @__PURE__ */ new Map();
47
+ const pattern = /"((?:[^"\\\n]|\\.)*)"\s*:/g;
48
+ for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {
49
+ let key;
50
+ try {
51
+ key = JSON.parse(`"${match[1]}"`);
52
+ } catch {
53
+ continue;
54
+ }
55
+ if (!lines.has(key)) lines.set(key, lineAt(text, match.index));
56
+ }
57
+ return lines;
58
+ }
59
+ /**
60
+ * Flattens a nested catalog: objects join their keys with `separator`,
61
+ * arrays use indexes (`list.0`), as i18next and vue-i18n address them.
62
+ * Leaves that are not strings are reported, never stringified.
63
+ */
64
+ function flattenTree(tree, text, separator = ".") {
65
+ const texts = /* @__PURE__ */ new Map();
66
+ const problems = [];
67
+ const lines = keyLines(text);
68
+ const lineOf = (key) => {
69
+ const last = key.lastIndexOf(separator);
70
+ return lines.get(key) ?? lines.get(last === -1 ? key : key.slice(last + 1)) ?? 0;
71
+ };
72
+ const visit = (value, key) => {
73
+ if (typeof value === "string") {
74
+ texts.set(key, {
75
+ value,
76
+ line: lineOf(key)
77
+ });
78
+ return;
79
+ }
80
+ if (Array.isArray(value)) {
81
+ value.forEach((item, index) => visit(item, key === "" ? String(index) : `${key}${separator}${index}`));
82
+ return;
83
+ }
84
+ if (typeof value === "object" && value !== null) {
85
+ for (const [name, item] of Object.entries(value)) visit(item, key === "" ? name : `${key}${separator}${name}`);
86
+ return;
87
+ }
88
+ if (value === null) return;
89
+ problems.push({
90
+ message: `"${key}" holds ${typeof value} ${JSON.stringify(value)}, not a string; it was skipped`,
91
+ line: lineOf(key)
92
+ });
93
+ };
94
+ if (typeof tree !== "object" || tree === null || Array.isArray(tree)) {
95
+ problems.push({
96
+ message: "a catalog is an object of messages",
97
+ line: 1
98
+ });
99
+ return {
100
+ texts,
101
+ problems
102
+ };
103
+ }
104
+ visit(tree, "");
105
+ return {
106
+ texts,
107
+ problems
108
+ };
109
+ }
110
+ var YamlError = class extends Error {
111
+ line;
112
+ constructor(message, line) {
113
+ super(message);
114
+ this.line = line;
115
+ }
116
+ };
117
+ /**
118
+ * Block mappings and sequences of scalars: plain, quoted (single and double,
119
+ * also over several lines) and block scalars (`|`, `>`, with chomping and an
120
+ * indentation indicator); comments; `{}` and `[]`. Anchors, aliases, tags,
121
+ * non-empty flow collections and complex keys are errors, since guessing
122
+ * would change a message silently. Plain scalars stay strings (`404`, `yes`):
123
+ * a message catalog has nothing else.
124
+ */
125
+ function parseYaml(text) {
126
+ const lines = (text.charCodeAt(0) === 65279 ? text.slice(1) : text).split("\n").map((raw, index) => {
127
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
128
+ const indent = /^ */.exec(line)?.[0].length ?? 0;
129
+ return {
130
+ no: index + 1,
131
+ indent,
132
+ text: line.slice(indent),
133
+ raw: line
134
+ };
135
+ });
136
+ let at = 0;
137
+ const blank = (line) => line.text === "" || line.text.startsWith("#");
138
+ const skipBlank = () => {
139
+ while (at < lines.length && blank(lines[at])) at++;
140
+ };
141
+ skipBlank();
142
+ if (at < lines.length && /^---\s*(?:#.*)?$/.test(lines[at].raw)) {
143
+ at++;
144
+ skipBlank();
145
+ }
146
+ const fail = (message, line) => {
147
+ throw new YamlError(message, line?.no ?? 0);
148
+ };
149
+ /** A plain scalar's text with a trailing ` #comment` removed. */
150
+ const plain = (value) => {
151
+ const comment = /(?:^|\s)#/.exec(value);
152
+ return (comment === null ? value : value.slice(0, comment.index)).trim();
153
+ };
154
+ const checkScalarStart = (value, line) => {
155
+ if (/^[&*!]/.test(value)) fail(`anchors, aliases and tags are not supported (${value.split(/\s/)[0]})`, line);
156
+ if (/^[[{]/.test(value) && !/^(?:\{\s*\}|\[\s*\])\s*(?:#.*)?$/.test(value)) fail("flow collections are not supported: write the entries as a block", line);
157
+ if (/^[|>]/.test(value) === false && /^[?%@`]/.test(value)) fail(`a scalar cannot start with ${value[0]}: quote it`, line);
158
+ };
159
+ /** A quoted scalar starting at `value` on `line`, possibly continued on later lines; folds line breaks. */
160
+ const quoted = (value, line) => {
161
+ const quote = value[0];
162
+ let source = value.slice(1);
163
+ let out = "";
164
+ let current = line;
165
+ for (;;) {
166
+ for (let i = 0; i < source.length; i++) {
167
+ const c = source[i];
168
+ if (quote === "'" && c === "'") {
169
+ if (source[i + 1] === "'") {
170
+ out += "'";
171
+ i++;
172
+ continue;
173
+ }
174
+ return {
175
+ text: out,
176
+ rest: source.slice(i + 1)
177
+ };
178
+ }
179
+ if (quote === "\"" && c === "\"") return {
180
+ text: out,
181
+ rest: source.slice(i + 1)
182
+ };
183
+ if (quote === "\"" && c === "\\") {
184
+ const next = source[i + 1];
185
+ if (next === void 0) {
186
+ source = "";
187
+ out += "\0";
188
+ break;
189
+ }
190
+ const simple = {
191
+ n: "\n",
192
+ t: " ",
193
+ r: "\r",
194
+ "0": "\0",
195
+ "\"": "\"",
196
+ "\\": "\\",
197
+ "/": "/",
198
+ " ": " ",
199
+ b: "\b",
200
+ f: "\f",
201
+ e: "\x1B",
202
+ N: "…",
203
+ _: "\xA0"
204
+ };
205
+ if (next in simple) {
206
+ out += simple[next];
207
+ i++;
208
+ continue;
209
+ }
210
+ const width = next === "x" ? 2 : next === "u" ? 4 : next === "U" ? 8 : 0;
211
+ if (width === 0) fail(`unknown escape \\${next}`, current);
212
+ const hex = source.slice(i + 2, i + 2 + width);
213
+ if (!new RegExp(`^[0-9a-fA-F]{${width}}$`).test(hex)) fail(`malformed escape \\${next}${hex}`, current);
214
+ out += String.fromCodePoint(Number.parseInt(hex, 16));
215
+ i += 1 + width;
216
+ continue;
217
+ }
218
+ out += c;
219
+ }
220
+ at++;
221
+ if (at >= lines.length) fail("unterminated quoted scalar", current);
222
+ let breaks = 0;
223
+ while (at < lines.length && lines[at].text === "") {
224
+ breaks++;
225
+ at++;
226
+ }
227
+ if (at >= lines.length) fail("unterminated quoted scalar", current);
228
+ current = lines[at];
229
+ if (out.endsWith("\0")) out = out.slice(0, -1);
230
+ else {
231
+ out = out.replace(/[ \t]+$/, "");
232
+ out += breaks > 0 ? "\n".repeat(breaks) : " ";
233
+ }
234
+ source = current.text.replace(/^\s+/, "");
235
+ }
236
+ };
237
+ /** A block scalar whose header (`|`, `>-`, `|2`…) is `header`; the content follows `line`. */
238
+ const blockScalar = (header, parentIndent, line) => {
239
+ const m = /^([|>])([+-]?)([1-9]?)([+-]?)\s*(?:#.*)?$/.exec(header);
240
+ if (m === null) fail(`malformed block scalar header ${header}`, line);
241
+ const [, style, chompA, digit, chompB] = m;
242
+ const chomp = chompA || chompB;
243
+ at++;
244
+ let indent = digit === "" ? -1 : parentIndent + Number(digit);
245
+ const content = [];
246
+ while (at < lines.length) {
247
+ const current = lines[at];
248
+ if (current.raw.trim() === "") {
249
+ content.push("");
250
+ at++;
251
+ continue;
252
+ }
253
+ if (indent === -1) {
254
+ if (current.indent <= parentIndent) break;
255
+ indent = current.indent;
256
+ }
257
+ if (current.indent < indent) break;
258
+ content.push(current.raw.slice(indent));
259
+ at++;
260
+ }
261
+ let trailing = 0;
262
+ while (content.length > 0 && content[content.length - 1] === "") {
263
+ content.pop();
264
+ trailing++;
265
+ }
266
+ let value;
267
+ if (style === "|") value = content.join("\n");
268
+ else {
269
+ value = "";
270
+ for (let i = 0; i < content.length; i++) {
271
+ const piece = content[i];
272
+ if (i > 0) {
273
+ const previous = content[i - 1];
274
+ if (piece === "") value += "\n";
275
+ else if (previous !== "") value += /^\s/.test(piece) || /^\s/.test(previous) ? "\n" : " ";
276
+ }
277
+ value += piece;
278
+ }
279
+ }
280
+ if (content.length === 0) return "";
281
+ if (chomp === "-") return value;
282
+ if (chomp === "+") return value + "\n".repeat(trailing + 1);
283
+ return value + "\n";
284
+ };
285
+ /** A plain scalar starting on `line`, with its continuation lines (more indented than `parentIndent`). */
286
+ const plainScalar = (first, parentIndent) => {
287
+ let value = plain(first);
288
+ if (/(?:^|\s)#/.test(first)) {
289
+ at++;
290
+ return value;
291
+ }
292
+ at++;
293
+ let breaks = 0;
294
+ while (at < lines.length) {
295
+ const current = lines[at];
296
+ if (current.text === "") {
297
+ breaks++;
298
+ at++;
299
+ continue;
300
+ }
301
+ if (current.indent <= parentIndent || current.text.startsWith("#")) break;
302
+ if (/^[^"'#][^:#]*:(?:\s|$)/.test(current.text) || current.text.startsWith("- ")) break;
303
+ value += breaks > 0 ? "\n".repeat(breaks) : " ";
304
+ breaks = 0;
305
+ value += plain(current.text);
306
+ const ends = /(?:^|\s)#/.test(current.text);
307
+ at++;
308
+ if (ends) break;
309
+ }
310
+ return value;
311
+ };
312
+ /** The value after `key:` or `- ` (`rest`), at a node whose own indentation is `indent`. */
313
+ const valueAfter = (rest, indent, line) => {
314
+ const value = rest.trim();
315
+ if (value === "" || value.startsWith("#")) {
316
+ at++;
317
+ skipBlank();
318
+ const next = lines[at];
319
+ if (next === void 0 || next.indent <= indent) {
320
+ if (next !== void 0 && next.indent === indent && next.text.startsWith("- ")) return node(indent);
321
+ return null;
322
+ }
323
+ return node(next.indent);
324
+ }
325
+ checkScalarStart(value, line);
326
+ if (value === "{}" || /^\{\s*\}/.test(value)) {
327
+ at++;
328
+ return {};
329
+ }
330
+ if (/^\[\s*\]/.test(value)) {
331
+ at++;
332
+ return [];
333
+ }
334
+ if (value.startsWith("|") || value.startsWith(">")) return blockScalar(value, indent, line);
335
+ if (value.startsWith("\"") || value.startsWith("'")) {
336
+ const result = quoted(value, line);
337
+ if (plain(result.rest) !== "") fail(`unexpected text after a quoted scalar: ${result.rest.trim()}`, lines[at]);
338
+ at++;
339
+ return result.text;
340
+ }
341
+ if (value === "~" || value === "null") {
342
+ at++;
343
+ return null;
344
+ }
345
+ return plainScalar(value, indent);
346
+ };
347
+ /** A mapping key at the start of `text`: its name and what follows the `:`. */
348
+ const keyOf = (text, line) => {
349
+ if (text.startsWith("\"") || text.startsWith("'")) {
350
+ const saved = at;
351
+ const result = quoted(text, line);
352
+ if (at !== saved) fail("a key cannot span lines", line);
353
+ const rest = result.rest.replace(/^\s*/, "");
354
+ if (!rest.startsWith(":")) return null;
355
+ return {
356
+ key: result.text,
357
+ rest: rest.slice(1)
358
+ };
359
+ }
360
+ if (text.startsWith("? ")) fail("complex keys (? …) are not supported", line);
361
+ const m = /^([^#][^:]*?)\s*:(?:\s|$)/.exec(text);
362
+ if (m === null) return null;
363
+ return {
364
+ key: m[1],
365
+ rest: text.slice(m[0].length)
366
+ };
367
+ };
368
+ /** A block node whose lines are indented by exactly `indent`. */
369
+ const node = (indent) => {
370
+ skipBlank();
371
+ const first = lines[at];
372
+ if (first === void 0) return null;
373
+ if (first.text.startsWith("- ") || first.text === "-") {
374
+ const out = [];
375
+ while (at < lines.length) {
376
+ skipBlank();
377
+ const line = lines[at];
378
+ if (line === void 0 || line.indent < indent) break;
379
+ if (line.indent > indent) fail("unexpected indentation", line);
380
+ if (!(line.text.startsWith("- ") || line.text === "-")) break;
381
+ const rest = line.text.slice(1);
382
+ const inner = rest.trimStart();
383
+ if (inner !== "" && !/^["'|>]/.test(inner) && keyOf(inner, line) !== null) {
384
+ const shift = line.indent + 1 + (rest.length - inner.length);
385
+ lines[at] = {
386
+ ...line,
387
+ indent: shift,
388
+ text: inner
389
+ };
390
+ out.push(node(shift));
391
+ continue;
392
+ }
393
+ out.push(valueAfter(rest, indent, line));
394
+ }
395
+ return out;
396
+ }
397
+ const out = {};
398
+ while (at < lines.length) {
399
+ skipBlank();
400
+ const line = lines[at];
401
+ if (line === void 0 || line.indent < indent) break;
402
+ if (line.indent > indent) fail("unexpected indentation", line);
403
+ if (line.text === "..." || line.text === "---") fail("only one YAML document is supported", line);
404
+ const entry = keyOf(line.text, line);
405
+ if (entry === null) fail(`expected "key: value", found ${line.text}`, line);
406
+ const { key, rest } = entry;
407
+ if (Object.hasOwn(out, key)) fail(`duplicate key ${key}`, line);
408
+ out[key] = valueAfter(rest, indent, line);
409
+ }
410
+ return out;
411
+ };
412
+ try {
413
+ skipBlank();
414
+ if (at >= lines.length) return {
415
+ value: {},
416
+ problem: null
417
+ };
418
+ const value = node(lines[at].indent);
419
+ skipBlank();
420
+ if (at < lines.length) fail("unexpected indentation", lines[at]);
421
+ return {
422
+ value,
423
+ problem: null
424
+ };
425
+ } catch (error) {
426
+ if (error instanceof YamlError) return {
427
+ value: void 0,
428
+ problem: {
429
+ message: error.message,
430
+ line: error.line
431
+ }
432
+ };
433
+ throw error;
434
+ }
435
+ }
436
+ /** Line numbers of YAML keys (`key:` at any depth), for diagnostics. */
437
+ function yamlKeyLines(text) {
438
+ const lines = /* @__PURE__ */ new Map();
439
+ text.split("\n").forEach((line, index) => {
440
+ const m = /^\s*(?:-\s+)?["']?([^"':#]+?)["']?\s*:(?:\s|$)/.exec(line);
441
+ if (m !== null && !lines.has(m[1])) lines.set(m[1], index + 1);
442
+ });
443
+ return lines;
444
+ }
445
+ const KEYWORD_STYLES = /* @__PURE__ */ new Set([
446
+ "integer",
447
+ "percent",
448
+ "short",
449
+ "medium",
450
+ "long",
451
+ "full"
452
+ ]);
453
+ /**
454
+ * A message compiled by `formatjs compile --ast` (an array of typed
455
+ * elements) as vi18n's AST; null when an element is not one FormatJS writes.
456
+ */
457
+ function formatjsAstToIcu(elements) {
458
+ if (!Array.isArray(elements)) return null;
459
+ const out = [];
460
+ for (const element of elements) {
461
+ if (typeof element !== "object" || element === null) return null;
462
+ const value = element.value;
463
+ switch (element.type) {
464
+ case 0:
465
+ if (typeof value !== "string") return null;
466
+ if (value !== "") out.push({
467
+ type: "literal",
468
+ value
469
+ });
470
+ break;
471
+ case 1:
472
+ out.push({
473
+ type: "argument",
474
+ name: value
475
+ });
476
+ break;
477
+ case 2:
478
+ case 3:
479
+ case 4: {
480
+ const style = formatjsStyle(element.style);
481
+ if (style === void 0) return null;
482
+ out.push({
483
+ type: element.type === 2 ? "number" : element.type === 3 ? "date" : "time",
484
+ name: value,
485
+ style,
486
+ options: null
487
+ });
488
+ break;
489
+ }
490
+ case 5:
491
+ case 6: {
492
+ const options = [];
493
+ const raw = element.options;
494
+ if (typeof raw !== "object" || raw === null) return null;
495
+ for (const [key, option] of Object.entries(raw)) {
496
+ const nested = formatjsAstToIcu(option?.value);
497
+ if (nested === null) return null;
498
+ options.push({
499
+ key,
500
+ value: nested
501
+ });
502
+ }
503
+ if (element.type === 5) out.push({
504
+ type: "select",
505
+ name: value,
506
+ options
507
+ });
508
+ else out.push({
509
+ type: "plural",
510
+ pluralType: element.pluralType === "ordinal" ? "ordinal" : "cardinal",
511
+ name: value,
512
+ offset: typeof element.offset === "number" ? element.offset : 0,
513
+ options
514
+ });
515
+ break;
516
+ }
517
+ case 7:
518
+ out.push({ type: "pound" });
519
+ break;
520
+ case 8: {
521
+ const children = formatjsAstToIcu(element.children);
522
+ if (children === null) return null;
523
+ out.push({
524
+ type: "tag",
525
+ name: value,
526
+ children
527
+ });
528
+ break;
529
+ }
530
+ default: return null;
531
+ }
532
+ }
533
+ return out;
534
+ }
535
+ function formatjsStyle(style) {
536
+ if (style === null || style === void 0) return null;
537
+ if (typeof style === "string") {
538
+ if (style.startsWith("::")) return {
539
+ kind: "skeleton",
540
+ value: style.slice(2).trim()
541
+ };
542
+ return KEYWORD_STYLES.has(style) ? {
543
+ kind: "keyword",
544
+ value: style
545
+ } : void 0;
546
+ }
547
+ if (typeof style !== "object") return void 0;
548
+ const skeleton = style;
549
+ if (typeof skeleton.pattern === "string") return {
550
+ kind: "skeleton",
551
+ value: skeleton.pattern
552
+ };
553
+ if (Array.isArray(skeleton.tokens)) return {
554
+ kind: "skeleton",
555
+ value: skeleton.tokens.map((token) => [token.stem, ...token.options ?? []].join("/")).join(" ")
556
+ };
557
+ }
558
+ /**
559
+ * The id `babel-plugin-formatjs` / `@formatjs/ts-transformer` give a
560
+ * descriptor without one, with the default `idInterpolationPattern`
561
+ * (`[sha512:contenthash:base64:6]` over `defaultMessage#description`).
562
+ */
563
+ function formatjsId(defaultMessage, description) {
564
+ const content = description === null || description === "" ? defaultMessage : `${defaultMessage}#${description}`;
565
+ return createHash("sha512").update(content).digest("base64").slice(0, 6);
566
+ }
567
+ /** Lingui's `generateMessageId(message, context)` (v4+): sha256 over `message␟context`, base64, 6 characters. */
568
+ function linguiId(message, context) {
569
+ return createHash("sha256").update(message + "" + (context ?? "")).digest("base64").slice(0, 6);
570
+ }
571
+ /** Appends literal text, merging with a preceding literal. */
572
+ function pushLiteral(nodes, value) {
573
+ if (value === "") return;
574
+ const last = nodes[nodes.length - 1];
575
+ if (last?.type === "literal") last.value += value;
576
+ else nodes.push({
577
+ type: "literal",
578
+ value
579
+ });
580
+ }
581
+ //#endregion
582
+ //#region src/cli/commands/migrate/react.ts
583
+ /**
584
+ * Rich text for the React codemods (i18next `<Trans>`, react-intl rich
585
+ * values, Lingui `<Trans>`): numbered tags (`<0>…</0>`, which vi18n reads as
586
+ * text since a tag name starts with a letter) are renamed after the element
587
+ * they stand for, in the source and every translation alike, and each
588
+ * element becomes a `rich()` handler that wraps the translated children.
589
+ */
590
+ function newNaming() {
591
+ return {
592
+ byPath: /* @__PURE__ */ new Map(),
593
+ sourcePaths: /* @__PURE__ */ new Set()
594
+ };
595
+ }
596
+ function baseName(element, path) {
597
+ if (element === "html") return path.replace(/^html:/, "");
598
+ if (element === null || element.type !== "JSXElement") return "c" + path.replace(/\W/g, "_");
599
+ const name = jsxName(element).split(/[.:]/).pop().replace(/^[A-Z]/, (c) => c.toLowerCase()).replace(/[^A-Za-z0-9_-]/g, "");
600
+ return /^[A-Za-z]/.test(name) ? name : `c${name}`;
601
+ }
602
+ /** Renames the tags of a message (source first, then translations) through `naming`. */
603
+ function renameTags(nodes, naming, pathOf, elementAt, source) {
604
+ const taken = new Set(naming.byPath.values());
605
+ const walk = (list, parent) => list.map((node) => {
606
+ if (node.type === "plural" || node.type === "select") return {
607
+ ...node,
608
+ options: node.options.map((o) => ({
609
+ key: o.key,
610
+ value: walk(o.value, parent)
611
+ }))
612
+ };
613
+ if (node.type !== "tag") return node;
614
+ const path = pathOf(node.name, parent);
615
+ let name = node.name;
616
+ if (path !== null) {
617
+ const known = naming.byPath.get(path);
618
+ if (known !== void 0) name = known;
619
+ else {
620
+ const base = baseName(elementAt(path), path);
621
+ name = base;
622
+ for (let n = 2; taken.has(name); n++) name = `${base}${n}`;
623
+ naming.byPath.set(path, name);
624
+ taken.add(name);
625
+ }
626
+ if (source) naming.sourcePaths.add(path);
627
+ }
628
+ return {
629
+ type: "tag",
630
+ name,
631
+ children: node.children === null ? null : walk(node.children, path ?? parent)
632
+ };
633
+ });
634
+ return walk(nodes, null);
635
+ }
636
+ /** This site's handler per tag name, or the paths it cannot map. */
637
+ function siteHandlers(naming, elementAt) {
638
+ const handlers = /* @__PURE__ */ new Map();
639
+ const missing = [];
640
+ for (const path of naming.sourcePaths) {
641
+ const name = naming.byPath.get(path);
642
+ const element = elementAt(path);
643
+ if (element === "html") handlers.set(name, {
644
+ element: null,
645
+ html: path.replace(/^html:/, "")
646
+ });
647
+ else if (element !== null && element.type === "JSXElement") handlers.set(name, {
648
+ element,
649
+ html: null
650
+ });
651
+ else missing.push(path.replace(/^html:/, ""));
652
+ }
653
+ return {
654
+ handlers,
655
+ missing
656
+ };
657
+ }
658
+ /** `ReactNode`, imported as a type in TSX so handlers and `rich<ReactNode>()` type-check. */
659
+ function reactTypes(builder) {
660
+ return { node: builder.typeImport("react", "ReactNode") };
661
+ }
662
+ /** A handler wrapping the translated children in the element's opening tag. */
663
+ function elementHandler(handler, selfClosing, r, types) {
664
+ const param = types === null ? "children" : `children: ${types.node}`;
665
+ if (handler.element === null) {
666
+ const tag = handler.html;
667
+ return selfClosing ? `() => <${tag} />` : `(${param}) => <${tag}>{children}</${tag}>`;
668
+ }
669
+ const element = handler.element;
670
+ const opening = element.openingElement;
671
+ if (selfClosing) return `() => ${r.src(opening.start, opening.end).replace(/\s*>$/, "").replace(/\s*\/$/, "")} />`;
672
+ return `(${param}) => ${r.src(opening.start, opening.end).replace(/\s*\/>$/, ">")}{children}</${jsxName(element)}>`;
673
+ }
674
+ /** Whether a message's tag is written self-closing (`<br/>`, `<0/>`). */
675
+ function selfClosingTags(nodes, out = /* @__PURE__ */ new Set()) {
676
+ for (const node of nodes) if (node.type === "tag") {
677
+ if (node.children === null) out.add(node.name);
678
+ else selfClosingTags(node.children, out);
679
+ } else if (node.type === "plural" || node.type === "select") for (const option of node.options) selfClosingTags(option.value, out);
680
+ return out;
681
+ }
682
+ /**
683
+ * Annotates the first parameter of handlers in a values object
684
+ * (`(chunks) => …` → `(chunks: ReactNode) => …`): react-intl's types gave it
685
+ * a type from context, vi18n's values are `unknown`, so under
686
+ * `noImplicitAny` the handler would no longer type-check.
687
+ */
688
+ function annotateHandlers(builder, values, typeName) {
689
+ const props = objectProperties(values);
690
+ if (props === null) return;
691
+ const text = builder.text;
692
+ for (const property of props.order) {
693
+ if (property.type === "SpreadElement") continue;
694
+ const fn = unwrap(property.value);
695
+ if (fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression") continue;
696
+ const param = fn.params[0];
697
+ if (param === void 0 || param.type !== "Identifier" || isNode(param.typeAnnotation)) continue;
698
+ const name = param.name;
699
+ const parenthesized = text.slice(fn.start, param.start).includes("(");
700
+ builder.replace({
701
+ start: param.start,
702
+ end: param.start + name.length
703
+ }, parenthesized ? `${name}: ${typeName}` : `(${name}: ${typeName})`);
704
+ }
705
+ }
706
+ /** `rich<ReactNode>(message, { … })`. */
707
+ function richCall(r, key, entries, types) {
708
+ const typeArgument = types === null ? "" : `<${types.node}>`;
709
+ return `${r.fn.rich}${typeArgument}(${r.message(key)}, { ${entries.join(", ")} })`;
710
+ }
711
+ /** `name: value`, or the shorthand when the value is the name. */
712
+ function entry(name, code) {
713
+ if (code === name) return name;
714
+ return `${/^[A-Za-z_$][\w$]*$/.test(name) || /^(?:0|[1-9]\d*)$/.test(name) ? name : jsString(name)}: ${code}`;
715
+ }
716
+ //#endregion
717
+ //#region src/cli/commands/migrate/i18next.ts
718
+ /**
719
+ * `vi18n migrate --from i18next` (INTERNALS §2.14): i18next and
720
+ * react-i18next.
721
+ *
722
+ * Catalogs are i18next JSON (v4): nested keys joined by `.`, one file per
723
+ * locale or one per namespace (`<locale>/<ns>.json`), plural suffixes
724
+ * (`_zero`, `_one`, …, `_other`, `_ordinal_*`, v3's `_plural`) folded into
725
+ * one ICU plural, `_<context>` variants into a select on `context`, `{{x}}`
726
+ * into `{x}` (`{{x, number}}`, `datetime`, `currency(EUR)` formatted), and
727
+ * `$t(key)` nesting inlined. Call sites: `t("key", options)` from
728
+ * `useTranslation`, `i18next.t`, a `t` prop; `<Trans>` (components from the
729
+ * `components` prop or the children, as react-i18next maps `<0>…</0>`)
730
+ * becomes `rich()`. `useTranslation` bindings, imports and `<I18nextProvider>`
731
+ * go once nothing else uses them; the rest is reported.
732
+ */
733
+ const MODULE_EXTENSIONS = /* @__PURE__ */ new Set([
734
+ ".js",
735
+ ".jsx",
736
+ ".mjs",
737
+ ".cjs",
738
+ ".ts",
739
+ ".tsx",
740
+ ".mts",
741
+ ".cts"
742
+ ]);
743
+ const PLURAL_SUFFIX = /_(zero|one|two|few|many|other)$/;
744
+ const ORDINAL_SUFFIX = /_ordinal_(zero|one|two|few|many|other)$/;
745
+ const CATEGORIES = [
746
+ "zero",
747
+ "one",
748
+ "two",
749
+ "few",
750
+ "many",
751
+ "other"
752
+ ];
753
+ const DEFAULT_NS = "translation";
754
+ /** react-i18next's `transKeepBasicHtmlNodesFor`: rendered as elements when no component maps them. */
755
+ const BASIC_HTML = /* @__PURE__ */ new Set([
756
+ "br",
757
+ "strong",
758
+ "i",
759
+ "p"
760
+ ]);
761
+ /** `t()` options that change what is looked up or returned, with no vi18n counterpart. */
762
+ const UNSUPPORTED_OPTIONS = /* @__PURE__ */ new Set([
763
+ "lng",
764
+ "lngs",
765
+ "fallbackLng",
766
+ "returnObjects",
767
+ "returnDetails",
768
+ "joinArrays",
769
+ "postProcess",
770
+ "interpolation",
771
+ "replace",
772
+ "skipInterpolation"
773
+ ]);
774
+ /** `t()` options that only steer the lookup; the rest are values. */
775
+ const LOOKUP_OPTIONS = /* @__PURE__ */ new Set([
776
+ "defaultValue",
777
+ "ns",
778
+ "keyPrefix",
779
+ "ordinal"
780
+ ]);
781
+ function isOldSource(source) {
782
+ return /^(?:react-|next-)?i18next(?:\/|$)/.test(source) || source.startsWith("i18next-");
783
+ }
784
+ /** Whether a key has plural or ordinal forms (`key_one`, `key_ordinal_two`, v3 `key_plural`). */
785
+ function hasForms(texts, key) {
786
+ return CATEGORIES.some((c) => texts.has(`${key}_${c}`) || texts.has(`${key}_ordinal_${c}`)) || texts.has(`${key}_plural`);
787
+ }
788
+ /** The base key of a plural or ordinal form (`item_one` → `item`). */
789
+ function baseKey(key) {
790
+ return key.replace(ORDINAL_SUFFIX, "").replace(PLURAL_SUFFIX, "").replace(/_plural$/, "");
791
+ }
792
+ /** One `{{…}}` as an ICU node: `count` in a plural branch is `#`. */
793
+ function interpolationNode(inner, inPlural) {
794
+ const body = inner.trim().replace(/^-\s*/, "");
795
+ const comma = body.indexOf(",");
796
+ const name = (comma === -1 ? body : body.slice(0, comma)).trim();
797
+ const format = comma === -1 ? null : body.slice(comma + 1).trim();
798
+ if (!/^[A-Za-z_$][\w$]*$/.test(name)) return { error: `{{${inner}}} reads a nested value, which an ICU argument cannot name` };
799
+ if (format === null) return { node: inPlural && name === "count" ? { type: "pound" } : {
800
+ type: "argument",
801
+ name
802
+ } };
803
+ const m = /^(\w+)(?:\((.*)\))?$/.exec(format);
804
+ const kind = m?.[1]?.toLowerCase();
805
+ const args = m?.[2]?.trim() ?? "";
806
+ if (kind === "number" && args === "") return { node: {
807
+ type: "number",
808
+ name,
809
+ style: null,
810
+ options: null
811
+ } };
812
+ if (kind === "datetime" && args === "") return { node: {
813
+ type: "date",
814
+ name,
815
+ style: null,
816
+ options: null
817
+ } };
818
+ if (kind === "currency") {
819
+ const code = /^(?:currency\s*:\s*)?["']?([A-Za-z]{3})["']?$/.exec(args)?.[1];
820
+ if (code !== void 0) return { node: {
821
+ type: "number",
822
+ name,
823
+ style: {
824
+ kind: "skeleton",
825
+ value: `currency/${code.toUpperCase()}`
826
+ },
827
+ options: null
828
+ } };
829
+ }
830
+ return { error: `{{${inner.trim()}}} uses the ${format} format, which has no ICU counterpart: format the value with fmt.* and pass the string` };
831
+ }
832
+ function createI18nextAdapter() {
833
+ /** locale → namespace → key → text. */
834
+ const catalogs = /* @__PURE__ */ new Map();
835
+ const namings = /* @__PURE__ */ new Map();
836
+ /** `ns:key` of every source text a call site names, its plural and context forms and nested `$t()` included. */
837
+ const referenced = /* @__PURE__ */ new Set();
838
+ /** Marks a key family (`key`, `key_one`, `key_male_other`…) and what its texts nest as named by code. */
839
+ const reference = (ns, key, depth = 0) => {
840
+ if (depth > 8) return;
841
+ for (const [k, text] of namespaces(sourceLocale).get(ns) ?? []) {
842
+ if (k !== key && !k.startsWith(key + "_")) continue;
843
+ if (referenced.has(`${ns}:${k}`)) continue;
844
+ referenced.add(`${ns}:${k}`);
845
+ for (const m of text.value.matchAll(/\$t\(([^(),]*)/g)) {
846
+ const target = m[1].trim();
847
+ const colon = target.indexOf(":");
848
+ if (colon !== -1 && namespaces(sourceLocale).has(target.slice(0, colon))) reference(target.slice(0, colon), target.slice(colon + 1), depth + 1);
849
+ else reference(ns, target, depth + 1);
850
+ }
851
+ }
852
+ };
853
+ let sourceLocale = "";
854
+ let defaultNs = null;
855
+ const namespaces = (locale) => catalogs.get(locale) ?? /* @__PURE__ */ new Map();
856
+ const textOf = (locale, ns, key) => namespaces(locale).get(ns)?.get(key);
857
+ /** Whether the source locale has `key` itself or one of its plural forms. */
858
+ const hasKey = (ns, key) => {
859
+ const texts = namespaces(sourceLocale).get(ns);
860
+ if (texts === void 0) return false;
861
+ return texts.has(key) || hasForms(texts, key);
862
+ };
863
+ /** The namespace holding `key`: the call's own, the default, or the only one that has it. */
864
+ const findNamespace = (key, preferred) => {
865
+ if (preferred !== null) return hasKey(preferred, key) ? preferred : null;
866
+ if (defaultNs !== null && hasKey(defaultNs, key)) return defaultNs;
867
+ const found = [...namespaces(sourceLocale).keys()].filter((ns) => hasKey(ns, key));
868
+ return found.length === 1 ? found[0] : null;
869
+ };
870
+ const messageKey = (ns, key) => ns === defaultNs ? key : `${ns}:${key}`;
871
+ /** Whether a source key only has plural forms, so a call must pass `count`. */
872
+ const isPlural = (ns, key) => {
873
+ const texts = namespaces(sourceLocale).get(ns);
874
+ return texts !== void 0 && !texts.has(key) && hasForms(texts, key);
875
+ };
876
+ /** `{{x}}`, `$t(…)` and, for `<Trans>`, `<0>…</0>` of one text as ICU nodes. */
877
+ const convert = (raw, locale, ns, tags, inPlural, depth = 0) => {
878
+ const root = [];
879
+ const stack = [];
880
+ const current = () => stack.length === 0 ? root : stack[stack.length - 1].nodes;
881
+ const token = /\{\{(.*?)\}\}|\$t\(([^()]*)\)|<(\/?)([A-Za-z0-9_-]+)\s*(\/?)>/g;
882
+ let at = 0;
883
+ for (let m = token.exec(raw); m !== null; m = token.exec(raw)) {
884
+ const [whole, interpolation, nested, closing, tag, selfClosing] = m;
885
+ if (tag !== void 0 && !tags) continue;
886
+ pushLiteral(current(), raw.slice(at, m.index));
887
+ at = m.index + whole.length;
888
+ if (interpolation !== void 0) {
889
+ const node = interpolationNode(interpolation, inPlural);
890
+ if ("error" in node) return node;
891
+ current().push(node.node);
892
+ } else if (nested !== void 0) {
893
+ if (depth > 8) return { error: "$t() nests deeper than 8 levels" };
894
+ if (nested.includes(",")) return { error: `$t(${nested}) passes options, which cannot be inlined` };
895
+ let target = nested.trim();
896
+ let targetNs = ns;
897
+ const colon = target.indexOf(":");
898
+ if (colon !== -1 && namespaces(locale).has(target.slice(0, colon))) {
899
+ targetNs = target.slice(0, colon);
900
+ target = target.slice(colon + 1);
901
+ }
902
+ const text = textOf(locale, targetNs, target);
903
+ if (text === void 0) return { error: `$t(${nested}) names no message in ${locale}` };
904
+ const inner = convert(text.value, locale, targetNs, tags, inPlural, depth + 1);
905
+ if ("error" in inner) return inner;
906
+ for (const node of inner.nodes) if (node.type === "literal") pushLiteral(current(), node.value);
907
+ else current().push(node);
908
+ } else if (closing === "/") {
909
+ const open = stack.pop();
910
+ if (open === void 0 || open.name !== tag) return { error: `</${tag}> closes no <${tag}>` };
911
+ current().push({
912
+ type: "tag",
913
+ name: open.name,
914
+ children: open.nodes
915
+ });
916
+ } else if (selfClosing === "/") current().push({
917
+ type: "tag",
918
+ name: tag,
919
+ children: null
920
+ });
921
+ else stack.push({
922
+ name: tag,
923
+ nodes: []
924
+ });
925
+ }
926
+ if (stack.length > 0) return { error: `<${stack[stack.length - 1].name}> is never closed` };
927
+ pushLiteral(current(), raw.slice(at));
928
+ return { nodes: root };
929
+ };
930
+ /** One key's text, or its plural forms as an ICU plural. */
931
+ const branch = (locale, ns, key, shape) => {
932
+ const texts = namespaces(locale).get(ns);
933
+ if (texts === void 0) return null;
934
+ if (shape.count) {
935
+ const categories = pluralCategories(locale)[shape.ordinal ? "ordinal" : "cardinal"];
936
+ const forms = [];
937
+ for (const category of CATEGORIES) {
938
+ const formKey = shape.ordinal ? `${key}_ordinal_${category}` : `${key}_${category}`;
939
+ const text = texts.get(formKey);
940
+ if (text === void 0) continue;
941
+ const converted = convert(text.value, locale, ns, shape.tags, true);
942
+ if ("error" in converted) return { error: `${formKey}: ${converted.error}` };
943
+ forms.push({
944
+ key: category === "zero" && !categories.includes("zero") ? "=0" : category,
945
+ value: converted.nodes
946
+ });
947
+ }
948
+ const legacy = texts.get(`${key}_plural`);
949
+ if (forms.length === 0 && legacy !== void 0 && !shape.ordinal) for (const [category, text] of [["one", texts.get(key)], ["other", legacy]]) {
950
+ if (text === void 0) continue;
951
+ const converted = convert(text.value, locale, ns, shape.tags, true);
952
+ if ("error" in converted) return converted;
953
+ forms.push({
954
+ key: category,
955
+ value: converted.nodes
956
+ });
957
+ }
958
+ if (forms.length > 0) {
959
+ if (!forms.some((f) => f.key === "other")) {
960
+ const plain = texts.get(key);
961
+ if (plain === void 0) return { error: `${key} has plural forms but no ${key}_other` };
962
+ const converted = convert(plain.value, locale, ns, shape.tags, true);
963
+ if ("error" in converted) return converted;
964
+ forms.push({
965
+ key: "other",
966
+ value: converted.nodes
967
+ });
968
+ }
969
+ const order = ["=0", ...CATEGORIES];
970
+ forms.sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key));
971
+ return { nodes: [{
972
+ type: "plural",
973
+ pluralType: shape.ordinal ? "ordinal" : "cardinal",
974
+ name: "count",
975
+ offset: 0,
976
+ options: forms
977
+ }] };
978
+ }
979
+ }
980
+ const text = texts.get(key);
981
+ return text === void 0 ? null : convert(text.value, locale, ns, shape.tags, false);
982
+ };
983
+ /** The message of a key and shape in one locale; null when the locale has none of its keys. */
984
+ const build = (locale, ns, key, shape) => {
985
+ if (!shape.context) return branch(locale, ns, key, shape);
986
+ const contexts = /* @__PURE__ */ new Set();
987
+ const keys = /* @__PURE__ */ new Set([...namespaces(sourceLocale).get(ns)?.keys() ?? [], ...namespaces(locale).get(ns)?.keys() ?? []]);
988
+ for (const k of keys) {
989
+ if (!k.startsWith(key + "_")) continue;
990
+ const rest = baseKey(k.slice(key.length + 1));
991
+ if (rest === "" || CATEGORIES.includes(rest) || rest === "plural" || rest.includes(".")) continue;
992
+ contexts.add(rest);
993
+ }
994
+ const options = [];
995
+ for (const context of [...contexts].sort()) {
996
+ const converted = branch(locale, ns, `${key}_${context}`, shape);
997
+ if (converted === null) continue;
998
+ if ("error" in converted) return converted;
999
+ options.push({
1000
+ key: context,
1001
+ value: converted.nodes
1002
+ });
1003
+ }
1004
+ const other = branch(locale, ns, key, shape);
1005
+ if (other !== null && "error" in other) return other;
1006
+ if (options.length === 0 && other === null) return null;
1007
+ options.push({
1008
+ key: "other",
1009
+ value: other === null ? [] : other.nodes
1010
+ });
1011
+ return { nodes: [{
1012
+ type: "select",
1013
+ name: "context",
1014
+ options
1015
+ }] };
1016
+ };
1017
+ /** Every text of a key family in a locale, for a translator comment. */
1018
+ const originalTexts = (locale, ns, key) => {
1019
+ const parts = [];
1020
+ for (const [k, text] of namespaces(locale).get(ns) ?? []) if (k === key) parts.push(text.value);
1021
+ else if (k.startsWith(key + "_")) parts.push(`${k.slice(key.length + 1)}: ${text.value}`);
1022
+ return parts.join(" | ");
1023
+ };
1024
+ /**
1025
+ * Registers the message of a key and shape; its registry key, or why it
1026
+ * cannot be. Tags are named through `tags` (path rules and the defining
1027
+ * site's elements); a later site of the same message reuses the naming.
1028
+ */
1029
+ const define = (env, ns, key, shape, fallback, tags) => {
1030
+ const id = messageKey(ns, key) + (shape.context ? " {context}" : "") + (shape.ordinal ? " {ordinal}" : "") + (shape.tags ? " <Trans>" : "");
1031
+ if (env.message(id) !== void 0) return { key: id };
1032
+ let source = build(sourceLocale, ns, key, shape);
1033
+ if (source === null && fallback !== null) source = convert(fallback, sourceLocale, ns, shape.tags, false);
1034
+ if (source === null) return { problem: `${messageKey(ns, key)} is in no ${sourceLocale} catalog` };
1035
+ if ("error" in source) return { problem: `${messageKey(ns, key)}: ${source.error}` };
1036
+ const naming = newNaming();
1037
+ const finish = (nodes, isSource) => printICU(tags === null ? nodes : renameTags(nodes, naming, tags.pathOf, tags.elementAt, isSource));
1038
+ const sourceText = finish(source.nodes, true);
1039
+ const translations = /* @__PURE__ */ new Map();
1040
+ for (const locale of catalogs.keys()) {
1041
+ if (locale === sourceLocale) continue;
1042
+ const converted = build(locale, ns, key, shape);
1043
+ if (converted === null) continue;
1044
+ const first = [...namespaces(locale).get(ns) ?? []].find(([k]) => k === key || k.startsWith(key + "_"))?.[1];
1045
+ translations.set(locale, "error" in converted ? {
1046
+ unconverted: originalTexts(locale, ns, key),
1047
+ reason: converted.error,
1048
+ ...first === void 0 ? {} : {
1049
+ file: first.file,
1050
+ line: first.line
1051
+ }
1052
+ } : { icu: finish(converted.nodes, false) });
1053
+ }
1054
+ let origin = textOf(sourceLocale, ns, key);
1055
+ for (const [k, text] of namespaces(sourceLocale).get(ns) ?? []) {
1056
+ if (origin !== void 0) break;
1057
+ if (k.startsWith(key + "_")) origin = text;
1058
+ }
1059
+ if (!env.define({
1060
+ key: id,
1061
+ named: true,
1062
+ source: sourceText,
1063
+ context: null,
1064
+ hint: null,
1065
+ translations,
1066
+ file: origin?.file ?? "",
1067
+ line: origin?.line ?? 0
1068
+ })) return { problem: `${messageKey(ns, key)} could not be converted` };
1069
+ if (tags !== null) namings.set(id, naming);
1070
+ return { key: id };
1071
+ };
1072
+ const readCatalog = (file, env) => {
1073
+ let text;
1074
+ try {
1075
+ text = readFileSync(file.file, "utf8");
1076
+ } catch (error) {
1077
+ env.report("catalog-parse", `cannot read: ${error.message}`, { file: file.file }, "error");
1078
+ return;
1079
+ }
1080
+ const parsed = parseJson(text);
1081
+ if (parsed.problem !== null) {
1082
+ env.report("catalog-parse", parsed.problem.message, {
1083
+ file: file.file,
1084
+ line: parsed.problem.line
1085
+ }, "error");
1086
+ return;
1087
+ }
1088
+ const flat = flattenTree(parsed.value, text);
1089
+ for (const problem of flat.problems) env.report("migrate-unconverted", problem.message, {
1090
+ file: file.file,
1091
+ line: problem.line
1092
+ });
1093
+ const ns = file.namespace ?? DEFAULT_NS;
1094
+ let byNs = catalogs.get(file.locale);
1095
+ if (byNs === void 0) catalogs.set(file.locale, byNs = /* @__PURE__ */ new Map());
1096
+ let texts = byNs.get(ns);
1097
+ if (texts === void 0) byNs.set(ns, texts = /* @__PURE__ */ new Map());
1098
+ for (const [key, value] of flat.texts) texts.set(key, {
1099
+ ...value,
1100
+ file: file.file
1101
+ });
1102
+ };
1103
+ const planModule = (file, text, env) => {
1104
+ let program;
1105
+ try {
1106
+ program = parseModule(text, langFromId(file), file).program;
1107
+ } catch (error) {
1108
+ if (!isDiagnosticError(error)) throw error;
1109
+ env.diagnostics.push({
1110
+ ...error.diagnostic,
1111
+ file: env.rel(file)
1112
+ });
1113
+ return null;
1114
+ }
1115
+ const info = analyzeModule(program);
1116
+ const builder = new ModuleBuilder({
1117
+ file,
1118
+ text,
1119
+ info,
1120
+ isOldSource
1121
+ });
1122
+ const tsx = /\.[mc]?tsx$/.test(extname(file)) || extname(file) === ".tsx";
1123
+ const imported = (name) => {
1124
+ const locals = /* @__PURE__ */ new Set();
1125
+ for (const declaration of info.imports) {
1126
+ if (!isOldSource(declaration.source)) continue;
1127
+ for (const s of declaration.specifiers) if (s.imported === name) locals.add(s.local);
1128
+ }
1129
+ return locals;
1130
+ };
1131
+ const useTranslation = imported("useTranslation");
1132
+ const transNames = imported("Trans");
1133
+ const providers = imported("I18nextProvider");
1134
+ const instances = /* @__PURE__ */ new Set([
1135
+ ...imported("default"),
1136
+ ...imported("i18n"),
1137
+ ...imported("i18next")
1138
+ ]);
1139
+ const importedT = imported("t");
1140
+ const withTranslation = imported("withTranslation");
1141
+ const scopes = /* @__PURE__ */ new Map();
1142
+ const hookInstances = /* @__PURE__ */ new Set();
1143
+ const fixedLocale = /* @__PURE__ */ new Set();
1144
+ for (const binding of info.bindings) {
1145
+ const init = binding.declarator?.init;
1146
+ if (init === null || init === void 0) continue;
1147
+ const call = unwrap(init);
1148
+ if (call.type !== "CallExpression") continue;
1149
+ const callee = identifierName(call.callee);
1150
+ const isHook = callee !== null && useTranslation.has(callee);
1151
+ const fixed = memberParts(call.callee)?.property === "getFixedT";
1152
+ if (!isHook && !fixed) continue;
1153
+ const args = call.arguments;
1154
+ if (isHook) builder.hooks.push(binding);
1155
+ const nsNode = isHook ? args[0] : args[1];
1156
+ let ns = null;
1157
+ if (nsNode !== void 0) {
1158
+ const n = unwrap(nsNode);
1159
+ ns = stringValue(n) ?? (n.type === "ArrayExpression" ? stringValue(n.elements[0]) : null);
1160
+ }
1161
+ const options = isHook ? args[1] : args[2];
1162
+ const prefix = options === void 0 ? null : stringValue(objectProperties(options)?.properties.get("keyPrefix")?.value);
1163
+ const slot = binding.slot;
1164
+ const pattern = binding.declarator.id;
1165
+ const role = slot === null ? "t" : slot.type === "Property" ? keyName(slot) : pattern.type === "ArrayPattern" ? pattern.elements.indexOf(slot) === 0 ? "t" : "i18n" : null;
1166
+ if (role === "i18n") hookInstances.add(binding);
1167
+ else if (role === "t") scopes.set(binding, {
1168
+ ns,
1169
+ prefix
1170
+ });
1171
+ const lng = args[0] === void 0 ? null : unwrap(args[0]);
1172
+ if (fixed && lng !== null && !(lng.type === "Literal" && lng.value === null) && identifierName(lng) !== "undefined") {
1173
+ fixedLocale.add(binding);
1174
+ builder.note(call.start, "getFixedT(lng) pins a locale; its calls were not rewritten: use withLocale(lng, () => t(…)) or createTranslator(lng) from @menapo/vi18n/server");
1175
+ }
1176
+ }
1177
+ /** The innermost binding of `name` visible at `offset`. */
1178
+ const resolve = (name, offset) => {
1179
+ let best = null;
1180
+ for (const b of info.bindings) {
1181
+ if (b.name !== name || offset < b.scope.start || offset >= b.scope.end) continue;
1182
+ if (best === null || b.scope.end - b.scope.start < best.scope.end - best.scope.start) best = b;
1183
+ }
1184
+ return best;
1185
+ };
1186
+ /** Keys a computed key can produce, defined so they reach the `msg()` table. */
1187
+ const computedKeys = (node, ns, prefix) => {
1188
+ const all = [];
1189
+ for (const [space, texts] of namespaces(sourceLocale)) {
1190
+ if (ns !== null && space !== ns) continue;
1191
+ for (const key of new Set([...texts.keys()].map(baseKey))) all.push({
1192
+ ns: space,
1193
+ key
1194
+ });
1195
+ }
1196
+ const found = /* @__PURE__ */ new Set();
1197
+ const add = (entryNs, key) => {
1198
+ const shape = {
1199
+ count: isPlural(entryNs, key),
1200
+ ordinal: false,
1201
+ context: false,
1202
+ tags: false
1203
+ };
1204
+ const defined = define(env, entryNs, key, shape, null, null);
1205
+ reference(entryNs, key);
1206
+ if ("key" in defined) found.add(defined.key);
1207
+ };
1208
+ const escape = (q) => q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1209
+ const visitKey = (n) => {
1210
+ if (n.type === "Literal" && typeof n.value === "string") {
1211
+ const key = prefix === null ? n.value : `${prefix}.${n.value}`;
1212
+ for (const e of all) if (e.key === key) add(e.ns, e.key);
1213
+ } else if (n.type === "TemplateLiteral") {
1214
+ const quasis = n.quasis.map((q) => q.value.cooked ?? "");
1215
+ if (prefix !== null) quasis[0] = `${prefix}.${quasis[0]}`;
1216
+ if (/[A-Za-z]/.test(quasis.join(""))) {
1217
+ const pattern = new RegExp("^" + quasis.map(escape).join("[^]+?") + "$");
1218
+ for (const e of all) if (pattern.test(e.key)) add(e.ns, e.key);
1219
+ }
1220
+ return;
1221
+ }
1222
+ for (const [, child] of childEntries(n)) visitKey(child);
1223
+ };
1224
+ visitKey(node);
1225
+ return [...found];
1226
+ };
1227
+ /** `t(key, options)` or `<x>.t(key, options)`. */
1228
+ const tCall = (node) => {
1229
+ const callee = unwrap(node.callee);
1230
+ const args = node.arguments;
1231
+ let known = false;
1232
+ let consume = [];
1233
+ let scope = {
1234
+ ns: null,
1235
+ prefix: null
1236
+ };
1237
+ let pinned = false;
1238
+ if (callee.type === "Identifier") {
1239
+ const name = callee.name;
1240
+ const binding = resolve(name, callee.start);
1241
+ if (binding !== null && scopes.has(binding)) {
1242
+ known = true;
1243
+ pinned = fixedLocale.has(binding);
1244
+ scope = scopes.get(binding);
1245
+ } else if (importedT.has(name)) known = true;
1246
+ else if (name !== "t" || binding?.kind === "import") return;
1247
+ consume = [callee];
1248
+ } else {
1249
+ const member = memberParts(callee);
1250
+ if (member === null || member.property !== "t") return;
1251
+ const object = unwrap(member.object);
1252
+ if (object.type === "Identifier") {
1253
+ consume = [object];
1254
+ const binding = resolve(object.name, object.start);
1255
+ if (binding !== null && scopes.has(binding)) {
1256
+ known = true;
1257
+ pinned = fixedLocale.has(binding);
1258
+ scope = scopes.get(binding);
1259
+ } else if (instances.has(object.name) || binding !== null && hookInstances.has(binding)) known = true;
1260
+ }
1261
+ }
1262
+ const first = args[0];
1263
+ if (first === void 0 || first.type === "SpreadElement") return;
1264
+ const options = args[1];
1265
+ const optionProps = options === void 0 ? null : objectProperties(options);
1266
+ let rawKey = stringValue(first);
1267
+ if (rawKey === null) {
1268
+ const keys = computedKeys(first, scope.ns, scope.prefix);
1269
+ if (keys.length > 0 || known) builder.note(first.start, `t(${text.slice(first.start, first.end)}) uses a computed key and was not rewritten`, keys);
1270
+ return;
1271
+ }
1272
+ const label = JSON.stringify(rawKey);
1273
+ let ns = stringValue(optionProps?.properties.get("ns")?.value) ?? scope.ns;
1274
+ const colon = rawKey.indexOf(":");
1275
+ if (colon !== -1 && namespaces(sourceLocale).has(rawKey.slice(0, colon))) {
1276
+ ns = rawKey.slice(0, colon);
1277
+ rawKey = rawKey.slice(colon + 1);
1278
+ }
1279
+ const key = scope.prefix === null ? rawKey : `${scope.prefix}.${rawKey}`;
1280
+ const fallback = options !== void 0 && optionProps === null ? stringValue(options) : stringValue(optionProps?.properties.get("defaultValue")?.value);
1281
+ const resolvedNs = findNamespace(key, ns);
1282
+ if (resolvedNs !== null) reference(resolvedNs, key);
1283
+ if (resolvedNs === null && fallback === null) {
1284
+ if (known) builder.note(node.start, `t(${label}): the key is in no ${sourceLocale} catalog; not rewritten`);
1285
+ return;
1286
+ }
1287
+ const unsupported = [...optionProps?.properties.keys() ?? []].filter((k) => UNSUPPORTED_OPTIONS.has(k));
1288
+ if (unsupported.length > 0 || args.length > 2) {
1289
+ builder.note(node.start, unsupported.includes("lng") || unsupported.includes("lngs") ? `t(${label}) fixes a locale (${unsupported.join(", ")}): use withLocale(lng, () => t(…)) from @menapo/vi18n/server; not rewritten` : `t(${label}, …) passes ${unsupported.join(", ") || "a third argument"}, which vi18n has no counterpart for; not rewritten`);
1290
+ return;
1291
+ }
1292
+ const finalNs = resolvedNs ?? ns ?? defaultNs ?? DEFAULT_NS;
1293
+ if (pinned) {
1294
+ const shape = {
1295
+ count: isPlural(finalNs, key),
1296
+ ordinal: false,
1297
+ context: false,
1298
+ tags: false
1299
+ };
1300
+ const defined = define(env, finalNs, key, shape, fallback, null);
1301
+ if ("key" in defined) builder.note(node.start, `t(${label}) uses a locale getFixedT() pinned; not rewritten`, [defined.key]);
1302
+ return;
1303
+ }
1304
+ const passesValues = options !== void 0 && optionProps === null && fallback === null;
1305
+ const shape = {
1306
+ count: optionProps?.properties.has("count") === true || passesValues && isPlural(finalNs, key),
1307
+ ordinal: optionProps?.properties.has("ordinal") === true,
1308
+ context: optionProps?.properties.has("context") === true,
1309
+ tags: false
1310
+ };
1311
+ if (!shape.count && isPlural(finalNs, key)) {
1312
+ builder.note(node.start, `t(${label}) has plural forms but passes no count; not rewritten`);
1313
+ return;
1314
+ }
1315
+ const defined = define(env, finalNs, key, shape, fallback, null);
1316
+ if ("problem" in defined) {
1317
+ builder.note(node.start, `${defined.problem}; t(${label}) was not rewritten`);
1318
+ return;
1319
+ }
1320
+ const kept = (optionProps?.order ?? []).filter((p) => p.type === "SpreadElement" || !LOOKUP_OPTIONS.has(keyName(p) ?? ""));
1321
+ builder.site(node, (r) => {
1322
+ let values = "";
1323
+ if (kept.length > 0) values = `, { ${kept.map((p) => r.src(p.start, p.end)).join(", ")} }`;
1324
+ else if (passesValues) values = `, ${r.src(options.start, options.end)}`;
1325
+ return `${r.fn.t}(${r.message(defined.key)}${values})`;
1326
+ }, {
1327
+ keys: [defined.key],
1328
+ uses: ["t"],
1329
+ consume
1330
+ });
1331
+ };
1332
+ /** `<Trans>` as `rich()`, or `t()` when its message has no tags. */
1333
+ const trans = (element) => {
1334
+ const attributes = jsxAttributes(element);
1335
+ const attr = (name) => attributes.named.get(name)?.value ?? null;
1336
+ const has = (name) => attributes.named.has(name);
1337
+ if (attributes.spreads.length > 0) {
1338
+ builder.note(element.start, "<Trans {...props}> spreads props and was not rewritten");
1339
+ return;
1340
+ }
1341
+ for (const name of [
1342
+ "tOptions",
1343
+ "parent",
1344
+ "i18nIsDynamicList",
1345
+ "shouldUnescape"
1346
+ ]) if (has(name)) {
1347
+ builder.note(element.start, `<Trans ${name}> has no vi18n counterpart; not rewritten`);
1348
+ return;
1349
+ }
1350
+ const children = jsxChildren(element);
1351
+ const childString = nodesToString(children);
1352
+ let rawKey = has("i18nKey") ? attributeString(attr("i18nKey")) : null;
1353
+ if (has("i18nKey") && rawKey === null) {
1354
+ builder.note(element.start, "<Trans i18nKey={…}> with a computed key was not rewritten");
1355
+ return;
1356
+ }
1357
+ const fallback = has("defaults") ? attributeString(attr("defaults")) : childString.ok ? childString.text : null;
1358
+ rawKey ??= fallback;
1359
+ if (rawKey === null || rawKey === "") {
1360
+ builder.note(element.start, `<Trans> without i18nKey: ${childString.reason || "no text"}; not rewritten`);
1361
+ return;
1362
+ }
1363
+ let ns = has("ns") ? attributeString(attr("ns")) : null;
1364
+ const colon = rawKey.indexOf(":");
1365
+ if (colon !== -1 && namespaces(sourceLocale).has(rawKey.slice(0, colon))) {
1366
+ ns = rawKey.slice(0, colon);
1367
+ rawKey = rawKey.slice(colon + 1);
1368
+ }
1369
+ const resolvedNs = findNamespace(rawKey, ns);
1370
+ if (resolvedNs !== null) reference(resolvedNs, rawKey);
1371
+ const finalNs = resolvedNs ?? ns ?? defaultNs ?? DEFAULT_NS;
1372
+ const componentsExpr = has("components") ? attributeExpression(attr("components")) : null;
1373
+ let indexed = children;
1374
+ let named = null;
1375
+ if (componentsExpr !== null) {
1376
+ const c = unwrap(componentsExpr);
1377
+ if (c.type === "ArrayExpression") indexed = c.elements.map((e) => e === null ? c : unwrap(e));
1378
+ else {
1379
+ const props = objectProperties(c);
1380
+ if (props === null || props.spreads.length > 0) {
1381
+ builder.note(element.start, "<Trans components={…}> is not a literal object or array; not rewritten");
1382
+ return;
1383
+ }
1384
+ named = new Map([...props.properties].map(([k, p]) => [k, unwrap(p.value)]));
1385
+ indexed = null;
1386
+ }
1387
+ }
1388
+ const pathOf = (name, parent) => {
1389
+ if (/^\d+$/.test(name) && indexed !== null) return parent === null ? name : `${parent}.${name}`;
1390
+ if (named?.has(name) === true) return name;
1391
+ if (BASIC_HTML.has(name)) return `html:${name}`;
1392
+ return null;
1393
+ };
1394
+ const elementAt = (path) => {
1395
+ if (path.startsWith("html:")) return "html";
1396
+ if (named !== null && !/^\d/.test(path)) return named.get(path) ?? null;
1397
+ let scope = indexed;
1398
+ let found = null;
1399
+ for (const part of path.split(".")) {
1400
+ found = scope?.[Number(part)] ?? null;
1401
+ if (found === null || found.type !== "JSXElement") return null;
1402
+ scope = jsxChildren(found);
1403
+ }
1404
+ return found;
1405
+ };
1406
+ const shape = {
1407
+ count: has("count"),
1408
+ ordinal: false,
1409
+ context: has("context"),
1410
+ tags: true
1411
+ };
1412
+ const defined = define(env, finalNs, rawKey, shape, resolvedNs === null ? fallback : null, {
1413
+ pathOf,
1414
+ elementAt
1415
+ });
1416
+ if ("problem" in defined) {
1417
+ builder.note(element.start, `${defined.problem}; <Trans> was not rewritten`);
1418
+ return;
1419
+ }
1420
+ const message = env.message(defined.key);
1421
+ const naming = namings.get(defined.key);
1422
+ if (message === void 0 || naming === void 0) return;
1423
+ const { handlers, missing } = siteHandlers(naming, elementAt);
1424
+ if (missing.length > 0) {
1425
+ builder.note(element.start, `<Trans>: ${missing.map((m) => `<${m}>`).join(", ")} ${missing.length === 1 ? "maps" : "map"} to no element here; not rewritten`, [defined.key]);
1426
+ return;
1427
+ }
1428
+ const selfClosing = selfClosingTags(env.codec.parse(message.canonical));
1429
+ const values = [];
1430
+ const valuesExpr = has("values") ? attributeExpression(attr("values")) : null;
1431
+ if (valuesExpr !== null) {
1432
+ const props = objectProperties(valuesExpr);
1433
+ if (props === null) {
1434
+ builder.note(element.start, "<Trans values={…}> is not an object literal; not rewritten");
1435
+ return;
1436
+ }
1437
+ values.push(...props.order);
1438
+ }
1439
+ const collect = (list) => {
1440
+ for (const child of list) {
1441
+ if (child.type === "JSXElement") collect(jsxChildren(child));
1442
+ if (child.type !== "JSXExpressionContainer") continue;
1443
+ const props = objectProperties(child.expression);
1444
+ if (props !== null) values.push(...props.order.filter((p) => p.type === "SpreadElement" || keyName(p) !== "format"));
1445
+ }
1446
+ };
1447
+ collect(children);
1448
+ const extra = [];
1449
+ for (const name of ["count", "context"]) {
1450
+ const expression = has(name) ? attributeExpression(attr(name)) : null;
1451
+ if (expression !== null) extra.push([name, expression]);
1452
+ }
1453
+ const rich = handlers.size > 0;
1454
+ const types = rich && tsx ? reactTypes(builder) : null;
1455
+ builder.site(element, (r) => {
1456
+ const entries = [];
1457
+ for (const [name, handler] of handlers) entries.push(entry(name, elementHandler(handler, selfClosing.has(name), r, types)));
1458
+ for (const p of values) entries.push(r.src(p.start, p.end));
1459
+ for (const [name, expression] of extra) entries.push(entry(name, r.src(expression.start, expression.end)));
1460
+ const call = rich ? richCall(r, defined.key, entries, types) : `${r.fn.t}(${r.message(defined.key)}${entries.length > 0 ? `, { ${entries.join(", ")} }` : ""})`;
1461
+ return inJsxChildren(info, element) ? `{${call}}` : call;
1462
+ }, {
1463
+ keys: [defined.key],
1464
+ uses: [rich ? "rich" : "t"],
1465
+ consume: jsxNameNodes(element)
1466
+ });
1467
+ };
1468
+ const unwrapProvider = (element) => {
1469
+ const children = jsxChildren(element);
1470
+ const opening = element.openingElement;
1471
+ const closing = element.closingElement;
1472
+ builder.consumeRange(opening);
1473
+ builder.consume(jsxNameNodes(element));
1474
+ const nested = inJsxChildren(info, element);
1475
+ builder.replace(element, (r) => {
1476
+ if (closing === null) return nested ? "" : "null";
1477
+ if (nested) return r.src(opening.end, closing.start).trim();
1478
+ if (children.length === 1 && children[0].type === "JSXElement") return r.src(children[0].start, children[0].end);
1479
+ return `<>${r.src(opening.end, closing.start)}</>`;
1480
+ });
1481
+ };
1482
+ /** Setup and APIs without a mechanical counterpart, reported. */
1483
+ const otherCall = (node) => {
1484
+ const name = identifierName(node.callee);
1485
+ if (name !== null && withTranslation.has(name)) {
1486
+ builder.note(node.start, "withTranslation() injects t as a prop: drop it and import t from @menapo/vi18n where the component calls it");
1487
+ return;
1488
+ }
1489
+ const member = memberParts(node.callee);
1490
+ if (member === null) return;
1491
+ let root = unwrap(member.object);
1492
+ for (;;) if (root.type === "CallExpression") root = unwrap(root.callee);
1493
+ else if (root.type === "MemberExpression") root = unwrap(root.object);
1494
+ else break;
1495
+ const object = identifierName(root);
1496
+ const isInstance = object !== null && (instances.has(object) || /^i18n(?:ext)?$/.test(object));
1497
+ if (member.property === "init" && isInstance) builder.note(node.start, "i18next.init(): delete this setup once nothing uses i18next; vi18n needs only its Vite plugin and vi18n.config.ts");
1498
+ else if (member.property === "changeLanguage" && isInstance) builder.note(node.start, "changeLanguage(lng): use setLocale(lng) from @menapo/vi18n");
1499
+ else if (member.property === "exists" && isInstance) builder.note(node.start, "exists(key) has no vi18n counterpart: every message the code names exists");
1500
+ };
1501
+ const visit = (node) => {
1502
+ if (node.type === "CallExpression") {
1503
+ tCall(node);
1504
+ otherCall(node);
1505
+ } else if (node.type === "JSXElement") {
1506
+ const name = jsxName(node);
1507
+ if (transNames.has(name)) trans(node);
1508
+ else if (providers.has(name)) unwrapProvider(node);
1509
+ else if (imported("Translation").has(name)) builder.note(node.start, "<Translation> render props are not rewritten: call t() from @menapo/vi18n inside");
1510
+ }
1511
+ for (const [, child] of childEntries(node)) visit(child);
1512
+ };
1513
+ visit(program);
1514
+ return builder.finish();
1515
+ };
1516
+ return {
1517
+ catalogExtensions: [".json"],
1518
+ catalogsRequired: true,
1519
+ extensions: MODULE_EXTENSIONS,
1520
+ mentions: (text) => /i18next|\bt\s*\(|<Trans\b/.test(text),
1521
+ load(files, env) {
1522
+ sourceLocale = env.config.sourceLocale;
1523
+ for (const file of files) readCatalog(file, env);
1524
+ const source = namespaces(sourceLocale);
1525
+ defaultNs = source.has(DEFAULT_NS) ? DEFAULT_NS : source.size === 1 ? [...source.keys()][0] : null;
1526
+ },
1527
+ planFile: planModule,
1528
+ unusedKeys() {
1529
+ const out = /* @__PURE__ */ new Set();
1530
+ for (const [ns, texts] of namespaces(sourceLocale)) for (const key of texts.keys()) if (!referenced.has(`${ns}:${key}`)) out.add(messageKey(ns, key));
1531
+ return [...out];
1532
+ }
1533
+ };
1534
+ }
1535
+ /** react-i18next's `nodesToString`: the key and default text `<Trans>` derives from its children. */
1536
+ function nodesToString(children) {
1537
+ let text = "";
1538
+ for (const [index, child] of children.entries()) {
1539
+ if (child.type === "JSXText") {
1540
+ text += cleanJsxText(child.value);
1541
+ continue;
1542
+ }
1543
+ if (child.type === "JSXExpressionContainer") {
1544
+ const expression = unwrap(child.expression);
1545
+ const literal = stringValue(expression);
1546
+ if (literal !== null) {
1547
+ text += literal;
1548
+ continue;
1549
+ }
1550
+ const props = objectProperties(expression);
1551
+ if (props !== null && props.spreads.length === 0) {
1552
+ const format = stringValue(props.properties.get("format")?.value);
1553
+ const names = [...props.properties.keys()].filter((k) => k !== "format");
1554
+ if (names.length === 1) {
1555
+ text += `{{${format === null ? names[0] : `${names[0]}, ${format}`}}}`;
1556
+ continue;
1557
+ }
1558
+ }
1559
+ return {
1560
+ ok: false,
1561
+ text,
1562
+ reason: `{${text.length > 0 ? "…" : ""}} holds an expression, not text or {{ value }}`
1563
+ };
1564
+ }
1565
+ if (child.type === "JSXElement") {
1566
+ const name = jsxName(child);
1567
+ const attributes = child.openingElement.attributes;
1568
+ const inner = jsxChildren(child);
1569
+ const basic = BASIC_HTML.has(name);
1570
+ if (inner.length === 0) {
1571
+ text += basic && attributes.length === 0 ? `<${name}/>` : `<${index}></${index}>`;
1572
+ continue;
1573
+ }
1574
+ if (basic && attributes.length === 0 && inner.length === 1 && inner[0].type === "JSXText") {
1575
+ text += `<${name}>${cleanJsxText(inner[0].value)}</${name}>`;
1576
+ continue;
1577
+ }
1578
+ const nested = nodesToString(inner);
1579
+ if (!nested.ok) return nested;
1580
+ text += `<${index}>${nested.text}</${index}>`;
1581
+ continue;
1582
+ }
1583
+ return {
1584
+ ok: false,
1585
+ text,
1586
+ reason: `a ${child.type} child`
1587
+ };
1588
+ }
1589
+ return {
1590
+ ok: true,
1591
+ text,
1592
+ reason: ""
1593
+ };
1594
+ }
1595
+ //#endregion
1596
+ export { linguiId as _, annotateHandlers as a, pushLiteral as b, newNaming as c, richCall as d, selfClosingTags as f, formatjsId as g, formatjsAstToIcu as h, isOldSource as i, reactTypes as l, flattenTree as m, createI18nextAdapter as n, elementHandler as o, siteHandlers as p, interpolationNode as r, entry as s, MODULE_EXTENSIONS as t, renameTags as u, parseJson as v, yamlKeyLines as x, parseYaml as y };