@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,651 @@
1
+ import { o as isDiagnosticError } from "./types-D3QGM8XO.js";
2
+ import { n as printICU } from "./codec-DuvU2VQy.js";
3
+ import { u as parseModule } from "./worker-84U6uYB3.js";
4
+ import { f as readString, g as skipSpace, h as skipBalanced, o as importLineEdit, p as readTemplate, r as argumentEnd, y as vueRegions } from "./migrate-IpLDMXmq.js";
5
+ import { i as analyzeModule, l as identifierName, s as childEntries, t as ModuleBuilder, v as memberParts, x as unwrap, y as objectProperties } from "./rewrite-EsIw_lsq.js";
6
+ import { b as pushLiteral, m as flattenTree, s as entry, t as MODULE_EXTENSIONS, v as parseJson, x as yamlKeyLines, y as parseYaml } from "./i18next-BEJgv_S8.js";
7
+ import { extname } from "node:path";
8
+ import { readFileSync } from "node:fs";
9
+ //#region src/cli/commands/migrate/vue-i18n.ts
10
+ /**
11
+ * `vi18n migrate --from vue-i18n` (INTERNALS §2.14): vue-i18n 9–11 (and the
12
+ * v8 legacy API it keeps).
13
+ *
14
+ * Catalogs are JSON or YAML per locale, or one file keyed by locale; nested
15
+ * keys join with `.`. Messages convert to ICU: `{name}` and `{0}` stay
16
+ * arguments, `{'@'}` literals become text, linked messages (`@:key`,
17
+ * `@.lower:key`, `@:(key)`) are inlined from the same locale, and `|`
18
+ * pluralization becomes a plural on `count` (two forms `one`/`other`, three
19
+ * `=0`/`one`/`other`; `{count}` and `{n}` inside are `#`). Call sites:
20
+ * `t()`/`$t()`/`tc()`/`$tc()` in templates (interpolations and bound
21
+ * attributes) and scripts, `i18n.global.t()`, `this.$t()`. `useI18n()`
22
+ * bindings and imports go once unused; `<i18n-t>`, `v-t`, `$d`/`$n` and the
23
+ * setup are reported, their messages kept in the `msg()` table.
24
+ */
25
+ function isOldSource(source) {
26
+ return source === "vue-i18n" || source.startsWith("vue-i18n/") || source === "@intlify/core-base" || source === "petite-vue-i18n";
27
+ }
28
+ const MODIFIERS = {
29
+ upper: (s) => s.toUpperCase(),
30
+ lower: (s) => s.toLowerCase(),
31
+ capitalize: (s) => s === "" ? s : s[0].toUpperCase() + s.slice(1)
32
+ };
33
+ /**
34
+ * One vue-i18n message as ICU. `linked(key)` returns the raw text of a
35
+ * linked message in the same locale.
36
+ */
37
+ function convertVueMessage(raw, linked, depth = 0) {
38
+ const forms = [[]];
39
+ let current = forms[0];
40
+ let i = 0;
41
+ const literalText = (value) => pushLiteral(current, value);
42
+ while (i < raw.length) {
43
+ const c = raw[i];
44
+ if (c === "{") {
45
+ const quote = /^\{\s*'(?:[^'\\]|\\.)*'\s*\}/.exec(raw.slice(i));
46
+ const close = quote === null ? raw.indexOf("}", i + 1) : i + quote[0].length - 1;
47
+ if (close === -1) return { error: "an unclosed {" };
48
+ const body = raw.slice(i + 1, close).trim();
49
+ i = close + 1;
50
+ const literal = /^'((?:[^'\\]|\\.)*)'$/.exec(body);
51
+ if (literal !== null) {
52
+ literalText(literal[1].replace(/\\(.)/g, "$1"));
53
+ continue;
54
+ }
55
+ if (!/^[A-Za-z_$][\w$]*$|^\d+$/.test(body)) return { error: `{${body}} is not a named or list interpolation` };
56
+ current.push({
57
+ type: "argument",
58
+ name: body
59
+ });
60
+ continue;
61
+ }
62
+ if (c === "@") {
63
+ const m = /^@(?:\.(\w+))?:(?:\(([^)]*)\)|([\w$\-.]+))/.exec(raw.slice(i));
64
+ if (m === null) {
65
+ literalText("@");
66
+ i++;
67
+ continue;
68
+ }
69
+ const modifier = m[1];
70
+ let key = (m[2] ?? m[3] ?? "").trim();
71
+ let consumed = m[0].length;
72
+ if (m[3] !== void 0 && key.endsWith(".") && linked(key) === void 0 && linked(key.slice(0, -1)) !== void 0) {
73
+ key = key.slice(0, -1);
74
+ consumed--;
75
+ }
76
+ i += consumed;
77
+ if (depth > 8) return { error: "linked messages nest deeper than 8 levels" };
78
+ const text = linked(key);
79
+ if (text === void 0) return { error: `@:${key} links to no message` };
80
+ const inner = convertVueMessage(text, linked, depth + 1);
81
+ if ("error" in inner) return inner;
82
+ if (inner.nodes.some((n) => n.type === "plural")) return { error: `@:${key} links to a plural message` };
83
+ if (modifier !== void 0) {
84
+ const apply = MODIFIERS[modifier];
85
+ if (apply === void 0) return { error: `@.${modifier}: is not a built-in modifier` };
86
+ if (inner.nodes.some((n) => n.type !== "literal")) return { error: `@.${modifier}:${key} changes a message with placeholders, which ICU cannot` };
87
+ literalText(apply(inner.nodes.map((n) => n.value).join("")));
88
+ continue;
89
+ }
90
+ for (const node of inner.nodes) if (node.type === "literal") literalText(node.value);
91
+ else current.push(node);
92
+ continue;
93
+ }
94
+ if (c === "|") {
95
+ current = [];
96
+ forms.push(current);
97
+ i++;
98
+ continue;
99
+ }
100
+ literalText(c);
101
+ i++;
102
+ }
103
+ if (forms.length === 1) return { nodes: forms[0] };
104
+ const trimmed = forms.map((form) => trimForm(form));
105
+ const keys = trimmed.length === 2 ? ["one", "other"] : trimmed.length === 3 ? [
106
+ "=0",
107
+ "one",
108
+ "other"
109
+ ] : null;
110
+ if (keys === null) return { error: `${trimmed.length} plural forms need custom pluralRules; write the ICU plural by hand` };
111
+ return { nodes: [{
112
+ type: "plural",
113
+ pluralType: "cardinal",
114
+ name: "count",
115
+ offset: 0,
116
+ options: trimmed.map((form, index) => ({
117
+ key: keys[index],
118
+ value: form.map((node) => node.type === "argument" && (node.name === "count" || node.name === "n") ? { type: "pound" } : node)
119
+ }))
120
+ }] };
121
+ }
122
+ function trimForm(form) {
123
+ const out = form.map((n) => ({ ...n }));
124
+ const first = out[0];
125
+ if (first?.type === "literal") {
126
+ first.value = first.value.replace(/^\s+/, "");
127
+ if (first.value === "") out.shift();
128
+ }
129
+ const last = out[out.length - 1];
130
+ if (last?.type === "literal") {
131
+ last.value = last.value.replace(/\s+$/, "");
132
+ if (last.value === "") out.pop();
133
+ }
134
+ return out;
135
+ }
136
+ function createVueI18nAdapter() {
137
+ /** locale → key → text. */
138
+ const catalogs = /* @__PURE__ */ new Map();
139
+ const referenced = /* @__PURE__ */ new Set();
140
+ let sourceLocale = "";
141
+ const textOf = (locale, key) => catalogs.get(locale)?.get(key);
142
+ const add = (locale, key, value, file) => {
143
+ let texts = catalogs.get(locale);
144
+ if (texts === void 0) catalogs.set(locale, texts = /* @__PURE__ */ new Map());
145
+ texts.set(key, {
146
+ ...value,
147
+ file
148
+ });
149
+ };
150
+ const readCatalog = (file, env) => {
151
+ let text;
152
+ try {
153
+ text = readFileSync(file.file, "utf8");
154
+ } catch (error) {
155
+ env.report("catalog-parse", `cannot read: ${error.message}`, { file: file.file }, "error");
156
+ return;
157
+ }
158
+ const yaml = extname(file.file) === ".yaml" || extname(file.file) === ".yml";
159
+ const parsed = yaml ? parseYaml(text) : parseJson(text);
160
+ if (parsed.problem !== null) {
161
+ env.report("catalog-parse", parsed.problem.message, {
162
+ file: file.file,
163
+ line: parsed.problem.line
164
+ }, "error");
165
+ return;
166
+ }
167
+ const keyLines = yaml ? yamlKeyLines(text) : null;
168
+ const read = (tree, locale) => {
169
+ const flat = flattenTree(tree, yaml ? "" : text);
170
+ for (const problem of flat.problems) env.report("migrate-unconverted", problem.message, {
171
+ file: file.file,
172
+ line: problem.line
173
+ });
174
+ for (const [key, value] of flat.texts) {
175
+ const line = keyLines === null ? value.line : keyLines.get(key.split(".").pop()) ?? 0;
176
+ add(locale, key, {
177
+ value: value.value,
178
+ line
179
+ }, file.file);
180
+ }
181
+ };
182
+ const value = parsed.value;
183
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
184
+ const keys = Object.keys(value);
185
+ const locales = keys.map((k) => env.config.locales.find((l) => l.toLowerCase() === k.toLowerCase().replace(/_/g, "-")));
186
+ if (keys.length > 0 && locales.every((l) => l !== void 0) && Object.values(value).every((v) => typeof v === "object" && v !== null)) {
187
+ keys.forEach((k, index) => read(value[k], locales[index]));
188
+ return;
189
+ }
190
+ }
191
+ read(value, file.locale);
192
+ };
193
+ /** Registers a message; its registry key, or why it cannot be. */
194
+ const define = (env, key, fallback) => {
195
+ referenced.add(key);
196
+ if (env.message(key) !== void 0) return { key };
197
+ const convert = (locale, raw) => convertVueMessage(raw, (linkedKey) => {
198
+ referenced.add(linkedKey);
199
+ return textOf(locale, linkedKey)?.value;
200
+ });
201
+ const sourceText = textOf(sourceLocale, key);
202
+ const raw = sourceText?.value ?? fallback;
203
+ if (raw === null) return { problem: `${key} is in no ${sourceLocale} catalog` };
204
+ const source = convert(sourceLocale, raw);
205
+ if ("error" in source) return { problem: `${key}: ${source.error}` };
206
+ const translations = /* @__PURE__ */ new Map();
207
+ for (const [locale, texts] of catalogs) {
208
+ if (locale === sourceLocale) continue;
209
+ const found = texts.get(key);
210
+ if (found === void 0) continue;
211
+ const converted = convert(locale, found.value);
212
+ translations.set(locale, "error" in converted ? {
213
+ unconverted: found.value,
214
+ reason: converted.error,
215
+ file: found.file,
216
+ line: found.line
217
+ } : { icu: printICU(converted.nodes) });
218
+ }
219
+ return env.define({
220
+ key,
221
+ named: true,
222
+ source: printICU(source.nodes),
223
+ context: null,
224
+ hint: null,
225
+ translations,
226
+ file: sourceText?.file ?? "",
227
+ line: sourceText?.line ?? 0
228
+ }) ? { key } : { problem: `${key} could not be converted` };
229
+ };
230
+ const isPluralKey = (env, key) => {
231
+ const message = env.message(key);
232
+ return message !== void 0 && message.canonical.startsWith("{count, plural,") && message.canonical.endsWith("}");
233
+ };
234
+ /** Keys a computed key can produce (string literals and matching templates in it). */
235
+ const keysIn = (code) => {
236
+ const keys = [...catalogs.get(sourceLocale)?.keys() ?? []];
237
+ const found = /* @__PURE__ */ new Set();
238
+ for (let i = 0; i < code.length; i++) {
239
+ const c = code[i];
240
+ if (c === "\"" || c === "'") {
241
+ const s = readString(code, i);
242
+ if (s === null) break;
243
+ if (keys.includes(s.value)) found.add(s.value);
244
+ i = s.end - 1;
245
+ } else if (c === "`") {
246
+ const s = readTemplate(code, i);
247
+ if (s === null) break;
248
+ if (/[A-Za-z]/.test(s.quasis.join(""))) {
249
+ const pattern = new RegExp("^" + s.quasis.map((q) => q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^]+?") + "$");
250
+ for (const key of keys) if (pattern.test(key)) found.add(key);
251
+ }
252
+ i = s.end - 1;
253
+ }
254
+ }
255
+ return [...found];
256
+ };
257
+ /**
258
+ * The values of a `t`-family call from its arguments as written (default
259
+ * message strings left out): `t(key, named | list | values)`,
260
+ * `t(key, count)` and `t(key, named | list, count)` for a plural message
261
+ * (`tc` always pluralizes), or why vi18n migrate cannot read them.
262
+ */
263
+ const callValues = (env, key, args, tc) => {
264
+ const plural = tc || isPluralKey(env, key);
265
+ let named = null;
266
+ let list = null;
267
+ let count = null;
268
+ const [a, b] = args;
269
+ if (args.length > 2) return { problem: "more than two value arguments" };
270
+ if (a !== void 0 && b === void 0) {
271
+ if (a.kind === "object") named = 0;
272
+ else if (a.kind === "array") list = 0;
273
+ else if (plural) count = 0;
274
+ else named = 0;
275
+ } else if (a !== void 0 && b !== void 0) {
276
+ if ((a.kind === "object" || a.kind === "array") && b.kind === "other") {
277
+ if (a.kind === "object") named = 0;
278
+ else list = 0;
279
+ count = 1;
280
+ } else return { problem: "options vi18n has no counterpart for" };
281
+ }
282
+ const namedArg = named === null ? void 0 : args[named];
283
+ const namesCount = namedArg?.kind === "object" && /[{,]\s*count\s*[:,}]/.test(namedArg.code);
284
+ if (plural && count === null && !namesCount) return { problem: "a plural message without a count" };
285
+ return { values: (src) => {
286
+ const entries = [];
287
+ if (count !== null) entries.push(entry("count", src(count).trim()));
288
+ if (named !== null) {
289
+ const code = src(named).trim();
290
+ if (args[named]?.kind === "object") {
291
+ const inner = code.slice(1, -1).trim();
292
+ if (inner !== "") entries.push(inner);
293
+ } else if (entries.length === 0) return `, ${code}`;
294
+ else entries.push(`...${code}`);
295
+ }
296
+ if (list !== null) splitTopLevel(src(list).trim().slice(1, -1)).forEach((item, index) => entries.push(`${index}: ${item.trim()}`));
297
+ return entries.length === 0 ? "" : `, { ${entries.join(", ")} }`;
298
+ } };
299
+ };
300
+ const planFile = (file, text, env) => {
301
+ const vue = extname(file) === ".vue";
302
+ const regions = vue ? vueRegions(text) : null;
303
+ const scripts = [];
304
+ if (vue) for (const m of text.matchAll(/<script\b([^>]*)>/g)) {
305
+ const attrs = m[1];
306
+ if (/\bsrc\s*=/.test(attrs)) continue;
307
+ const start = m.index + m[0].length;
308
+ const close = text.indexOf("<\/script>", start);
309
+ if (close === -1) continue;
310
+ scripts.push({
311
+ start,
312
+ end: close,
313
+ setup: /\bsetup\b/.test(attrs),
314
+ lang: /\blang\s*=\s*["']([^"']+)["']/.exec(attrs)?.[1] ?? null,
315
+ open: m.index
316
+ });
317
+ }
318
+ let code = text;
319
+ if (vue) {
320
+ code = text.replace(/[^\n]/g, " ");
321
+ for (const s of scripts) code = code.slice(0, s.start) + text.slice(s.start, s.end) + code.slice(s.end);
322
+ }
323
+ const lang = vue ? scripts.some((s) => s.lang === "ts" || s.lang === "tsx") ? "ts" : "js" : extname(file).includes("ts") ? "ts" : "js";
324
+ let program;
325
+ try {
326
+ program = parseModule(code, lang, file).program;
327
+ } catch (error) {
328
+ if (!isDiagnosticError(error)) throw error;
329
+ env.diagnostics.push({
330
+ ...error.diagnostic,
331
+ file: env.rel(file)
332
+ });
333
+ return null;
334
+ }
335
+ const info = analyzeModule(program);
336
+ const outside = /* @__PURE__ */ new Map();
337
+ const templateStart = text.indexOf("<template");
338
+ const templateEnd = text.lastIndexOf("</template>");
339
+ const inTemplate = (offset) => regions !== null && templateStart !== -1 && offset > templateStart && offset < templateEnd && !regions.code.some((r) => r.start <= offset && offset < r.end);
340
+ const templateCalls = [];
341
+ if (vue && regions !== null) {
342
+ const pattern = /(?<![\w$.-])(\$tc?|tc?|\$te|\$d|\$n|\$rt)\s*\(/g;
343
+ for (let m = pattern.exec(text); m !== null; m = pattern.exec(text)) {
344
+ if (!inTemplate(m.index) || !inExpression(text, m.index, regions)) continue;
345
+ const callee = m[1];
346
+ if (callee === "t" || callee === "tc") {
347
+ let list = outside.get(callee);
348
+ if (list === void 0) outside.set(callee, list = []);
349
+ list.push(m.index);
350
+ }
351
+ const call = lexicalCall(text, m.index, callee, m.index + m[0].length);
352
+ if (call !== null) templateCalls.push(call);
353
+ }
354
+ }
355
+ const blockOf = (offset) => scripts.find((s) => s.start <= offset && offset <= s.end) ?? null;
356
+ let plainSites = false;
357
+ /**
358
+ * The import goes into `<script setup>`, which the template sees, unless
359
+ * Options API code in a plain `<script>` calls `t` too: then into that
360
+ * block, whose imports the template sees beside a `<script setup>` (one
361
+ * is added, empty, when the component has none).
362
+ */
363
+ const placeImport = vue ? (line, removed) => {
364
+ const setup = scripts.find((s) => s.setup);
365
+ const plain = scripts.find((s) => !s.setup);
366
+ const target = plainSites && plain !== void 0 ? plain : setup ?? null;
367
+ if (target === null) return [importLineEdit(file, text, line, null)];
368
+ const lastImport = info.imports.filter((d) => d.node.start >= target.start && d.node.end <= target.end && !removed.has(d.node)).pop();
369
+ const at = lastImport === void 0 ? target.start : lastImport.node.end;
370
+ const edits = [{
371
+ start: at,
372
+ end: at,
373
+ text: `\n${line}`
374
+ }];
375
+ if (target === plain && setup === void 0 && templateCalls.length > 0) {
376
+ const close = text.indexOf("<\/script>", plain.end) + 9;
377
+ const langAttr = plain.lang === null ? "" : ` lang="${plain.lang}"`;
378
+ edits.push({
379
+ start: close,
380
+ end: close,
381
+ text: `\n<script setup${langAttr}><\/script>`
382
+ });
383
+ }
384
+ return edits;
385
+ } : void 0;
386
+ const builder = new ModuleBuilder({
387
+ file,
388
+ text,
389
+ info,
390
+ isOldSource,
391
+ outside,
392
+ ...placeImport === void 0 ? {} : { placeImport }
393
+ });
394
+ const useI18n = /* @__PURE__ */ new Set();
395
+ for (const declaration of info.imports) {
396
+ if (!isOldSource(declaration.source)) continue;
397
+ for (const s of declaration.specifiers) if (s.imported === "useI18n") useI18n.add(s.local);
398
+ }
399
+ const tBindings = /* @__PURE__ */ new Set();
400
+ for (const binding of info.bindings) {
401
+ const init = binding.declarator?.init;
402
+ if (init === null || init === void 0) continue;
403
+ const call = unwrap(init);
404
+ const callee = call.type === "CallExpression" ? identifierName(call.callee) : null;
405
+ if (callee === null || !useI18n.has(callee)) continue;
406
+ builder.hooks.push(binding);
407
+ const slot = binding.slot;
408
+ const name = slot !== null && slot.type === "Property" ? slot.key.name : null;
409
+ if (name === "t" || name === "tc") tBindings.add(binding);
410
+ const options = call.arguments[0];
411
+ if (options !== void 0 && objectProperties(options)?.properties.has("messages") === true) builder.note(call.start, "useI18n({ messages }) holds component-local messages, which were not migrated: move them into the locale catalogs");
412
+ }
413
+ if (vue && templateStart !== -1) for (const binding of builder.hooks) {
414
+ const pattern = new RegExp(String.raw`(?<![\w$.])${binding.name.replace(/\$/g, "$")}(?![\w$])`, "g");
415
+ for (const m of text.slice(templateStart, templateEnd).matchAll(pattern)) {
416
+ const offset = templateStart + m.index;
417
+ let list = outside.get(binding.name);
418
+ if (list === void 0) outside.set(binding.name, list = []);
419
+ if (!list.includes(offset)) list.push(offset);
420
+ }
421
+ }
422
+ const resolve = (name, offset) => {
423
+ let best = null;
424
+ for (const b of info.bindings) {
425
+ if (b.name !== name || offset < b.scope.start || offset >= b.scope.end) continue;
426
+ if (best === null || b.scope.end - b.scope.start < best.scope.end - best.scope.start) best = b;
427
+ }
428
+ return best;
429
+ };
430
+ /** One `t`-family call: rewrite, note, or nothing when it is not vue-i18n's. */
431
+ const site = (call, known, consume, markup) => {
432
+ const callee = call.callee.replace(/^\$/, "");
433
+ if (callee === "te" || callee === "d" || callee === "n" || callee === "rt") {
434
+ if (known || call.callee.startsWith("$")) {
435
+ const hint = callee === "d" ? "use fmt.date from @menapo/vi18n" : callee === "n" ? "use fmt.number from @menapo/vi18n" : callee === "te" ? "vi18n needs no existence check, since every message the code names exists" : "translate the text with t() where it is known";
436
+ builder.note(call.start, `${call.callee}() is not rewritten: ${hint}`);
437
+ }
438
+ return;
439
+ }
440
+ const first = call.args[0];
441
+ if (first === void 0) return;
442
+ const firstCode = text.slice(first.start, first.end).trim();
443
+ const literal = /^["']/.test(firstCode) ? readString(firstCode, 0) : null;
444
+ let key = literal !== null && literal.end === firstCode.length ? literal.value : null;
445
+ if (key === null && firstCode.startsWith("`")) {
446
+ const t = readTemplate(firstCode, 0);
447
+ if (t !== null && t.quasis.length === 1 && t.end === firstCode.length) key = t.quasis[0];
448
+ }
449
+ if (key === null) {
450
+ const keys = keysIn(firstCode);
451
+ for (const k of keys) define(env, k, null);
452
+ if (keys.length > 0 || known) builder.note(call.start, `${call.callee}(${firstCode}) uses a computed key and was not rewritten`, keys.filter((k) => env.message(k) !== void 0));
453
+ return;
454
+ }
455
+ const rest = call.args.slice(1).map((a) => {
456
+ const argCode = text.slice(a.start, a.end).trim();
457
+ return {
458
+ code: argCode,
459
+ kind: argCode.startsWith("{") ? "object" : argCode.startsWith("[") ? "array" : /^["'`]/.test(argCode) ? "string" : "other",
460
+ range: a
461
+ };
462
+ });
463
+ const fallback = rest.find((a) => a.kind === "string");
464
+ const fallbackText = fallback === void 0 ? null : readString(fallback.code, 0)?.value ?? null;
465
+ if (textOf(sourceLocale, key) === void 0 && fallbackText === null) {
466
+ if (known) builder.note(call.start, `${call.callee}(${JSON.stringify(key)}): the key is in no ${sourceLocale} catalog; not rewritten`);
467
+ return;
468
+ }
469
+ const defined = define(env, key, fallbackText);
470
+ if ("problem" in defined) {
471
+ builder.note(call.start, `${defined.problem}; ${call.callee}() was not rewritten`);
472
+ return;
473
+ }
474
+ const values = callValues(env, key, rest.filter((a) => a.kind !== "string"), callee === "tc");
475
+ if ("problem" in values) {
476
+ builder.note(call.start, `${call.callee}(${JSON.stringify(key)}, …) passes ${values.problem}; not rewritten`, [defined.key]);
477
+ return;
478
+ }
479
+ const argRanges = rest.filter((a) => a.kind !== "string").map((a) => a.range);
480
+ for (const offset of consume.offsets) builder.consumed.add(offset);
481
+ if (blockOf(call.start)?.setup === false) plainSites = true;
482
+ builder.site(call, (r) => {
483
+ const src = (index) => {
484
+ const range = argRanges[index];
485
+ return r.src(range.start, range.end);
486
+ };
487
+ return `${r.fn.t}(${r.message(defined.key)}${values.values(src)})`;
488
+ }, {
489
+ keys: [defined.key],
490
+ uses: ["t"],
491
+ consume: consume.nodes,
492
+ ...markup === null ? {} : { markup }
493
+ });
494
+ };
495
+ const visit = (node) => {
496
+ if (node.type === "CallExpression") {
497
+ const callee = unwrap(node.callee);
498
+ const args = node.arguments.map((a) => ({
499
+ start: a.start,
500
+ end: a.end
501
+ }));
502
+ let name = null;
503
+ let known = false;
504
+ let consume = [];
505
+ if (callee.type === "Identifier") {
506
+ const binding = resolve(callee.name, callee.start);
507
+ if (binding !== null && tBindings.has(binding)) {
508
+ name = callee.name;
509
+ known = true;
510
+ consume = [callee];
511
+ }
512
+ } else {
513
+ const member = memberParts(callee);
514
+ if (member !== null && /^\$?(?:tc?|te|d|n|rt)$/.test(member.property)) {
515
+ const object = unwrap(member.object);
516
+ const thisCall = object.type === "ThisExpression" && member.property.startsWith("$");
517
+ const global = object.type === "MemberExpression" && memberParts(object)?.property === "global";
518
+ if (thisCall || global || member.property === "t" || member.property === "tc") {
519
+ name = member.property.startsWith("$") || thisCall || global ? member.property : member.property;
520
+ known = thisCall || global;
521
+ let root = object;
522
+ while (root.type === "MemberExpression") root = unwrap(root.object);
523
+ if (root.type === "Identifier") consume = [root];
524
+ }
525
+ }
526
+ }
527
+ if (name !== null && node.arguments.every((a) => a.type !== "SpreadElement")) site({
528
+ callee: name,
529
+ calleeStart: callee.start,
530
+ start: node.start,
531
+ end: node.end,
532
+ args
533
+ }, known, {
534
+ nodes: consume,
535
+ offsets: []
536
+ }, null);
537
+ const member = memberParts(callee);
538
+ const calleeName = identifierName(callee);
539
+ if (calleeName === "createI18n" || member?.property === "use" && node.arguments.some((a) => /^i18n$/i.test(identifierName(a) ?? ""))) builder.note(node.start, calleeName === "createI18n" ? "createI18n(): delete this setup once nothing uses vue-i18n; vi18n needs only its Vite plugin and vi18n.config.ts" : "app.use(i18n): remove it with the vue-i18n setup");
540
+ }
541
+ for (const [, child] of childEntries(node)) visit(child);
542
+ };
543
+ visit(program);
544
+ if (vue && regions !== null) {
545
+ for (const call of templateCalls) {
546
+ const bare = call.callee === "t" || call.callee === "tc";
547
+ if (bare) {
548
+ const binding = resolve(call.callee, scripts.find((s) => s.setup)?.start ?? 0);
549
+ if (binding === null || !tBindings.has(binding)) {
550
+ if (binding !== null) continue;
551
+ }
552
+ }
553
+ const markup = { quote: regions.attributes.find((a) => a.start <= call.start && call.start < a.end)?.quote ?? null };
554
+ site(call, true, {
555
+ nodes: [],
556
+ offsets: bare ? [call.calleeStart] : []
557
+ }, markup);
558
+ }
559
+ for (const m of text.matchAll(/<(i18n-t|i18n|I18nT|i18n-n|i18n-d)\b([^>]*)>/g)) {
560
+ if (!inTemplate(m.index)) continue;
561
+ const attrs = m[2];
562
+ const keypath = /\b(?:keypath|path)\s*=\s*"([^"]*)"|\b(?:keypath|path)\s*=\s*'([^']*)'/.exec(attrs);
563
+ const key = keypath?.[1] ?? keypath?.[2] ?? null;
564
+ const keys = [];
565
+ if (key !== null && "key" in define(env, key, null)) keys.push(key);
566
+ builder.note(m.index, `<${m[1]}> is not rewritten: render rich() from @menapo/vi18n in a component (see the rich text guide)${key === null ? "" : ""}`, keys);
567
+ }
568
+ for (const m of text.matchAll(/\sv-t\s*=\s*("[^"]*"|'[^']*')/g)) {
569
+ if (!inTemplate(m.index)) continue;
570
+ const value = m[1].slice(1, -1).trim();
571
+ const literal = /^'([^']*)'$|^"([^"]*)"$/.exec(value);
572
+ const key = literal?.[1] ?? literal?.[2] ?? null;
573
+ const keys = key !== null && "key" in define(env, key, null) ? [key] : [];
574
+ builder.note(m.index + 1, "v-t is not rewritten: write {{ t(…) }} with the message", keys);
575
+ }
576
+ for (const m of text.matchAll(/^<i18n\b[^>]*>/gm)) {
577
+ if (inTemplate(m.index)) continue;
578
+ builder.note(m.index, "an <i18n> block holds component-local messages, which were not migrated: move them into the locale catalogs");
579
+ }
580
+ }
581
+ return builder.finish();
582
+ };
583
+ return {
584
+ catalogExtensions: [
585
+ ".json",
586
+ ".yaml",
587
+ ".yml"
588
+ ],
589
+ catalogsRequired: true,
590
+ extensions: /* @__PURE__ */ new Set([...MODULE_EXTENSIONS, ".vue"]),
591
+ mentions: (text) => /vue-i18n|\$tc?\s*\(|\bt\s*\(|i18n/.test(text),
592
+ load(files, env) {
593
+ sourceLocale = env.config.sourceLocale;
594
+ for (const file of files) readCatalog(file, env);
595
+ },
596
+ planFile,
597
+ unusedKeys() {
598
+ return [...catalogs.get(sourceLocale)?.keys() ?? []].filter((key) => !referenced.has(key));
599
+ }
600
+ };
601
+ }
602
+ /** Whether a template offset is inside `{{ }}` or the value of a bound attribute or directive. */
603
+ function inExpression(text, offset, regions) {
604
+ const attribute = regions.attributes.find((a) => a.start <= offset && offset < a.end);
605
+ if (attribute !== void 0) {
606
+ const name = /([^\s"'<>/=]+)\s*=\s*$/.exec(text.slice(Math.max(0, attribute.start - 200), attribute.start - 1))?.[1];
607
+ return name !== void 0 && /^(?::|@|#|v-)/.test(name);
608
+ }
609
+ const open = text.lastIndexOf("{{", offset);
610
+ if (open === -1) return false;
611
+ return text.lastIndexOf("}}", offset) < open;
612
+ }
613
+ /** A call at `start` whose `(` ends at `open`: argument ranges as written. */
614
+ function lexicalCall(text, start, callee, open) {
615
+ const close = skipBalanced(text, open - 1);
616
+ if (close === null) return null;
617
+ const args = [];
618
+ let at = skipSpace(text, open);
619
+ while (at < close - 1) {
620
+ const end = argumentEnd(text, at);
621
+ if (end === null || end > close - 1) return null;
622
+ args.push({
623
+ start: at,
624
+ end
625
+ });
626
+ at = skipSpace(text, end + 1);
627
+ if (text[end] === ")") break;
628
+ }
629
+ return {
630
+ callee,
631
+ calleeStart: start,
632
+ start,
633
+ end: close,
634
+ args
635
+ };
636
+ }
637
+ /** Splits `a, b, c` at top-level commas. */
638
+ function splitTopLevel(code) {
639
+ const out = [];
640
+ let at = 0;
641
+ while (at < code.length) {
642
+ const end = argumentEnd(code + ")", at);
643
+ if (end === null) break;
644
+ const part = code.slice(at, end).trim();
645
+ if (part !== "") out.push(part);
646
+ at = end + 1;
647
+ }
648
+ return out;
649
+ }
650
+ //#endregion
651
+ export { createVueI18nAdapter };