@linguanahq/compiler 0.1.0 → 0.1.2
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/index.d.ts +27 -0
- package/dist/index.js +794 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/transform.d.ts +17 -0
- package/dist/transform.js +468 -0
- package/package.json +40 -31
- package/src/index.ts +0 -984
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import generateImport from "@babel/generator";
|
|
2
|
+
import { type Diagnostic, type ManifestMessage } from "@linguanahq/catalog";
|
|
3
|
+
export type TransformOptions = {
|
|
4
|
+
sourceLocale: string;
|
|
5
|
+
root?: string;
|
|
6
|
+
runtimePackage?: string;
|
|
7
|
+
compilerRuntime?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const generate: typeof generateImport;
|
|
10
|
+
export type TransformResult = {
|
|
11
|
+
code: string;
|
|
12
|
+
map: ReturnType<typeof generate>["map"];
|
|
13
|
+
messages: ManifestMessage[];
|
|
14
|
+
diagnostics: Diagnostic[];
|
|
15
|
+
};
|
|
16
|
+
export declare function transformSource(code: string, filename: string, options: TransformOptions): TransformResult;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
// src/transform.ts
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import generateImport from "@babel/generator";
|
|
4
|
+
import { parse } from "@babel/parser";
|
|
5
|
+
import traverseImport from "@babel/traverse";
|
|
6
|
+
import * as t from "@babel/types";
|
|
7
|
+
import {
|
|
8
|
+
contentHash,
|
|
9
|
+
normalizeSource,
|
|
10
|
+
parseMessagePlaceholders,
|
|
11
|
+
stableMessageId
|
|
12
|
+
} from "@linguanahq/catalog";
|
|
13
|
+
var generate = generateImport.default ?? generateImport;
|
|
14
|
+
var traverse = traverseImport.default ?? traverseImport;
|
|
15
|
+
var EXCLUDED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre"]);
|
|
16
|
+
var ALLOWED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
17
|
+
"title",
|
|
18
|
+
"placeholder",
|
|
19
|
+
"aria-label",
|
|
20
|
+
"alt",
|
|
21
|
+
"label",
|
|
22
|
+
"value"
|
|
23
|
+
]);
|
|
24
|
+
var MACHINE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
25
|
+
"id",
|
|
26
|
+
"class",
|
|
27
|
+
"className",
|
|
28
|
+
"name",
|
|
29
|
+
"role",
|
|
30
|
+
"href",
|
|
31
|
+
"src",
|
|
32
|
+
"action",
|
|
33
|
+
"method",
|
|
34
|
+
"data-testid",
|
|
35
|
+
"data-test-id",
|
|
36
|
+
"data-analytics",
|
|
37
|
+
"data-event"
|
|
38
|
+
]);
|
|
39
|
+
function jsxName(node) {
|
|
40
|
+
if (t.isJSXIdentifier(node)) return node.name;
|
|
41
|
+
if (t.isJSXMemberExpression(node)) return jsxName(node.property);
|
|
42
|
+
return `${node.namespace.name}:${node.name.name}`;
|
|
43
|
+
}
|
|
44
|
+
function renderedJsxText(value) {
|
|
45
|
+
const lines = value.replace(/\r\n?/g, "\n").split("\n");
|
|
46
|
+
let result = "";
|
|
47
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
48
|
+
let line = lines[index]?.replace(/\t/g, " ") ?? "";
|
|
49
|
+
if (index !== 0) line = line.replace(/^\s+/, "");
|
|
50
|
+
if (index !== lines.length - 1) line = line.replace(/\s+$/, "");
|
|
51
|
+
if (!line) continue;
|
|
52
|
+
if (result && !result.endsWith(" ")) result += " ";
|
|
53
|
+
result += line;
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function location(path, root, filename) {
|
|
58
|
+
const start = path.node.loc?.start;
|
|
59
|
+
return {
|
|
60
|
+
file: relative(root, filename).replaceAll("\\", "/"),
|
|
61
|
+
line: start?.line ?? 1,
|
|
62
|
+
column: start?.column ?? 0
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function getLiteralAttribute(opening, name) {
|
|
66
|
+
for (const attribute of opening.attributes) {
|
|
67
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name) && attribute.name.name === name && t.isStringLiteral(attribute.value)) {
|
|
68
|
+
return attribute.value.value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
function hasAttribute(opening, name) {
|
|
74
|
+
return opening.attributes.some(
|
|
75
|
+
(attribute) => t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name) && attribute.name.name === name
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function attributeValue(opening, name) {
|
|
79
|
+
for (const attribute of opening.attributes) {
|
|
80
|
+
if (!t.isJSXAttribute(attribute) || !t.isJSXIdentifier(attribute.name, { name }))
|
|
81
|
+
continue;
|
|
82
|
+
if (attribute.value === null) return true;
|
|
83
|
+
if (t.isStringLiteral(attribute.value)) return attribute.value.value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function isUnsafeStaticCopy(source) {
|
|
87
|
+
const normalized = normalizeSource(source);
|
|
88
|
+
return /^(?:https?:\/\/|mailto:|tel:|www\.)/i.test(normalized) || /^\/?[\w.-]+(?:\/[\w./-]+)+(?:\?\S*)?$/.test(normalized) || /^[a-z0-9]+(?:[_:.][a-z0-9-]+)+$/i.test(normalized);
|
|
89
|
+
}
|
|
90
|
+
function isExcluded(path) {
|
|
91
|
+
return Boolean(
|
|
92
|
+
path.findParent((parent) => {
|
|
93
|
+
if (!parent.isJSXElement()) return false;
|
|
94
|
+
const opening = parent.node.openingElement;
|
|
95
|
+
const tag = jsxName(opening.name);
|
|
96
|
+
return tag === "T" || EXCLUDED_ELEMENTS.has(tag.toLowerCase()) || hasAttribute(opening, "data-no-translate") || hasAttribute(opening, "hidden") || attributeValue(opening, "aria-hidden") === "true" || tag.toLowerCase() === "input" && attributeValue(opening, "type") === "hidden";
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
function isExcludedElement(opening) {
|
|
101
|
+
const tag = jsxName(opening.name);
|
|
102
|
+
return EXCLUDED_ELEMENTS.has(tag.toLowerCase()) || hasAttribute(opening, "data-no-translate") || hasAttribute(opening, "hidden") || attributeValue(opening, "aria-hidden") === "true" || tag.toLowerCase() === "input" && attributeValue(opening, "type") === "hidden";
|
|
103
|
+
}
|
|
104
|
+
function hasMixedRichChildren(path) {
|
|
105
|
+
return Boolean(
|
|
106
|
+
path.findParent((parent) => {
|
|
107
|
+
if (!parent.isJSXElement()) return false;
|
|
108
|
+
const children = parent.node.children;
|
|
109
|
+
const hasElement = children.some(
|
|
110
|
+
(child) => t.isJSXElement(child) || t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)
|
|
111
|
+
);
|
|
112
|
+
const hasText = children.some(
|
|
113
|
+
(child) => t.isJSXText(child) && Boolean(renderedJsxText(child.value))
|
|
114
|
+
);
|
|
115
|
+
return hasElement && hasText;
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function placeholders(source) {
|
|
120
|
+
try {
|
|
121
|
+
return parseMessagePlaceholders(source);
|
|
122
|
+
} catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function stringProperty(object, name) {
|
|
127
|
+
for (const property of object.properties) {
|
|
128
|
+
if (!t.isObjectProperty(property)) continue;
|
|
129
|
+
const key = t.isIdentifier(property.key) ? property.key.name : t.isStringLiteral(property.key) ? property.key.value : void 0;
|
|
130
|
+
if (key === name && t.isStringLiteral(property.value))
|
|
131
|
+
return property.value.value;
|
|
132
|
+
}
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
function isTranslationCall(path, runtimePackage = "@linguanahq/react") {
|
|
136
|
+
if (!t.isIdentifier(path.node.callee)) return false;
|
|
137
|
+
const name = path.node.callee.name;
|
|
138
|
+
const binding = path.scope.getBinding(name);
|
|
139
|
+
if (!binding) return false;
|
|
140
|
+
if (binding.path.isImportSpecifier()) {
|
|
141
|
+
const declaration = binding.path.parentPath;
|
|
142
|
+
return declaration.isImportDeclaration() && declaration.node.source.value === runtimePackage && t.isIdentifier(binding.path.node.imported, { name: "t" });
|
|
143
|
+
}
|
|
144
|
+
const declarator = binding.path.isVariableDeclarator() ? binding.path : binding.path.findParent((candidate) => candidate.isVariableDeclarator());
|
|
145
|
+
if (!declarator?.isVariableDeclarator()) return false;
|
|
146
|
+
const init = t.isAwaitExpression(declarator.node.init) ? declarator.node.init.argument : declarator.node.init;
|
|
147
|
+
if (!t.isCallExpression(init) || !t.isIdentifier(init.callee) || !t.isObjectPattern(declarator.node.id))
|
|
148
|
+
return false;
|
|
149
|
+
const factory = path.scope.getBinding(init.callee.name);
|
|
150
|
+
const imported = factory?.path;
|
|
151
|
+
const nextFactory = imported?.isImportSpecifier() && imported.parentPath.isImportDeclaration() && ["@linguanahq/next/client", "@linguanahq/next/server"].includes(
|
|
152
|
+
imported.parentPath.node.source.value
|
|
153
|
+
) && t.isIdentifier(imported.node.imported) && ["useTranslation", "getTranslations"].includes(imported.node.imported.name);
|
|
154
|
+
if (!nextFactory && init.callee.name !== "useTranslation") return false;
|
|
155
|
+
return declarator.node.id.properties.some(
|
|
156
|
+
(property) => t.isObjectProperty(property) && t.isIdentifier(property.key, { name: "t" }) && t.isIdentifier(property.value, { name })
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
function transformSource(code, filename, options) {
|
|
160
|
+
const root = options.root ?? process.cwd();
|
|
161
|
+
const ast = parse(code, {
|
|
162
|
+
sourceType: "module",
|
|
163
|
+
sourceFilename: filename,
|
|
164
|
+
plugins: ["jsx", "typescript"]
|
|
165
|
+
});
|
|
166
|
+
const names = /* @__PURE__ */ new Set();
|
|
167
|
+
traverse(ast, {
|
|
168
|
+
Identifier(path) {
|
|
169
|
+
names.add(path.node.name);
|
|
170
|
+
},
|
|
171
|
+
JSXIdentifier(path) {
|
|
172
|
+
names.add(path.node.name);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
const unique = (base) => {
|
|
176
|
+
let name = base;
|
|
177
|
+
let suffix = 0;
|
|
178
|
+
while (names.has(name)) name = `${base}${++suffix}`;
|
|
179
|
+
names.add(name);
|
|
180
|
+
return name;
|
|
181
|
+
};
|
|
182
|
+
const messageName = unique("__LinguanaMessage");
|
|
183
|
+
const attributesName = unique("__LinguanaAttributes");
|
|
184
|
+
const localizedName = unique("__localized");
|
|
185
|
+
const messages = /* @__PURE__ */ new Map();
|
|
186
|
+
const diagnostics = [];
|
|
187
|
+
let needsRuntime = false;
|
|
188
|
+
const richDiagnosticLocations = /* @__PURE__ */ new Set();
|
|
189
|
+
const addMessage = (message) => {
|
|
190
|
+
const existing = messages.get(message.id);
|
|
191
|
+
if (existing && existing.contentHash !== message.contentHash) {
|
|
192
|
+
diagnostics.push({
|
|
193
|
+
code: "duplicate-id",
|
|
194
|
+
severity: "error",
|
|
195
|
+
message: `Message ID ${message.id} refers to incompatible source content`,
|
|
196
|
+
location: message.locations[0],
|
|
197
|
+
remediation: "Choose a unique explicit ID or make the source messages identical."
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (existing) {
|
|
202
|
+
existing.locations.push(...message.locations);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
messages.set(message.id, message);
|
|
206
|
+
};
|
|
207
|
+
traverse(ast, {
|
|
208
|
+
JSXText(path) {
|
|
209
|
+
const source = renderedJsxText(path.node.value);
|
|
210
|
+
if (!source || isExcluded(path)) return;
|
|
211
|
+
if (isUnsafeStaticCopy(source)) {
|
|
212
|
+
diagnostics.push({
|
|
213
|
+
code: "unsafe-static-copy",
|
|
214
|
+
severity: "info",
|
|
215
|
+
message: "URL-like or machine-readable text was excluded from extraction.",
|
|
216
|
+
location: location(path, root, filename)
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (hasMixedRichChildren(path)) {
|
|
221
|
+
const itemLocation = location(path, root, filename);
|
|
222
|
+
const key = `${itemLocation.file}:${itemLocation.line}:${itemLocation.column}`;
|
|
223
|
+
if (!richDiagnosticLocations.has(key)) {
|
|
224
|
+
richDiagnosticLocations.add(key);
|
|
225
|
+
diagnostics.push({
|
|
226
|
+
code: "ambiguous-rich-text",
|
|
227
|
+
severity: "warning",
|
|
228
|
+
message: "Mixed text and JSX elements require an explicit <T> message.",
|
|
229
|
+
location: itemLocation,
|
|
230
|
+
remediation: "Use <T defaultMessage components> or data-no-translate."
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const parent = path.findParent((candidate) => candidate.isJSXElement());
|
|
236
|
+
if (!parent?.isJSXElement()) return;
|
|
237
|
+
const element = jsxName(parent.node.openingElement.name);
|
|
238
|
+
const context = { kind: "text", element };
|
|
239
|
+
const id = stableMessageId({
|
|
240
|
+
sourceLocale: options.sourceLocale,
|
|
241
|
+
source,
|
|
242
|
+
context
|
|
243
|
+
});
|
|
244
|
+
addMessage({
|
|
245
|
+
id,
|
|
246
|
+
source,
|
|
247
|
+
normalized: normalizeSource(source),
|
|
248
|
+
contentHash: contentHash(source),
|
|
249
|
+
context,
|
|
250
|
+
placeholders: placeholders(source),
|
|
251
|
+
confidence: "high",
|
|
252
|
+
locations: [location(path, root, filename)]
|
|
253
|
+
});
|
|
254
|
+
needsRuntime = true;
|
|
255
|
+
path.replaceWith(
|
|
256
|
+
t.jsxExpressionContainer(
|
|
257
|
+
t.jsxElement(
|
|
258
|
+
t.jsxOpeningElement(
|
|
259
|
+
t.jsxIdentifier(messageName),
|
|
260
|
+
[
|
|
261
|
+
t.jsxAttribute(t.jsxIdentifier("id"), t.stringLiteral(id)),
|
|
262
|
+
t.jsxAttribute(
|
|
263
|
+
t.jsxIdentifier("defaultMessage"),
|
|
264
|
+
t.stringLiteral(source)
|
|
265
|
+
)
|
|
266
|
+
],
|
|
267
|
+
true
|
|
268
|
+
),
|
|
269
|
+
null,
|
|
270
|
+
[],
|
|
271
|
+
true
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
JSXExpressionContainer(path) {
|
|
277
|
+
if (isExcluded(path) || t.isJSXEmptyExpression(path.node.expression) || !path.parentPath.isJSXElement())
|
|
278
|
+
return;
|
|
279
|
+
if (t.isConditionalExpression(path.node.expression) || t.isBinaryExpression(path.node.expression) || t.isTemplateLiteral(path.node.expression)) {
|
|
280
|
+
diagnostics.push({
|
|
281
|
+
code: "dynamic-candidate",
|
|
282
|
+
severity: "warning",
|
|
283
|
+
message: "Dynamic JSX copy was not automatically extracted.",
|
|
284
|
+
location: location(path, root, filename),
|
|
285
|
+
remediation: "Use t(), <T>, data-no-translate, or an explicit annotation."
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
CallExpression(path) {
|
|
290
|
+
if (!isTranslationCall(path, options.runtimePackage)) return;
|
|
291
|
+
const [sourceNode, , optionsNode] = path.node.arguments;
|
|
292
|
+
if (!t.isStringLiteral(sourceNode)) return;
|
|
293
|
+
const explicitId = t.isObjectExpression(optionsNode) ? stringProperty(optionsNode, "id") : void 0;
|
|
294
|
+
const developer = t.isObjectExpression(optionsNode) ? stringProperty(optionsNode, "context") : void 0;
|
|
295
|
+
const context = { kind: "explicit", developer };
|
|
296
|
+
const id = stableMessageId({
|
|
297
|
+
sourceLocale: options.sourceLocale,
|
|
298
|
+
source: sourceNode.value,
|
|
299
|
+
context,
|
|
300
|
+
explicitId
|
|
301
|
+
});
|
|
302
|
+
addMessage({
|
|
303
|
+
id,
|
|
304
|
+
source: sourceNode.value,
|
|
305
|
+
normalized: normalizeSource(sourceNode.value),
|
|
306
|
+
contentHash: contentHash(sourceNode.value),
|
|
307
|
+
context,
|
|
308
|
+
placeholders: placeholders(sourceNode.value),
|
|
309
|
+
confidence: "explicit",
|
|
310
|
+
locations: [location(path, root, filename)]
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
JSXElement: {
|
|
314
|
+
exit(path) {
|
|
315
|
+
if (isExcluded(path) || isExcludedElement(path.node.openingElement))
|
|
316
|
+
return;
|
|
317
|
+
const opening = path.node.openingElement;
|
|
318
|
+
const tag = jsxName(opening.name);
|
|
319
|
+
if (tag === "T") {
|
|
320
|
+
const source = getLiteralAttribute(opening, "defaultMessage");
|
|
321
|
+
if (!source) return;
|
|
322
|
+
const explicitId = getLiteralAttribute(opening, "id");
|
|
323
|
+
const developer = getLiteralAttribute(opening, "context");
|
|
324
|
+
const context = { kind: "explicit", developer };
|
|
325
|
+
const id = stableMessageId({
|
|
326
|
+
sourceLocale: options.sourceLocale,
|
|
327
|
+
source,
|
|
328
|
+
context,
|
|
329
|
+
explicitId
|
|
330
|
+
});
|
|
331
|
+
addMessage({
|
|
332
|
+
id,
|
|
333
|
+
source,
|
|
334
|
+
normalized: normalizeSource(source),
|
|
335
|
+
contentHash: contentHash(source),
|
|
336
|
+
context,
|
|
337
|
+
placeholders: placeholders(source),
|
|
338
|
+
confidence: "explicit",
|
|
339
|
+
locations: [location(path, root, filename)]
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const descriptors = [];
|
|
344
|
+
const localizedNames = [];
|
|
345
|
+
for (const attribute of opening.attributes) {
|
|
346
|
+
if (!t.isJSXAttribute(attribute) || !t.isJSXIdentifier(attribute.name) || !t.isStringLiteral(attribute.value))
|
|
347
|
+
continue;
|
|
348
|
+
const name = attribute.name.name;
|
|
349
|
+
if (MACHINE_ATTRIBUTES.has(name) || name.startsWith("on") || name === "data-translate")
|
|
350
|
+
continue;
|
|
351
|
+
const optInValue = attributeValue(opening, "data-translate");
|
|
352
|
+
const optedIn = typeof optInValue === "string" && optInValue.split(/[\s,]+/).includes(name);
|
|
353
|
+
if (!ALLOWED_ATTRIBUTES.has(name) && !optedIn) continue;
|
|
354
|
+
if (name === "value" && tag.toLowerCase() !== "button") continue;
|
|
355
|
+
const source = attribute.value.value;
|
|
356
|
+
if (!source || isUnsafeStaticCopy(source)) continue;
|
|
357
|
+
const context = {
|
|
358
|
+
kind: "attribute",
|
|
359
|
+
element: tag,
|
|
360
|
+
attribute: name
|
|
361
|
+
};
|
|
362
|
+
const id = stableMessageId({
|
|
363
|
+
sourceLocale: options.sourceLocale,
|
|
364
|
+
source,
|
|
365
|
+
context
|
|
366
|
+
});
|
|
367
|
+
addMessage({
|
|
368
|
+
id,
|
|
369
|
+
source,
|
|
370
|
+
normalized: normalizeSource(source),
|
|
371
|
+
contentHash: contentHash(source),
|
|
372
|
+
context,
|
|
373
|
+
placeholders: placeholders(source),
|
|
374
|
+
confidence: "high",
|
|
375
|
+
locations: [location(path, root, filename)]
|
|
376
|
+
});
|
|
377
|
+
localizedNames.push(name);
|
|
378
|
+
descriptors.push(
|
|
379
|
+
t.objectExpression([
|
|
380
|
+
t.objectProperty(t.identifier("name"), t.stringLiteral(name)),
|
|
381
|
+
t.objectProperty(t.identifier("id"), t.stringLiteral(id)),
|
|
382
|
+
t.objectProperty(
|
|
383
|
+
t.identifier("defaultMessage"),
|
|
384
|
+
t.stringLiteral(source)
|
|
385
|
+
)
|
|
386
|
+
])
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
if (!descriptors.length) return;
|
|
390
|
+
needsRuntime = true;
|
|
391
|
+
const original = t.cloneNode(path.node, true);
|
|
392
|
+
for (const attribute of original.openingElement.attributes) {
|
|
393
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name) && localizedNames.includes(attribute.name.name)) {
|
|
394
|
+
attribute.value = t.jsxExpressionContainer(
|
|
395
|
+
t.memberExpression(
|
|
396
|
+
t.identifier(localizedName),
|
|
397
|
+
t.stringLiteral(attribute.name.name),
|
|
398
|
+
true
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const wrapper = t.jsxElement(
|
|
404
|
+
t.jsxOpeningElement(
|
|
405
|
+
t.jsxIdentifier(attributesName),
|
|
406
|
+
[
|
|
407
|
+
t.jsxAttribute(
|
|
408
|
+
t.jsxIdentifier("descriptors"),
|
|
409
|
+
t.jsxExpressionContainer(t.arrayExpression(descriptors))
|
|
410
|
+
)
|
|
411
|
+
],
|
|
412
|
+
false
|
|
413
|
+
),
|
|
414
|
+
t.jsxClosingElement(t.jsxIdentifier(attributesName)),
|
|
415
|
+
[
|
|
416
|
+
t.jsxExpressionContainer(
|
|
417
|
+
t.arrowFunctionExpression(
|
|
418
|
+
[t.identifier(localizedName)],
|
|
419
|
+
original
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
]
|
|
423
|
+
);
|
|
424
|
+
path.replaceWith(wrapper);
|
|
425
|
+
path.skip();
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
Program: {
|
|
429
|
+
exit(path) {
|
|
430
|
+
if (!needsRuntime) return;
|
|
431
|
+
path.unshiftContainer(
|
|
432
|
+
"body",
|
|
433
|
+
t.importDeclaration(
|
|
434
|
+
[
|
|
435
|
+
t.importSpecifier(
|
|
436
|
+
t.identifier(messageName),
|
|
437
|
+
t.identifier("LinguanaMessage")
|
|
438
|
+
),
|
|
439
|
+
t.importSpecifier(
|
|
440
|
+
t.identifier(attributesName),
|
|
441
|
+
t.identifier("LinguanaAttributes")
|
|
442
|
+
)
|
|
443
|
+
],
|
|
444
|
+
t.stringLiteral(
|
|
445
|
+
options.compilerRuntime ?? "@linguanahq/react/compiler-runtime"
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
const output = generate(
|
|
453
|
+
ast,
|
|
454
|
+
{ sourceMaps: true, sourceFileName: filename },
|
|
455
|
+
code
|
|
456
|
+
);
|
|
457
|
+
return {
|
|
458
|
+
code: output.code,
|
|
459
|
+
map: output.map,
|
|
460
|
+
messages: [...messages.values()].sort(
|
|
461
|
+
(left, right) => left.id.localeCompare(right.id)
|
|
462
|
+
),
|
|
463
|
+
diagnostics
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
export {
|
|
467
|
+
transformSource
|
|
468
|
+
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
2
|
+
"name": "@linguanahq/compiler",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Linguana compiler integration",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./transform": {
|
|
13
|
+
"types": "./dist/transform.d.ts",
|
|
14
|
+
"import": "./dist/transform.js",
|
|
15
|
+
"default": "./dist/transform.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.12.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@babel/generator": "^7.28.5",
|
|
26
|
+
"@babel/parser": "^7.28.5",
|
|
27
|
+
"@babel/traverse": "^7.28.5",
|
|
28
|
+
"@babel/types": "^7.29.8",
|
|
29
|
+
"@linguanahq/catalog": "0.1.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vite": ">=7 <9"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"vite": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
33
42
|
}
|