@menapo/vi18n 0.0.0-stage → 0.1.0

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-D34P-S--.js +1 -0
  6. package/dist/check-DyrAbv73.js +201 -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-B5Mz6WJm.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-B7p40M__.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-DGsMbcTZ.js +103 -0
  25. package/dist/extract-Dujlsgnk.js +298 -0
  26. package/dist/formats-KPIg-08V.js +781 -0
  27. package/dist/i18next-CrUnKFfh.js +1596 -0
  28. package/dist/i18next-Rgo-v9l0.js +1 -0
  29. package/dist/import-Cwdhcywt.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-BecCTvKi.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui--PcOOYry.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-Cq1Nuu-Q.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-Cf0T7qPW.js +28 -0
  43. package/dist/merge-driver-CpiiSVIc.js +422 -0
  44. package/dist/messages-Cxa-5tR0.js +166 -0
  45. package/dist/migrate-CntqvrW4.js +1 -0
  46. package/dist/migrate-dPgq9uzy.js +1484 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DsaHKHEt.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C7g11THL.js +2031 -0
  52. package/dist/pipeline-wxS0SApG.js +1 -0
  53. package/dist/project-Dwx7iPm6.js +381 -0
  54. package/dist/project-pXipT8iG.js +1046 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-CaBVwJ9u.js +519 -0
  58. package/dist/restore-Btq66Tid.js +175 -0
  59. package/dist/rewrite-CiTRAICl.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-DYD_VXKy.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-BDkykMjb.js +11823 -0
  68. package/dist/server-qv3mz70d.js +10485 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DV3o8QD7.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-DZArW5aE.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-CcVzger_.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-DZR3MkSA.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-BlyLGlOa.js +28 -0
  93. package/dist/vite-scan-B_YW4X_a.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-CEMnpv7k.js +651 -0
  97. package/dist/which-C6PFI1Dm.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,519 @@
