langsys-js-react 0.6.7 → 0.7.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.
package/dist/vite.mjs ADDED
@@ -0,0 +1,621 @@
1
+ import { transformAsync } from '@babel/core';
2
+ import { derivePlaceholderNames, NON_TRANSLATABLE_ELEMENTS, stripC0Controls, TRANSLATABLE_ATTRIBUTES } from 'langsys-js-typescript/pure';
3
+
4
+ // src/transform/run.ts
5
+ function unwrap(e) {
6
+ let n = e;
7
+ while (n.type === "TSAsExpression" || n.type === "TSNonNullExpression" || n.type === "TSSatisfiesExpression" || n.type === "TSTypeAssertion" || n.type === "ParenthesizedExpression") {
8
+ n = n.expression;
9
+ }
10
+ return n;
11
+ }
12
+ function segments(e) {
13
+ const n = unwrap(e);
14
+ if (n.type === "Identifier") return [n.name];
15
+ if (n.type === "ThisExpression") return [];
16
+ if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
17
+ if (n.computed || n.property.type !== "Identifier") return null;
18
+ const head = segments(n.object);
19
+ return head ? [...head, n.property.name] : null;
20
+ }
21
+ return null;
22
+ }
23
+ function shapeOf(e) {
24
+ const n = unwrap(e);
25
+ if (n.type === "Identifier") return { identifier: n.name };
26
+ if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
27
+ const segs = segments(n);
28
+ return segs && segs.length ? { member: segs } : { other: "computed" };
29
+ }
30
+ if (n.type === "CallExpression" || n.type === "OptionalCallExpression") {
31
+ const args = n.arguments;
32
+ if (args.some((x) => x.type === "SpreadElement" || x.type === "ArgumentPlaceholder")) return { other: "call-multi" };
33
+ const callee = segments(n.callee);
34
+ return { call: { callee: callee ? callee.join(".") : "", args: args.map((x) => shapeOf(x)) } };
35
+ }
36
+ if (n.type === "BinaryExpression" || n.type === "LogicalExpression") return { other: "binary" };
37
+ if (n.type === "ConditionalExpression") return { other: "conditional" };
38
+ if (n.type === "TemplateLiteral") return { other: "template" };
39
+ return { other: n.type };
40
+ }
41
+ function derivable(shape) {
42
+ if ("identifier" in shape || "member" in shape) return true;
43
+ if ("call" in shape) return shape.call.args.length === 1 && derivable(shape.call.args[0]);
44
+ return false;
45
+ }
46
+ function nameAll(expressions, taken = []) {
47
+ const explicit = [...taken];
48
+ const shapes = expressions.map((x) => shapeOf(x.expression));
49
+ const names = derivePlaceholderNames([
50
+ ...explicit.map((name) => ({ shape: { other: "explicit" }, explicit: name })),
51
+ ...shapes.map((shape) => ({ shape }))
52
+ ]).slice(explicit.length);
53
+ return expressions.map((x, i) => ({ ...x, name: names[i], unnameable: !derivable(shapes[i]) }));
54
+ }
55
+
56
+ // src/transform/babel.ts
57
+ var SOURCE = "langsys-js-react";
58
+ var TARGETS = /* @__PURE__ */ new Set(["Translate", "Phrase"]);
59
+ var VERBATIM = "DontTranslate";
60
+ var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "plaintext"]);
61
+ var TEXT_ONLY = /* @__PURE__ */ new Set(["textarea", "title", "option"]);
62
+ var VALUE_ELEMENTS = /* @__PURE__ */ new Set(["button"]);
63
+ var VALUE_INPUT_TYPES = /* @__PURE__ */ new Set(["submit", "button"]);
64
+ var UNNAMED = "data-ls-unnamed";
65
+ var EXPLICIT = /%([A-Za-z_][A-Za-z0-9_]*)%/g;
66
+ var BRACED = /\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*[,}]/g;
67
+ function langsysPlaceholders({ types: T }) {
68
+ const sourceOf = (state, node) => state.file.code.slice(node.start ?? 0, node.end ?? 0);
69
+ const warn = (state, n) => {
70
+ if (!n.unnameable) return;
71
+ const loc = n.expression.loc?.start;
72
+ state.meta.warnings.push({ line: loc?.line ?? null, column: loc?.column ?? null, source: n.source, name: n.name });
73
+ };
74
+ const explicitNames = (texts, params, written = EXPLICIT) => {
75
+ const out = /* @__PURE__ */ new Set();
76
+ for (const s of texts) for (const m of s.matchAll(written)) out.add(m[1]);
77
+ if (params && params.type === "ObjectExpression") {
78
+ for (const p of params.properties) {
79
+ if (p.type === "ObjectProperty" && !p.computed) {
80
+ if (p.key.type === "Identifier") out.add(p.key.name);
81
+ else if (p.key.type === "StringLiteral") out.add(p.key.value);
82
+ }
83
+ }
84
+ }
85
+ return out;
86
+ };
87
+ const optOut = (tag, opening) => {
88
+ if (NON_TRANSLATABLE_ELEMENTS.includes(tag)) return "shown";
89
+ let unknown = false;
90
+ for (const a of opening.attributes) {
91
+ if (a.type !== "JSXAttribute" || a.name.type !== "JSXIdentifier") continue;
92
+ if (a.name.name !== "translate" && a.name.name !== "data-notrans") continue;
93
+ const text = attributeText(a);
94
+ if (text === void 0) unknown = true;
95
+ else if (a.name.name === "translate" ? text.toLowerCase() === "no" : !["false", "0"].includes(text.trim().toLowerCase())) {
96
+ return "shown";
97
+ }
98
+ }
99
+ return unknown ? "unknown" : null;
100
+ };
101
+ const readsAsText = (tag, opening, name) => {
102
+ if (TRANSLATABLE_ATTRIBUTES.includes(name)) return true;
103
+ if (name !== "value") return false;
104
+ if (VALUE_ELEMENTS.has(tag)) return true;
105
+ if (tag !== "input") return false;
106
+ let type = "";
107
+ for (const a of opening.attributes) {
108
+ if (a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "type") type = attributeText(a);
109
+ }
110
+ return type === void 0 ? void 0 : VALUE_INPUT_TYPES.has(type.toLowerCase());
111
+ };
112
+ const isChildren = (e) => {
113
+ while (e.type === "TSAsExpression" || e.type === "TSSatisfiesExpression" || e.type === "TSNonNullExpression" || e.type === "TSTypeAssertion" || e.type === "ParenthesizedExpression") {
114
+ e = e.expression;
115
+ }
116
+ return e.type === "Identifier" && e.name === "children" || (e.type === "MemberExpression" || e.type === "OptionalMemberExpression") && !e.computed && e.property.type === "Identifier" && e.property.name === "children";
117
+ };
118
+ const holdsJsx = (e) => {
119
+ let jsx = false;
120
+ T.traverseFast(e, (n) => {
121
+ if (n.type === "JSXElement" || n.type === "JSXFragment") jsx = true;
122
+ });
123
+ return jsx;
124
+ };
125
+ const literalOf = (e) => e.type === "StringLiteral" || e.type === "NumericLiteral" ? String(e.value) : e.type === "TemplateLiteral" && !e.expressions.length ? e.quasis[0].value.cooked ?? void 0 : void 0;
126
+ const isFragment = (state, name) => name.type === "JSXIdentifier" && state.fragments.has(name.name) || name.type === "JSXMemberExpression" && name.object.type === "JSXIdentifier" && state.reactModules.has(name.object.name) && name.property.name === "Fragment";
127
+ const staticTexts = (unit) => {
128
+ const out = [];
129
+ for (const child of unit.children) {
130
+ T.traverseFast(child, (n) => {
131
+ if (n.type === "JSXText" || n.type === "StringLiteral") out.push(n.value);
132
+ else if (n.type === "TemplateElement") out.push(n.value.cooked ?? n.value.raw);
133
+ });
134
+ }
135
+ return out;
136
+ };
137
+ const holdsMarker = (node) => {
138
+ let found = false;
139
+ T.traverseFast(node, (n) => {
140
+ if (n.type === "JSXOpeningElement" && marks(n)) found = true;
141
+ });
142
+ return found;
143
+ };
144
+ const markedNames = (unit) => {
145
+ const out = /* @__PURE__ */ new Set();
146
+ for (const child of unit.children) {
147
+ T.traverseFast(child, (n) => {
148
+ if (n.type !== "JSXAttribute" || n.name.type !== "JSXIdentifier" || n.name.name !== "data-ls-param") return;
149
+ const name = n.value ? attributeText(n) : void 0;
150
+ if (name) out.add(name.trim());
151
+ });
152
+ }
153
+ return out;
154
+ };
155
+ const marks = (opening) => opening.attributes.some(
156
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "data-ls-param"
157
+ );
158
+ const attributeText = (a) => {
159
+ const v = a.value;
160
+ if (!v) return "";
161
+ if (v.type === "StringLiteral") return v.value;
162
+ if (v.type === "JSXExpressionContainer" && v.expression.type === "StringLiteral") return v.expression.value;
163
+ return void 0;
164
+ };
165
+ const marker = (name, expression) => T.jsxElement(
166
+ T.jsxOpeningElement(T.jsxIdentifier("span"), [
167
+ T.jsxAttribute(T.jsxIdentifier("data-ls-param"), T.stringLiteral(name))
168
+ ]),
169
+ T.jsxClosingElement(T.jsxIdentifier("span")),
170
+ [T.jsxExpressionContainer(expression)]
171
+ );
172
+ const asRendered = (text) => {
173
+ const [s] = T.react.buildChildren(
174
+ T.jsxElement(T.jsxOpeningElement(T.jsxIdentifier("x"), []), T.jsxClosingElement(T.jsxIdentifier("x")), [text])
175
+ );
176
+ return s?.type === "StringLiteral" ? s.value : "";
177
+ };
178
+ const placeholderAfter = (before, name) => {
179
+ const open = (text) => /%[A-Za-z_][A-Za-z0-9_]*$/.test(text.replace(EXPLICIT, "\0"));
180
+ return open(before) || open(stripC0Controls(before)) ? `{${name}}` : `%${name}%`;
181
+ };
182
+ const textNode = (value) => {
183
+ if (/[\r\n\t\u2028\u2029]/.test(value)) return T.jsxExpressionContainer(T.stringLiteral(value));
184
+ const node = T.jsxText(value);
185
+ node.extra = { raw: value.replace(/[&<>{}]/g, (c) => `&#${c.charCodeAt(0)};`), rawValue: value };
186
+ return node;
187
+ };
188
+ const placeholders = (named) => {
189
+ const seen = /* @__PURE__ */ new Set();
190
+ return named.filter((n) => !seen.has(n.name) && seen.add(n.name));
191
+ };
192
+ const mergeParams = (existing, named) => {
193
+ const props = named.map((n) => T.objectProperty(T.identifier(n.name), n.expression));
194
+ if (!existing) return T.objectExpression(props);
195
+ if (existing.type === "ObjectExpression") return T.objectExpression([...existing.properties, ...props]);
196
+ return T.objectExpression([T.spreadElement(existing), ...props]);
197
+ };
198
+ function rewriteTarget(path, state) {
199
+ const found = [];
200
+ const tag = path.node.openingElement.name;
201
+ const isPhrase = tag.type === "JSXIdentifier" && state.phrases.has(tag.name);
202
+ let unnamed = false;
203
+ let markAll = false;
204
+ const asWritten = /* @__PURE__ */ new WeakSet();
205
+ const placeholderNames = /* @__PURE__ */ new WeakMap();
206
+ const written = (value) => {
207
+ const node = T.jsxText(value);
208
+ asWritten.add(node);
209
+ return node;
210
+ };
211
+ const touched = /* @__PURE__ */ new Set();
212
+ const settle = (children) => {
213
+ const out = [];
214
+ let run = "";
215
+ for (const c of children) {
216
+ if (c.type === "JSXText") {
217
+ const name = placeholderNames.get(c);
218
+ run += name !== void 0 ? placeholderAfter(run, name) : asWritten.has(c) ? c.value : asRendered(c);
219
+ } else if (c.type === "JSXExpressionContainer" && c.expression.type === "JSXEmptyExpression") continue;
220
+ else {
221
+ if (run) out.push(textNode(run));
222
+ run = "";
223
+ out.push(c);
224
+ }
225
+ }
226
+ if (run) out.push(textNode(run));
227
+ children.splice(0, children.length, ...out);
228
+ };
229
+ const unwrap2 = (children) => {
230
+ for (let i = 0; i < children.length; i++) {
231
+ const c = children[i];
232
+ if (c.type === "JSXFragment" || c.type === "JSXElement" && isFragment(state, c.openingElement.name)) {
233
+ children.splice(i--, 1, ...c.children);
234
+ touched.add(children);
235
+ }
236
+ }
237
+ };
238
+ const aloneInRun = (children, i) => {
239
+ for (const step of [-1, 1]) {
240
+ for (let j = i + step; j >= 0 && j < children.length; j += step) {
241
+ const d = children[j];
242
+ if (d.type === "JSXElement" || d.type === "JSXFragment") break;
243
+ if (d.type === "JSXText") {
244
+ if ((asWritten.has(d) ? d.value : asRendered(d)).trim()) return false;
245
+ continue;
246
+ }
247
+ if (d.type === "JSXSpreadChild") return false;
248
+ const x = d.expression;
249
+ if (x.type === "JSXEmptyExpression") continue;
250
+ const literal = literalOf(x);
251
+ if (literal !== void 0) {
252
+ if (literal.trim()) return false;
253
+ continue;
254
+ }
255
+ if (holdsJsx(x)) break;
256
+ return false;
257
+ }
258
+ }
259
+ return true;
260
+ };
261
+ const attributes = (opening, tag2, mode) => {
262
+ if (mode === "shown") return;
263
+ for (const a of opening.attributes) {
264
+ if (a.type !== "JSXAttribute" || a.name.type !== "JSXIdentifier" || a.value?.type !== "JSXExpressionContainer") continue;
265
+ const e = a.value.expression;
266
+ if (e.type === "JSXEmptyExpression" || literalOf(e) !== void 0) continue;
267
+ if (e.type === "LogicalExpression" || e.type === "ConditionalExpression" || holdsJsx(e)) continue;
268
+ const reads = readsAsText(tag2, opening, a.name.name);
269
+ if (reads === false) continue;
270
+ if (reads === void 0 || mode === "unknown") {
271
+ unnamed = true;
272
+ continue;
273
+ }
274
+ const parts = pieces(e) ?? [e];
275
+ const inTemplate = /* @__PURE__ */ new WeakSet();
276
+ T.traverseFast(e, (n) => {
277
+ if (n.type === "TemplateLiteral") n.expressions.forEach((x) => inTemplate.add(x));
278
+ });
279
+ const names = [];
280
+ const attr = a;
281
+ parts.forEach((p, k) => {
282
+ if (typeof p === "string") return;
283
+ found.push({
284
+ source: sourceOf(state, p),
285
+ expression: p,
286
+ param: true,
287
+ bare: false,
288
+ text: p === e ? void 0 : inTemplate.has(p) ? T.templateLiteral([T.templateElement({ raw: "" }), T.templateElement({ raw: "" }, true)], [p]) : T.binaryExpression("+", T.stringLiteral(""), p),
289
+ replace: (name) => {
290
+ names[k] = name;
291
+ const value = parts.reduce((out, q, j) => out + (typeof q === "string" ? q : placeholderAfter(out, names[j] ?? "")), "");
292
+ attr.value = T.jsxExpressionContainer(T.stringLiteral(value));
293
+ }
294
+ });
295
+ });
296
+ }
297
+ };
298
+ const walk = (children, where) => {
299
+ unwrap2(children);
300
+ children.forEach((c, i) => {
301
+ if (c.type === "JSXExpressionContainer") {
302
+ const e = c.expression;
303
+ if (e.type === "JSXEmptyExpression") {
304
+ touched.add(children);
305
+ return;
306
+ }
307
+ const literal = literalOf(e);
308
+ if (literal !== void 0) {
309
+ children[i] = written(literal);
310
+ touched.add(children);
311
+ return;
312
+ }
313
+ if (e.type === "JSXElement" || e.type === "JSXFragment" || e.type === "LogicalExpression" || e.type === "ConditionalExpression" || holdsJsx(e) || where.mode === "text" && isChildren(e) && aloneInRun(children, i)) {
314
+ if (isPhrase && where.mode === "text" && holdsMarker(e)) markAll = true;
315
+ return;
316
+ }
317
+ if (where.mode !== "text" && where.markerless) {
318
+ if (!isPhrase) unnamed = true;
319
+ return;
320
+ }
321
+ touched.add(children);
322
+ found.push({
323
+ source: sourceOf(state, e),
324
+ expression: e,
325
+ param: where.mode !== "shown",
326
+ bare: where.mode === "text" && where.markerless,
327
+ replace: (name) => {
328
+ if (where.mode !== "text" || markAll) {
329
+ children[i] = marker(name, e);
330
+ return;
331
+ }
332
+ const text = written(`%${name}%`);
333
+ placeholderNames.set(text, name);
334
+ children[i] = text;
335
+ }
336
+ });
337
+ } else if (c.type === "JSXElement") {
338
+ const n = c.openingElement.name;
339
+ if (n.type !== "JSXIdentifier" || /^[A-Z]/.test(n.name) && !state.verbatim.has(n.name)) {
340
+ if (isPhrase && where.mode === "text" && holdsMarker(c)) markAll = true;
341
+ return;
342
+ }
343
+ if (/^[a-z]/.test(n.name)) {
344
+ if (RAW_TEXT.has(n.name)) {
345
+ const value = (d) => d.type === "JSXExpressionContainer" && d.expression.type !== "JSXEmptyExpression" && literalOf(d.expression) === void 0;
346
+ if (!isPhrase && c.children.some(value)) unnamed = true;
347
+ return;
348
+ }
349
+ if (marks(c.openingElement)) {
350
+ if (isPhrase && where.mode === "text") markAll = true;
351
+ return;
352
+ }
353
+ const opt = optOut(n.name, c.openingElement);
354
+ const mode = where.mode === "shown" || opt === "shown" ? "shown" : !isPhrase && (where.mode === "unknown" || opt === "unknown") ? "unknown" : "text";
355
+ const foreign = n.name === "math" || n.name === "svg" ? n.name : where.foreign === "svg" && n.name === "foreignObject" ? null : where.foreign;
356
+ if (!isPhrase) attributes(c.openingElement, n.name, mode);
357
+ walk(c.children, { mode, foreign, markerless: foreign !== null || TEXT_ONLY.has(n.name) });
358
+ } else if (state.verbatim.has(n.name)) walk(c.children, { ...where, mode: "shown" });
359
+ }
360
+ });
361
+ };
362
+ walk(path.node.children, { mode: "text", foreign: null, markerless: false });
363
+ if (markAll && found.some((f) => f.bare)) {
364
+ unnamed = true;
365
+ for (let i = found.length - 1; i >= 0; i--) if (found[i].bare) found.splice(i, 1);
366
+ }
367
+ const attrs = path.node.openingElement.attributes;
368
+ if (unnamed && !attrs.some((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === UNNAMED)) {
369
+ attrs.push(T.jsxAttribute(T.jsxIdentifier(UNNAMED), T.stringLiteral("")));
370
+ }
371
+ if (!found.length) {
372
+ touched.forEach(settle);
373
+ if (unnamed || touched.size) state.meta.rewritten++;
374
+ return;
375
+ }
376
+ const existing = attrs.find(
377
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "params"
378
+ );
379
+ const existingExpr = existing?.value?.type === "JSXExpressionContainer" && existing.value.expression.type !== "JSXEmptyExpression" ? existing.value.expression : null;
380
+ const named = nameAll(found, /* @__PURE__ */ new Set([...explicitNames(staticTexts(path.node), existingExpr), ...markedNames(path.node)]));
381
+ found.forEach((f, i) => f.replace(named[i].name));
382
+ placeholders(named).forEach((n) => warn(state, n));
383
+ const params = placeholders(named.filter((_, i) => found[i].param)).map((n) => {
384
+ const uses = found.filter((f, i) => f.param && named[i].name === n.name);
385
+ const given = uses.find((f) => !f.text);
386
+ return { ...n, expression: given ? given.expression : uses[0].text };
387
+ });
388
+ touched.forEach(settle);
389
+ if (params.length) {
390
+ const value = T.jsxExpressionContainer(mergeParams(existingExpr, params));
391
+ if (existing) existing.value = value;
392
+ else attrs.push(T.jsxAttribute(T.jsxIdentifier("params"), value));
393
+ }
394
+ state.meta.rewritten++;
395
+ }
396
+ function pieces(node) {
397
+ if (node.type === "TemplateLiteral") {
398
+ const out = [];
399
+ node.quasis.forEach((q, i) => {
400
+ if (q.value.cooked) out.push(q.value.cooked);
401
+ if (i < node.expressions.length) out.push(node.expressions[i]);
402
+ });
403
+ return node.expressions.length ? out : null;
404
+ }
405
+ if (node.type !== "BinaryExpression" || node.operator !== "+") return null;
406
+ const spine = [];
407
+ for (let n = node; n.type === "BinaryExpression" && n.operator === "+"; n = n.left) spine.push(n);
408
+ const operands = [spine[spine.length - 1].left, ...spine.map((s) => s.right).reverse()];
409
+ const first = operands.findIndex((o) => o.type === "StringLiteral" || o.type === "TemplateLiteral");
410
+ if (first < 0) return null;
411
+ const flat = [];
412
+ if (first === 1) flat.push(operands[0]);
413
+ else if (first > 1) flat.push(spine[spine.length - (first - 1)]);
414
+ for (const o of operands.slice(first)) {
415
+ if (o.type === "StringLiteral") flat.push(o.value);
416
+ else if (o.type === "TemplateLiteral") flat.push(...pieces(o) ?? [o.quasis[0].value.cooked ?? ""]);
417
+ else flat.push(o);
418
+ }
419
+ return flat.some((p) => typeof p !== "string") ? flat : null;
420
+ }
421
+ const isKeyShaped = (parts) => parts.every((p) => typeof p !== "string" || /^[A-Za-z0-9_.:-]*$/.test(p));
422
+ const secondArgument = (second, third) => {
423
+ if (!second) return "category";
424
+ if (second.type === "ObjectExpression" || second.type === "NullLiteral") return "params";
425
+ if (second.type === "StringLiteral" || second.type === "TemplateLiteral") return "category";
426
+ if (second.type === "Identifier" && second.name === "undefined") return "category";
427
+ if (second.type === "CallExpression" && second.callee.type === "Identifier" && second.callee.name === "inCategory") return "category";
428
+ return third ? "category" : "unknown";
429
+ };
430
+ const decidedAtRuntime = (params, second) => {
431
+ const a = T.identifier("a");
432
+ const p = T.identifier("p");
433
+ const isRef = T.logicalExpression(
434
+ "&&",
435
+ T.binaryExpression("!==", a, T.nullLiteral()),
436
+ T.binaryExpression(
437
+ "===",
438
+ T.memberExpression(a, T.callExpression(T.memberExpression(T.identifier("Symbol"), T.identifier("for")), [T.stringLiteral("langsys.category")]), true),
439
+ T.booleanLiteral(true)
440
+ )
441
+ );
442
+ const isParams = T.logicalExpression("&&", T.binaryExpression("===", T.unaryExpression("typeof", a), T.stringLiteral("object")), T.unaryExpression("!", isRef));
443
+ const pick = T.arrowFunctionExpression(
444
+ [p, a],
445
+ T.conditionalExpression(isParams, T.arrayExpression([T.objectExpression([T.spreadElement(a), T.spreadElement(p)])]), T.arrayExpression([a, p]))
446
+ );
447
+ return T.spreadElement(T.callExpression(pick, [params, second]));
448
+ };
449
+ function rewriteCall(path, state) {
450
+ const [phrase, second, third] = path.node.arguments;
451
+ if (!phrase || phrase.type === "SpreadElement" || phrase.type === "ArgumentPlaceholder") return;
452
+ const parts = pieces(phrase);
453
+ if (!parts || isKeyShaped(parts)) return;
454
+ if (second?.type === "SpreadElement" || second?.type === "ArgumentPlaceholder") return;
455
+ if (third?.type === "SpreadElement" || third?.type === "ArgumentPlaceholder") return;
456
+ const values = parts.filter((p) => typeof p !== "string").map((e) => ({ source: sourceOf(state, e), expression: e }));
457
+ const kind = secondArgument(second, third);
458
+ const paramsExpr = kind === "category" ? third ?? null : second.type === "NullLiteral" ? null : second;
459
+ const unread = !!paramsExpr && (paramsExpr.type !== "ObjectExpression" || paramsExpr.properties.some((p) => p.type !== "ObjectProperty" || p.computed));
460
+ const written = unread ? parts.filter((p) => typeof p === "string") : [];
461
+ const named = nameAll(values, explicitNames(written, paramsExpr, BRACED));
462
+ let next = 0;
463
+ const text = parts.map((p) => typeof p === "string" ? p : `{${named[next++].name}}`).join("");
464
+ const unique = placeholders(named);
465
+ unique.forEach((n) => warn(state, n));
466
+ const rest = path.node.arguments.slice(kind === "category" ? 3 : 2);
467
+ path.node.arguments = [
468
+ T.stringLiteral(text),
469
+ ...kind === "category" ? [second ?? T.identifier("undefined"), mergeParams(paramsExpr, unique)] : kind === "params" ? [mergeParams(paramsExpr, unique)] : [decidedAtRuntime(mergeParams(null, unique), second)],
470
+ ...rest
471
+ ];
472
+ state.meta.rewritten++;
473
+ }
474
+ const isBound = (path, name, bindings) => {
475
+ const b = path.scope.getBinding(name);
476
+ return !!b && bindings.has(b.identifier);
477
+ };
478
+ return {
479
+ name: "langsys-placeholders",
480
+ pre(file) {
481
+ this.meta = { warnings: [], rewritten: 0 };
482
+ file.metadata.langsys = this.meta;
483
+ this.targets = /* @__PURE__ */ new Set();
484
+ this.phrases = /* @__PURE__ */ new Set();
485
+ this.hooks = /* @__PURE__ */ new Set();
486
+ this.targetBindings = /* @__PURE__ */ new Set();
487
+ this.hookBindings = /* @__PURE__ */ new Set();
488
+ this.tBindings = /* @__PURE__ */ new Set();
489
+ this.verbatim = /* @__PURE__ */ new Set();
490
+ this.fragments = /* @__PURE__ */ new Set();
491
+ this.reactModules = /* @__PURE__ */ new Set();
492
+ },
493
+ visitor: {
494
+ Program(program, state) {
495
+ for (const stmt of program.node.body) {
496
+ if (stmt.type === "ImportDeclaration" && stmt.source.value === "react") {
497
+ for (const s of stmt.specifiers) {
498
+ if (s.type !== "ImportSpecifier") state.reactModules.add(s.local.name);
499
+ else if ((s.imported.type === "Identifier" ? s.imported.name : s.imported.value) === "Fragment") {
500
+ state.fragments.add(s.local.name);
501
+ }
502
+ }
503
+ }
504
+ if (stmt.type !== "ImportDeclaration" || stmt.source.value !== SOURCE) continue;
505
+ for (const s of stmt.specifiers) {
506
+ if (s.type !== "ImportSpecifier") continue;
507
+ const imported = s.imported.type === "Identifier" ? s.imported.name : s.imported.value;
508
+ if (TARGETS.has(imported)) state.targets.add(s.local.name);
509
+ if (imported === "Phrase") state.phrases.add(s.local.name);
510
+ if (imported === "useT") state.hooks.add(s.local.name);
511
+ if (imported === VERBATIM) state.verbatim.add(s.local.name);
512
+ }
513
+ }
514
+ if (!state.targets.size && !state.hooks.size) return;
515
+ const bind = (names, into) => names.forEach((name) => {
516
+ const b = program.scope.getBinding(name);
517
+ if (b) into.add(b.identifier);
518
+ });
519
+ bind(state.targets, state.targetBindings);
520
+ bind(state.hooks, state.hookBindings);
521
+ program.traverse({
522
+ VariableDeclarator(d) {
523
+ const init = d.node.init;
524
+ if (d.node.id.type === "Identifier" && init?.type === "CallExpression" && init.callee.type === "Identifier" && isBound(d, init.callee.name, state.hookBindings)) {
525
+ const b = d.scope.getBinding(d.node.id.name);
526
+ if (b) state.tBindings.add(b.identifier);
527
+ }
528
+ }
529
+ });
530
+ },
531
+ JSXElement(path, state) {
532
+ const n = path.node.openingElement.name;
533
+ if (n.type === "JSXIdentifier" && isBound(path, n.name, state.targetBindings)) rewriteTarget(path, state);
534
+ },
535
+ CallExpression(path, state) {
536
+ const callee = path.node.callee;
537
+ const isT = callee.type === "Identifier" && isBound(path, callee.name, state.tBindings) || callee.type === "CallExpression" && callee.callee.type === "Identifier" && isBound(path, callee.callee.name, state.hookBindings);
538
+ if (isT) rewriteCall(path, state);
539
+ }
540
+ }
541
+ };
542
+ }
543
+
544
+ // src/transform/run.ts
545
+ var SCRIPT = /\.(?:[cm]?[jt]sx?)$/;
546
+ function syntaxes(isTs, isJsx) {
547
+ const base = [...isJsx ? ["jsx"] : [], ...isTs ? ["typescript"] : []];
548
+ const out = [
549
+ [...base, "decorators-legacy", "decoratorAutoAccessors"],
550
+ [...base, ["decorators", {}], "decoratorAutoAccessors"]
551
+ ];
552
+ if (!isTs) out.push([...base, "flow", "decorators-legacy"]);
553
+ return out;
554
+ }
555
+ function wants(code, filename) {
556
+ const file = filename.split("?")[0];
557
+ return SCRIPT.test(file) && !file.includes("/node_modules/") && code.includes("langsys-js-react");
558
+ }
559
+ async function transformSource(code, filename, sourceMaps = true) {
560
+ if (!wants(code, filename)) return null;
561
+ const file = filename.split("?")[0];
562
+ const isTs = /\.[cm]?tsx?$/.test(file);
563
+ const isJsx = /x$/.test(file) || /\.[cm]?js$/.test(file);
564
+ let out = null;
565
+ let unparsed = null;
566
+ for (const plugins of syntaxes(isTs, isJsx)) {
567
+ try {
568
+ out = await transformAsync(code, {
569
+ filename: file,
570
+ babelrc: false,
571
+ configFile: false,
572
+ sourceMaps,
573
+ plugins: [langsysPlaceholders],
574
+ parserOpts: { plugins },
575
+ generatorOpts: { retainLines: false }
576
+ });
577
+ unparsed = null;
578
+ break;
579
+ } catch (err) {
580
+ if (err.code !== "BABEL_PARSE_ERROR") throw err;
581
+ unparsed ??= err;
582
+ }
583
+ }
584
+ if (unparsed) {
585
+ const reason = unparsed.message.replace(`${file}: `, "").split("\n")[0];
586
+ return {
587
+ code: null,
588
+ map: null,
589
+ warnings: [
590
+ `langsys: ${file} \u2014 Babel could not parse it (${reason}), so it is left as written and the values in its <Translate>, <Phrase> and t() calls are not made placeholders.`
591
+ ]
592
+ };
593
+ }
594
+ const meta = out?.metadata?.langsys;
595
+ if (!out?.code || !meta || meta.rewritten === 0) return null;
596
+ return {
597
+ code: out.code,
598
+ map: out.map ?? null,
599
+ warnings: meta.warnings.map(
600
+ (w) => `langsys: ${file}${w.line ? `:${w.line}:${(w.column ?? 0) + 1}` : ""} \u2014 no placeholder name can be derived from \`${w.source}\`, so it is named {${w.name}}. Give it a name with %name% and params.`
601
+ )
602
+ };
603
+ }
604
+
605
+ // src/transform/vite.ts
606
+ function langsys() {
607
+ return {
608
+ name: "langsys-placeholders",
609
+ enforce: "pre",
610
+ async transform(code, id) {
611
+ const out = await transformSource(code, id);
612
+ if (!out) return null;
613
+ for (const w of out.warnings) this.warn(w);
614
+ return out.code === null ? null : { code: out.code, map: out.map };
615
+ }
616
+ };
617
+ }
618
+
619
+ export { langsys as default };
620
+ //# sourceMappingURL=vite.mjs.map
621
+ //# sourceMappingURL=vite.mjs.map