1
+ import { o as isDiagnosticError } from "./types-D3QGM8XO.js";
2
+ import { n as printICU } from "./codec-DuvU2VQy.js";
3
+ import { l as langFromId, u as parseModule } from "./worker-84U6uYB3.js";
4
+ import { a as attributeExpression, b as stringValue, g as jsxNameNodes, h as jsxName, i as analyzeModule, l as identifierName, 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-CiTRAICl.js";
5
+ import { a as annotateHandlers, g as formatjsId, h as formatjsAstToIcu, l as reactTypes, t as MODULE_EXTENSIONS, v as parseJson } from "./i18next-CrUnKFfh.js";
6
+ import { extname } from "node:path";
7
+ import { readFileSync } from "node:fs";
8
+ //#region src/cli/commands/migrate/react-intl.ts
9
+ /**
10
+ * `vi18n migrate --from react-intl` (INTERNALS §2.14): react-intl and
11
+ * `@formatjs/intl`.
12
+ *
13
+ * Messages are ICU already. The source text is the code's `defaultMessage`
14
+ * (the source catalog's text where a descriptor has none); translations are
15
+ * the catalogs' texts by id: an explicit `id`, else the id
16
+ * `babel-plugin-formatjs` generates (`[sha512:contenthash:base64:6]`).
17
+ * Catalogs are JSON: `{ id: "text" }`, `{ id: { defaultMessage | message |
18
+ * string, description } }`, or `formatjs compile --ast` output.
19
+ * `<FormattedMessage>` and `intl.formatMessage()` become `t()` (`rich()`
20
+ * when values hold elements or tag handlers), `defineMessages` /
21
+ * `defineMessage` become `msg()`, `description` a `context` note.
22
+ * `useIntl()` bindings, imports and `<IntlProvider>` go once unused.
23
+ */
24
+ function isOldSource(source) {
25
+ return source === "react-intl" || source.startsWith("react-intl/") || source === "@formatjs/intl";
26
+ }
27
+ /** `fmt` counterparts of the formatting components and `intl.format*` methods. */
28
+ const FORMATTERS = {
29
+ FormattedNumber: "fmt.number(value, options)",
30
+ FormattedDate: "fmt.date(value, options)",
31
+ FormattedTime: "fmt.time(value, options)",
32
+ FormattedDateParts: "fmt.date(value, options)",
33
+ FormattedTimeParts: "fmt.time(value, options)",
34
+ FormattedRelativeTime: "fmt.relative(value, options)",
35
+ FormattedList: "fmt.list(values, options)",
36
+ FormattedDisplayName: "Intl.DisplayNames",
37
+ FormattedPlural: "an ICU plural: t(\"{n, plural, one {…} other {…}}\", { n })",
38
+ formatNumber: "fmt.number",
39
+ formatDate: "fmt.date",
40
+ formatTime: "fmt.time",
41
+ formatRelativeTime: "fmt.relative",
42
+ formatList: "fmt.list",
43
+ formatDisplayName: "Intl.DisplayNames",
44
+ formatPlural: "an ICU plural"
45
+ };
46
+ function createReactIntlAdapter() {
47
+ /** locale → id → text. */
48
+ const catalogs = /* @__PURE__ */ new Map();
49
+ const referenced = /* @__PURE__ */ new Set();
50
+ let sourceLocale = "";
51
+ const readCatalog = (file, env) => {
52
+ let text;
53
+ try {
54
+ text = readFileSync(file.file, "utf8");
55
+ } catch (error) {
56
+ env.report("catalog-parse", `cannot read: ${error.message}`, { file: file.file }, "error");
57
+ return;
58
+ }
59
+ const parsed = parseJson(text);
60
+ if (parsed.problem !== null) {
61
+ env.report("catalog-parse", parsed.problem.message, {
62
+ file: file.file,
63
+ line: parsed.problem.line
64
+ }, "error");
65
+ return;
66
+ }
67
+ const value = parsed.value;
68
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
69
+ env.report("catalog-parse", "a react-intl catalog is an object of messages by id", { file: file.file }, "error");
70
+ return;
71
+ }
72
+ let texts = catalogs.get(file.locale);
73
+ if (texts === void 0) catalogs.set(file.locale, texts = /* @__PURE__ */ new Map());
74
+ const lines = text.split("\n");
75
+ const lineOf = (id) => {
76
+ const needle = `${JSON.stringify(id)}:`;
77
+ return lines.findIndex((l) => l.includes(needle)) + 1;
78
+ };
79
+ for (const [id, entry] of Object.entries(value)) {
80
+ const line = lineOf(id);
81
+ let message = entry;
82
+ let description = null;
83
+ if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
84
+ const record = entry;
85
+ message = record.defaultMessage ?? record.message ?? record.string ?? record.translation;
86
+ description = describe(record.description);
87
+ }
88
+ if (typeof message === "string") texts.set(id, {
89
+ text: { icu: message },
90
+ description,
91
+ file: file.file,
92
+ line
93
+ });
94
+ else if (Array.isArray(message)) {
95
+ const nodes = formatjsAstToIcu(message);
96
+ texts.set(id, {
97
+ text: nodes === null ? {
98
+ unconverted: JSON.stringify(message),
99
+ reason: "a compiled message vi18n cannot print",
100
+ file: file.file,
101
+ line
102
+ } : { icu: printICU(nodes) },
103
+ description,
104
+ file: file.file,
105
+ line
106
+ });
107
+ } else env.report("migrate-unconverted", `${id} holds no message text; it was skipped`, {
108
+ file: file.file,
109
+ line
110
+ });
111
+ }
112
+ };
113
+ /** A message for a descriptor; its registry key, or why there is none. */
114
+ const define = (env, descriptor, file, line) => {
115
+ const named = descriptor.id !== null;
116
+ if (descriptor.id === null && descriptor.message === null) return { problem: "a descriptor without id or defaultMessage" };
117
+ const id = descriptor.id ?? formatjsId(descriptor.message, descriptor.hashed ?? descriptor.description);
118
+ referenced.add(id);
119
+ const known = env.message(id);
120
+ if (known !== void 0) {
121
+ let same = descriptor.message === null;
122
+ try {
123
+ same ||= env.codec.canonicalize(descriptor.message) === known.canonical;
124
+ } catch {
125
+ same = false;
126
+ }
127
+ if (!same) return { problem: `id ${id} has another defaultMessage at an earlier site (${JSON.stringify(known.source)})` };
128
+ return { key: id };
129
+ }
130
+ const fromCatalog = catalogs.get(sourceLocale)?.get(id);
131
+ let source = descriptor.message;
132
+ if (source === null) {
133
+ if (fromCatalog === void 0) return { problem: `${id} has no defaultMessage and is in no ${sourceLocale} catalog` };
134
+ if (!("icu" in fromCatalog.text)) return { problem: `${id}: ${fromCatalog.text.reason}` };
135
+ source = fromCatalog.text.icu;
136
+ } else if (fromCatalog !== void 0 && "icu" in fromCatalog.text && fromCatalog.text.icu !== source) env.report("migrate-manual", `${id}: the ${sourceLocale} catalog has ${JSON.stringify(fromCatalog.text.icu)}, the code's defaultMessage ${JSON.stringify(source)}; the defaultMessage was kept`, {
137
+ file: fromCatalog.file,
138
+ line: fromCatalog.line
139
+ });
140
+ const translations = /* @__PURE__ */ new Map();
141
+ for (const [locale, texts] of catalogs) {
142
+ if (locale === sourceLocale) continue;
143
+ const translated = texts.get(id);
144
+ if (translated !== void 0) translations.set(locale, translated.text);
145
+ }
146
+ return env.define({
147
+ key: id,
148
+ named,
149
+ source,
150
+ context: descriptor.description ?? fromCatalog?.description ?? null,
151
+ hint: null,
152
+ translations,
153
+ file: descriptor.message === null && fromCatalog !== void 0 ? fromCatalog.file : file,
154
+ line: descriptor.message === null && fromCatalog !== void 0 ? fromCatalog.line : line
155
+ }) ? { key: id } : { problem: `${id} could not be converted` };
156
+ };
157
+ const planModule = (file, text, env) => {
158
+ let program;
159
+ try {
160
+ program = parseModule(text, langFromId(file), file).program;
161
+ } catch (error) {
162
+ if (!isDiagnosticError(error)) throw error;
163
+ env.diagnostics.push({
164
+ ...error.diagnostic,
165
+ file: env.rel(file)
166
+ });
167
+ return null;
168
+ }
169
+ const info = analyzeModule(program);
170
+ const builder = new ModuleBuilder({
171
+ file,
172
+ text,
173
+ info,
174
+ isOldSource
175
+ });
176
+ const tsx = extname(file) === ".tsx";
177
+ const lineOf = (offset) => text.slice(0, offset).split("\n").length;
178
+ const imported = (name) => {
179
+ const locals = /* @__PURE__ */ new Set();
180
+ for (const declaration of info.imports) {
181
+ if (!isOldSource(declaration.source)) continue;
182
+ for (const s of declaration.specifiers) if (s.imported === name) locals.add(s.local);
183
+ }
184
+ return locals;
185
+ };
186
+ const has = (name, local) => imported(name).has(local);
187
+ const useIntl = imported("useIntl");
188
+ const intlBindings = /* @__PURE__ */ new Set();
189
+ for (const binding of info.bindings) {
190
+ const init = binding.declarator?.init;
191
+ if (init === null || init === void 0) continue;
192
+ const call = unwrap(init);
193
+ const callee = call.type === "CallExpression" ? identifierName(call.callee) : null;
194
+ if (callee === null || !useIntl.has(callee)) continue;
195
+ builder.hooks.push(binding);
196
+ intlBindings.add(binding);
197
+ }
198
+ /** Locals bound to `defineMessages()` / `defineMessage()` results: their members are `msg()` refs now. */
199
+ const messageTables = /* @__PURE__ */ new Set();
200
+ for (const binding of info.bindings) {
201
+ const init = binding.declarator?.init;
202
+ if (init === null || init === void 0) continue;
203
+ const call = unwrap(init);
204
+ const callee = call.type === "CallExpression" ? identifierName(call.callee) : null;
205
+ if (callee !== null && (has("defineMessages", callee) || has("defineMessage", callee))) messageTables.add(binding.name);
206
+ }
207
+ const relativeImports = /* @__PURE__ */ new Set();
208
+ for (const declaration of info.imports) {
209
+ if (!declaration.source.startsWith(".")) continue;
210
+ for (const s of declaration.specifiers) relativeImports.add(s.local);
211
+ }
212
+ /** A descriptor object literal, or null when a field is not static. */
213
+ const readDescriptor = (node) => {
214
+ const props = objectProperties(node);
215
+ if (props === null || props.spreads.length > 0) return null;
216
+ const field = (name) => {
217
+ const property = props.properties.get(name);
218
+ if (property === void 0) return null;
219
+ const value = unwrap(property.value);
220
+ if (name === "description" && value.type === "ObjectExpression") return describeNode(value)?.note;
221
+ const s = stringValue(value);
222
+ return s === null ? void 0 : s;
223
+ };
224
+ const id = field("id");
225
+ const message = field("defaultMessage");
226
+ const description = field("description");
227
+ if (id === void 0 || message === void 0 || description === void 0) return null;
228
+ const object = props.properties.get("description");
229
+ return {
230
+ id,
231
+ message,
232
+ description,
233
+ hashed: object !== void 0 && unwrap(object.value).type === "ObjectExpression" ? describeNode(unwrap(object.value))?.json ?? null : description
234
+ };
235
+ };
236
+ /** Whether an expression is a message the migrated code holds as a `msg()` ref. */
237
+ const isMessageRef = (node) => {
238
+ let root = unwrap(node);
239
+ while (root.type === "MemberExpression") root = unwrap(root.object);
240
+ const name = identifierName(root);
241
+ return name !== null && (messageTables.has(name) || relativeImports.has(name));
242
+ };
243
+ /** Whether values need `rich()`: an element or a function among them. */
244
+ const needsRich = (values, key) => {
245
+ if (key !== null) {
246
+ const message = env.message(key);
247
+ if (message !== void 0 && hasTag(env.codec.parse(message.canonical))) return true;
248
+ }
249
+ if (values === null) return false;
250
+ const props = objectProperties(values);
251
+ if (props === null) return false;
252
+ return props.order.some((p) => {
253
+ if (p.type === "SpreadElement") return false;
254
+ const value = unwrap(p.value);
255
+ return value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression" || value.type === "JSXElement" || value.type === "JSXFragment";
256
+ });
257
+ };
258
+ const call = (r, message, values, rich, types) => {
259
+ const first = "key" in message ? r.message(message.key) : r.src(message.ref.start, message.ref.end);
260
+ const rest = values === null ? "" : `, ${r.src(values.start, values.end)}`;
261
+ if (!rich) return `${r.fn.t}(${first}${rest})`;
262
+ return `${r.fn.rich}${types === null ? "" : `<${types.node}>`}(${first}${rest})`;
263
+ };
264
+ const formattedMessage = (element) => {
265
+ const attributes = jsxAttributes(element);
266
+ const attr = (name) => attributes.named.get(name)?.value ?? null;
267
+ if (attributes.named.has("children") || element.children.length > 0) {
268
+ builder.note(element.start, "<FormattedMessage> with a render function child was not rewritten: call t() and render the string");
269
+ return;
270
+ }
271
+ let message = null;
272
+ if (attributes.spreads.length === 1 && !attributes.named.has("id") && !attributes.named.has("defaultMessage")) {
273
+ const spread = attributes.spreads[0].argument;
274
+ if (isMessageRef(spread)) message = { ref: spread };
275
+ } else if (attributes.spreads.length === 0) {
276
+ const read = (name) => attributes.named.has(name) ? attributeString(attr(name)) ?? void 0 : null;
277
+ let description = read("description");
278
+ const descriptionExpr = attributeExpression(attr("description"));
279
+ let hashed = description;
280
+ if (description === void 0 && descriptionExpr !== null && unwrap(descriptionExpr).type === "ObjectExpression") {
281
+ const described = describeNode(unwrap(descriptionExpr));
282
+ description = described?.note;
283
+ hashed = described?.json;
284
+ }
285
+ const id = read("id");
286
+ const defaultMessage = read("defaultMessage");
287
+ if (id !== void 0 && defaultMessage !== void 0 && description !== void 0) {
288
+ const defined = define(env, {
289
+ id,
290
+ message: defaultMessage,
291
+ description,
292
+ hashed: hashed ?? null
293
+ }, file, lineOf(element.start));
294
+ if ("problem" in defined) {
295
+ builder.note(element.start, `${defined.problem}; <FormattedMessage> was not rewritten`);
296
+ return;
297
+ }
298
+ message = defined;
299
+ }
300
+ }
301
+ if (message === null) {
302
+ builder.note(element.start, "<FormattedMessage> with a computed id or defaultMessage was not rewritten");
303
+ return;
304
+ }
305
+ const values = attributeExpression(attr("values"));
306
+ if (attributes.named.has("values") && (values === null || objectProperties(values) === null)) {
307
+ builder.note(element.start, "<FormattedMessage values={…}> is not an object literal; not rewritten");
308
+ return;
309
+ }
310
+ const tagName = attributes.named.has("tagName") ? attributeString(attr("tagName")) : null;
311
+ if (attributes.named.has("tagName") && (tagName === null || !/^[a-z][a-z0-9-]*$/.test(tagName))) {
312
+ builder.note(element.start, "<FormattedMessage tagName={Component}> was not rewritten: wrap the t() call yourself");
313
+ return;
314
+ }
315
+ const rich = needsRich(values, "key" in message ? message.key : null);
316
+ const types = rich && tsx ? reactTypes(builder) : null;
317
+ if (types !== null && values !== null) annotateHandlers(builder, values, types.node);
318
+ const nested = inJsxChildren(info, element);
319
+ const resolved = message;
320
+ builder.site(element, (r) => {
321
+ const code = call(r, resolved, values, rich, types);
322
+ if (tagName !== null) return `<${tagName}>{${code}}</${tagName}>`;
323
+ return nested ? `{${code}}` : code;
324
+ }, {
325
+ keys: "key" in message ? [message.key] : [],
326
+ uses: [rich ? "rich" : "t"],
327
+ consume: jsxNameNodes(element)
328
+ });
329
+ };
330
+ /** `intl.formatMessage(descriptor, values)` and a destructured `formatMessage(…)`. */
331
+ const formatMessage = (node) => {
332
+ const callee = unwrap(node.callee);
333
+ const member = memberParts(callee);
334
+ let consume = [];
335
+ if (member !== null && member.property === "formatMessage") {
336
+ const object = unwrap(member.object);
337
+ if (object.type === "Identifier") consume = [object];
338
+ } else if (callee.type === "Identifier" && [...intlBindings].some((b) => b.name === callee.name)) consume = [callee];
339
+ else return false;
340
+ const args = node.arguments;
341
+ const first = args[0];
342
+ const values = args[1] ?? null;
343
+ if (first === void 0 || first.type === "SpreadElement" || args.length > 2) return false;
344
+ let message = null;
345
+ if (unwrap(first).type === "ObjectExpression") {
346
+ const descriptor = readDescriptor(unwrap(first));
347
+ if (descriptor === null) {
348
+ builder.note(node.start, "formatMessage() with a computed descriptor was not rewritten");
349
+ return true;
350
+ }
351
+ const defined = define(env, descriptor, file, lineOf(node.start));
352
+ if ("problem" in defined) {
353
+ builder.note(node.start, `${defined.problem}; formatMessage() was not rewritten`);
354
+ return true;
355
+ }
356
+ message = defined;
357
+ } else if (isMessageRef(first)) message = { ref: first };
358
+ if (message === null) {
359
+ builder.note(node.start, "formatMessage() with a descriptor vi18n migrate cannot trace was not rewritten: pass a msg() descriptor to t()");
360
+ return true;
361
+ }
362
+ if (values !== null && unwrap(values).type !== "ObjectExpression" && needsRich(null, "key" in message ? message.key : null)) {
363
+ builder.note(node.start, "formatMessage() with rich values that are not an object literal was not rewritten");
364
+ return true;
365
+ }
366
+ const rich = needsRich(values, "key" in message ? message.key : null);
367
+ const types = rich && tsx ? reactTypes(builder) : null;
368
+ if (types !== null && values !== null) annotateHandlers(builder, values, types.node);
369
+ const resolved = message;
370
+ builder.site(node, (r) => call(r, resolved, values, rich, types), {
371
+ keys: "key" in message ? [message.key] : [],
372
+ uses: [rich ? "rich" : "t"],
373
+ consume
374
+ });
375
+ return true;
376
+ };
377
+ /** `defineMessages({ … })` → `{ key: msg(…) }`, `defineMessage({ … })` → `msg(…)`. */
378
+ const defineCall = (node, kind) => {
379
+ const callee = unwrap(node.callee);
380
+ const arg = node.arguments[0];
381
+ if (arg === void 0) return;
382
+ if (kind === "defineMessage") {
383
+ const descriptor = readDescriptor(unwrap(arg));
384
+ if (descriptor === null) {
385
+ builder.note(node.start, "defineMessage() with a computed descriptor was not rewritten");
386
+ return;
387
+ }
388
+ const defined = define(env, descriptor, file, lineOf(node.start));
389
+ if ("problem" in defined) {
390
+ builder.note(node.start, `${defined.problem}; defineMessage() was not rewritten`);
391
+ return;
392
+ }
393
+ builder.site(node, (r) => `${r.fn.msg}(${r.message(defined.key)})`, {
394
+ keys: [defined.key],
395
+ uses: ["msg"],
396
+ consume: [callee]
397
+ });
398
+ return;
399
+ }
400
+ const props = objectProperties(arg);
401
+ if (props === null || props.spreads.length > 0) {
402
+ builder.note(node.start, "defineMessages() with spreads or computed keys was not rewritten");
403
+ return;
404
+ }
405
+ const entries = [];
406
+ for (const property of props.order) {
407
+ const descriptor = readDescriptor(unwrap(property.value));
408
+ const defined = descriptor === null ? null : define(env, descriptor, file, lineOf(property.start));
409
+ if (defined === null || "problem" in defined) {
410
+ builder.note(property.start, `defineMessages(): ${defined === null ? "a computed descriptor" : defined.problem}; the table was not rewritten`);
411
+ return;
412
+ }
413
+ entries.push({
414
+ property,
415
+ key: defined.key
416
+ });
417
+ }
418
+ builder.site(node, (r) => {
419
+ const lines = entries.map(({ property, key }) => {
420
+ const name = text.slice(property.key.start, property.key.end);
421
+ return `${property.computed === true ? `[${name}]` : name}: ${r.fn.msg}(${r.message(key)})`;
422
+ });
423
+ if (!text.slice(arg.start, arg.end).includes("\n")) return `{ ${lines.join(", ")} }`;
424
+ const indent = /\n([ \t]*)\S/.exec(text.slice(arg.start, arg.end))?.[1] ?? " ";
425
+ const closing = /\n([ \t]*)\}$/.exec(text.slice(arg.start, arg.end))?.[1] ?? "";
426
+ return `{\n${lines.map((l) => `${indent}${l},`).join("\n")}\n${closing}}`;
427
+ }, {
428
+ keys: entries.map((e) => e.key),
429
+ uses: ["msg"],
430
+ consume: [callee]
431
+ });
432
+ };
433
+ const unwrapProvider = (element) => {
434
+ const opening = element.openingElement;
435
+ const closing = element.closingElement;
436
+ const children = element.children.filter((c) => !(c.type === "JSXText" && c.value.trim() === ""));
437
+ builder.consumeRange(opening);
438
+ builder.consume(jsxNameNodes(element));
439
+ const nested = inJsxChildren(info, element);
440
+ builder.replace(element, (r) => {
441
+ if (closing === null) return nested ? "" : "null";
442
+ if (nested) return r.src(opening.end, closing.start).trim();
443
+ if (children.length === 1 && children[0].type === "JSXElement") return r.src(children[0].start, children[0].end);
444
+ return `<>${r.src(opening.end, closing.start)}</>`;
445
+ });
446
+ };
447
+ const visit = (node) => {
448
+ if (node.type === "JSXElement") {
449
+ const name = jsxName(node);
450
+ if (has("FormattedMessage", name)) formattedMessage(node);
451
+ else if (has("IntlProvider", name) || has("RawIntlProvider", name)) unwrapProvider(node);
452
+ else {
453
+ const imported_ = Object.keys(FORMATTERS).find((f) => has(f, name));
454
+ if (imported_ !== void 0) builder.note(node.start, `<${name}> is not rewritten: render ${FORMATTERS[imported_]} from @menapo/vi18n`);
455
+ }
456
+ } else if (node.type === "CallExpression") {
457
+ const callee = identifierName(node.callee);
458
+ if (callee !== null && has("defineMessages", callee)) defineCall(node, "defineMessages");
459
+ else if (callee !== null && has("defineMessage", callee)) defineCall(node, "defineMessage");
460
+ else if (callee !== null && has("injectIntl", callee)) builder.note(node.start, "injectIntl() injects intl as a prop: drop it and call t() from @menapo/vi18n");
461
+ else if (callee !== null && (has("createIntl", callee) || has("createIntlCache", callee))) builder.note(node.start, `${callee}(): use createTranslator(locale) or withLocale() from @menapo/vi18n/server`);
462
+ else if (!formatMessage(node)) {
463
+ const member = memberParts(node.callee);
464
+ if (member !== null && member.property in FORMATTERS && member.property.startsWith("format")) {
465
+ const object = identifierName(member.object);
466
+ if (object !== null && [...intlBindings].some((b) => b.name === object)) builder.note(node.start, `intl.${member.property}() is not rewritten: use ${FORMATTERS[member.property]} from @menapo/vi18n`);
467
+ }
468
+ }
469
+ }
470
+ for (const [, child] of childEntries(node)) visit(child);
471
+ };
472
+ visit(program);
473
+ return builder.finish();
474
+ };
475
+ return {
476
+ catalogExtensions: [".json"],
477
+ catalogsRequired: false,
478
+ extensions: MODULE_EXTENSIONS,
479
+ mentions: (text) => /react-intl|@formatjs\/intl|formatMessage/.test(text),
480
+ load(files, env) {
481
+ sourceLocale = env.config.sourceLocale;
482
+ for (const file of files) readCatalog(file, env);
483
+ },
484
+ planFile: planModule,
485
+ unusedKeys() {
486
+ return [...catalogs.get(sourceLocale)?.keys() ?? []].filter((id) => !referenced.has(id));
487
+ }
488
+ };
489
+ }
490
+ function hasTag(nodes) {
491
+ return nodes.some((node) => node.type === "tag" || (node.type === "plural" || node.type === "select") && node.options.some((o) => hasTag(o.value)));
492
+ }
493
+ /** A FormatJS `description` as one note: a string as is, an object as `key: value` pairs. */
494
+ function describe(value) {
495
+ if (typeof value === "string") return value === "" ? null : value;
496
+ if (typeof value !== "object" || value === null) return null;
497
+ const parts = Object.entries(value).map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
498
+ return parts.length === 0 ? null : parts.join("; ");
499
+ }
500
+ /** An object `description` as a note, and as the stable JSON FormatJS hashes. */
501
+ function describeNode(node) {
502
+ const props = objectProperties(node);
503
+ if (props === null || props.spreads.length > 0) return void 0;
504
+ const out = {};
505
+ for (const [key, property] of props.properties) {
506
+ const value = unwrap(property.value);
507
+ const s = stringValue(value);
508
+ if (s !== null) out[key] = s;
509
+ else if (value.type === "Literal") out[key] = value.value;
510
+ else return void 0;
511
+ }
512
+ const sorted = Object.fromEntries(Object.keys(out).sort().map((k) => [k, out[k]]));
513
+ return {
514
+ note: describe(out),
515
+ json: JSON.stringify(sorted)
516
+ };
517
+ }
518
+ //#endregion
519
+ export { createReactIntlAdapter };
@@ -0,0 +1,175 @@
1
+ import { s as parseIdScheme } from "./validate-BxpupdAf.js";
2
+ import { t as createCodec } from "./codec-DuvU2VQy.js";
3
+ import { d as parseCatalogBytes, m as updateCatalogFile } from "./library-D5CWvTQs.js";
4
+ import { t as VERSION } from "./version-BlyLGlOa.js";
5
+ import { c as sourceIndexOf } from "./units-CQ5Q99RC.js";
6
+ import { i as rootRelative, r as plural } from "./output-DsaHKHEt.js";
7
+ import { t as EXIT } from "./types-wSh9fCBY.js";
8
+ import { a as loadCatalogs, o as loadProject, p as targetLocales, t as CliExit, u as readErrors } from "./project-Dwx7iPm6.js";
9
+ import { n as applyRestore } from "./apply-Bre-lY9J.js";
10
+ import { basename, dirname } from "node:path";
11
+ import { promisify } from "node:util";
12
+ import { execFile } from "node:child_process";
13
+ //#region src/cli/commands/restore.ts
14
+ /**
15
+ * `vi18n restore --from <git-rev> [--locale l]… [--dry-run]` (PLAN §4.4,
16
+ * §5.19, INTERNALS §2.6.9): fills entries that are empty now from that
17
+ * revision's catalogs, by exact source text (generated ids) or manual id
18
+ * with the same source text. It undoes what `vi18n extract` rightly did
19
+ * when the code changed back: a reverted text, or a merge whose branch
20
+ * deleted an entry the other branch still uses. Only empty entries are
21
+ * filled, so nothing anyone translated since is overwritten.
22
+ */
23
+ const run = promisify(execFile);
24
+ /** Catalogs are text, but a large one at an old revision must not hit the default 1 MB buffer. */
25
+ const MAX_BUFFER = 1073741824;
26
+ function gitError(message) {
27
+ return new CliExit(EXIT.failure, [{
28
+ code: "restore-git",
29
+ severity: "error",
30
+ message
31
+ }]);
32
+ }
33
+ /** The revision as git sees it; a typo or a missing repository ends the command. */
34
+ async function verifyRevision(cwd, rev) {
35
+ try {
36
+ await run("git", [
37
+ "rev-parse",
38
+ "--verify",
39
+ "--quiet",
40
+ `${rev}^{commit}`
41
+ ], { cwd });
42
+ } catch (error) {
43
+ const failure = error;
44
+ if (failure.code === "ENOENT") throw gitError("git is not installed or not on PATH");
45
+ const stderr = String(failure.stderr ?? "").trim();
46
+ throw gitError(/not a git repository/i.test(stderr) ? `${cwd} is not inside a git repository` : `${rev} is not a commit of this repository`);
47
+ }
48
+ }
49
+ /** A file's bytes at a revision; null when it did not exist there. */
50
+ async function showAt(rev, path) {
51
+ try {
52
+ const { stdout } = await run("git", ["show", `${rev}:./${basename(path)}`], {
53
+ cwd: dirname(path),
54
+ encoding: "buffer",
55
+ maxBuffer: MAX_BUFFER
56
+ });
57
+ return stdout;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ function oldCatalog(bytes, options) {
63
+ const read = parseCatalogBytes(bytes, options);
64
+ return {
65
+ catalog: read.catalog,
66
+ error: read.diagnostics.find((diagnostic) => diagnostic.severity === "error") ?? null
67
+ };
68
+ }
69
+ async function restore(ctx, flags) {
70
+ const project = await loadProject(ctx);
71
+ const { config, codec, layout } = project;
72
+ const locales = targetLocales(config, flags.locales);
73
+ await verifyRevision(config.root, flags.from);
74
+ const loaded = await loadCatalogs(project);
75
+ const unreadable = readErrors(loaded.diagnostics);
76
+ if (unreadable.length > 0) throw new CliExit(EXIT.failure, unreadable);
77
+ const sourceLoaded = loaded.set.get(config.sourceLocale);
78
+ const context = {
79
+ layout,
80
+ codec,
81
+ sourceIndex: sourceIndexOf(sourceLoaded?.catalog ?? null, layout, codec)
82
+ };
83
+ let oldSource = null;
84
+ if (layout === "id" && sourceLoaded !== void 0) {
85
+ const bytes = await showAt(flags.from, sourceLoaded.path);
86
+ if (bytes !== null) {
87
+ const read = oldCatalog(bytes, {
88
+ locale: config.sourceLocale,
89
+ layout,
90
+ isSourceLocale: true
91
+ });
92
+ if (read.error === null) oldSource = read.catalog;
93
+ }
94
+ }
95
+ const data = {
96
+ from: flags.from,
97
+ dryRun: flags.dryRun,
98
+ locales: [],
99
+ restored: []
100
+ };
101
+ let failed = false;
102
+ for (const locale of locales) {
103
+ const target = loaded.set.get(locale);
104
+ if (target === void 0 || target.catalog === null) continue;
105
+ const file = rootRelative(config.root, target.path);
106
+ const bytes = await showAt(flags.from, target.path);
107
+ if (bytes === null) {
108
+ ctx.out.note(`vi18n restore: ${file} did not exist at ${flags.from}`);
109
+ continue;
110
+ }
111
+ const old = oldCatalog(bytes, {
112
+ locale,
113
+ layout,
114
+ isSourceLocale: false
115
+ });
116
+ if (old.error !== null) {
117
+ failed = true;
118
+ ctx.out.report([{
119
+ ...old.error,
120
+ code: "restore-git",
121
+ file: target.path,
122
+ locale,
123
+ message: `${file} at ${flags.from} cannot be read: ${old.error.message}`
124
+ }]);
125
+ continue;
126
+ }
127
+ const length = parseIdScheme(old.catalog.header.idScheme ?? "")?.length ?? codec.idLength;
128
+ const oldCodec = length === codec.idLength ? codec : createCodec(length);
129
+ const oldContext = {
130
+ layout,
131
+ codec: oldCodec,
132
+ sourceIndex: sourceIndexOf(oldSource, layout, oldCodec)
133
+ };
134
+ const apply = (catalog) => applyRestore({
135
+ catalog,
136
+ context,
137
+ old: old.catalog,
138
+ oldContext,
139
+ locale
140
+ });
141
+ let outcome = null;
142
+ if (flags.dryRun) outcome = apply(target.catalog);
143
+ else await updateCatalogFile(target.path, target.hash, (current) => {
144
+ if (current === null) return null;
145
+ outcome = apply(current);
146
+ return outcome.catalog === current ? null : outcome.catalog;
147
+ }, {
148
+ locale,
149
+ layout,
150
+ isSourceLocale: false,
151
+ version: VERSION
152
+ });
153
+ if (outcome === null) continue;
154
+ ctx.out.report(outcome.diagnostics.map((diagnostic) => ({
155
+ ...diagnostic,
156
+ file: target.path
157
+ })));
158
+ data.locales.push({
159
+ locale,
160
+ file,
161
+ restored: outcome.restored.length
162
+ });
163
+ for (const entry of outcome.restored) data.restored.push({
164
+ locale,
165
+ ...entry
166
+ });
167
+ ctx.out.result(`${locale}: ${plural(outcome.restored.length, "entry", "entries")} ${flags.dryRun ? "to restore" : "restored"} from ${flags.from}`);
168
+ }
169
+ return {
170
+ exitCode: failed ? EXIT.failure : EXIT.ok,
171
+ data
172
+ };
173
+ }
174
+ //#endregion
175
+ export { restore };