@linguanahq/vite 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/adapters.test.d.ts +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +622 -0
- package/dist/preview-client.d.ts +2 -0
- package/dist/preview.d.ts +41 -0
- package/dist/preview.test.d.ts +1 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +19 -0
- package/dist/shared.d.ts +34 -0
- package/dist/solid.d.ts +2 -0
- package/dist/solid.js +21 -0
- package/dist/svelte.d.ts +2 -0
- package/dist/svelte.js +282 -0
- package/dist/vue.d.ts +2 -0
- package/dist/vue.js +241 -0
- package/package.json +64 -49
- package/src/index.ts +0 -173
- package/src/react.ts +0 -19
- package/src/shared.ts +0 -145
- package/src/solid.ts +0 -21
- package/src/svelte.ts +0 -226
- package/src/vue.ts +0 -191
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/react.d.ts
ADDED
package/dist/react.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/react.ts
|
|
2
|
+
import { transformSource } from "@linguanahq/compiler/transform";
|
|
3
|
+
function react() {
|
|
4
|
+
return {
|
|
5
|
+
id: "react",
|
|
6
|
+
version: "0.1.2",
|
|
7
|
+
extensions: [".jsx", ".tsx"],
|
|
8
|
+
runtimeModule: "@linguanahq/react/compiler-runtime",
|
|
9
|
+
async transform(input) {
|
|
10
|
+
return transformSource(input.code, input.filename, {
|
|
11
|
+
sourceLocale: input.sourceLocale,
|
|
12
|
+
root: input.root
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
react
|
|
19
|
+
};
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Diagnostic, type ManifestMessage } from "@linguanahq/catalog";
|
|
2
|
+
export declare const EXCLUDED_ELEMENTS: Set<string>;
|
|
3
|
+
export declare const TRANSLATABLE_ATTRIBUTES: Set<string>;
|
|
4
|
+
export declare function renderedText(value: string): string;
|
|
5
|
+
export declare function escapeAttribute(value: string): string;
|
|
6
|
+
export declare function message(input: {
|
|
7
|
+
source: string;
|
|
8
|
+
filename: string;
|
|
9
|
+
root: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
sourceLocale: string;
|
|
13
|
+
element: string;
|
|
14
|
+
}): ManifestMessage;
|
|
15
|
+
export declare function attributeMessage(input: {
|
|
16
|
+
source: string;
|
|
17
|
+
filename: string;
|
|
18
|
+
root: string;
|
|
19
|
+
line: number;
|
|
20
|
+
column: number;
|
|
21
|
+
sourceLocale: string;
|
|
22
|
+
element: string;
|
|
23
|
+
attribute: string;
|
|
24
|
+
}): ManifestMessage;
|
|
25
|
+
export declare function explicitMessage(input: {
|
|
26
|
+
source: string;
|
|
27
|
+
explicitId?: string;
|
|
28
|
+
filename: string;
|
|
29
|
+
root: string;
|
|
30
|
+
line: number;
|
|
31
|
+
column: number;
|
|
32
|
+
sourceLocale: string;
|
|
33
|
+
}): ManifestMessage;
|
|
34
|
+
export declare function unsafeDiagnostic(item: ManifestMessage): Diagnostic | undefined;
|
package/dist/solid.d.ts
ADDED
package/dist/solid.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/solid.ts
|
|
2
|
+
import { transformSource } from "@linguanahq/compiler/transform";
|
|
3
|
+
function solid() {
|
|
4
|
+
return {
|
|
5
|
+
id: "solid",
|
|
6
|
+
version: "0.1.2",
|
|
7
|
+
extensions: [".jsx", ".tsx"],
|
|
8
|
+
runtimeModule: "@linguanahq/solid/compiler-runtime",
|
|
9
|
+
async transform(input) {
|
|
10
|
+
return transformSource(input.code, input.filename, {
|
|
11
|
+
sourceLocale: input.sourceLocale,
|
|
12
|
+
root: input.root,
|
|
13
|
+
runtimePackage: "@linguanahq/solid",
|
|
14
|
+
compilerRuntime: "@linguanahq/solid/compiler-runtime"
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
solid
|
|
21
|
+
};
|
package/dist/svelte.d.ts
ADDED
package/dist/svelte.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// src/svelte.ts
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
import { parse } from "svelte/compiler";
|
|
4
|
+
|
|
5
|
+
// src/shared.ts
|
|
6
|
+
import { relative } from "node:path";
|
|
7
|
+
import {
|
|
8
|
+
contentHash,
|
|
9
|
+
normalizeSource,
|
|
10
|
+
parseMessagePlaceholders,
|
|
11
|
+
stableMessageId
|
|
12
|
+
} from "@linguanahq/catalog";
|
|
13
|
+
var EXCLUDED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre"]);
|
|
14
|
+
var TRANSLATABLE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
15
|
+
"title",
|
|
16
|
+
"placeholder",
|
|
17
|
+
"aria-label",
|
|
18
|
+
"alt",
|
|
19
|
+
"label"
|
|
20
|
+
]);
|
|
21
|
+
function renderedText(value) {
|
|
22
|
+
return value.replace(/\s+/g, " ").trim();
|
|
23
|
+
}
|
|
24
|
+
function escapeAttribute(value) {
|
|
25
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<");
|
|
26
|
+
}
|
|
27
|
+
function message(input) {
|
|
28
|
+
const context = { kind: "text", element: input.element };
|
|
29
|
+
return {
|
|
30
|
+
id: stableMessageId({
|
|
31
|
+
sourceLocale: input.sourceLocale,
|
|
32
|
+
source: input.source,
|
|
33
|
+
context
|
|
34
|
+
}),
|
|
35
|
+
source: input.source,
|
|
36
|
+
normalized: normalizeSource(input.source),
|
|
37
|
+
contentHash: contentHash(input.source),
|
|
38
|
+
context,
|
|
39
|
+
placeholders: parseMessagePlaceholders(input.source),
|
|
40
|
+
confidence: "high",
|
|
41
|
+
locations: [
|
|
42
|
+
{
|
|
43
|
+
file: relative(input.root, input.filename).replaceAll("\\", "/"),
|
|
44
|
+
line: input.line,
|
|
45
|
+
column: input.column
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function attributeMessage(input) {
|
|
51
|
+
const context = {
|
|
52
|
+
kind: "attribute",
|
|
53
|
+
element: input.element,
|
|
54
|
+
attribute: input.attribute
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
id: stableMessageId({
|
|
58
|
+
sourceLocale: input.sourceLocale,
|
|
59
|
+
source: input.source,
|
|
60
|
+
context
|
|
61
|
+
}),
|
|
62
|
+
source: input.source,
|
|
63
|
+
normalized: normalizeSource(input.source),
|
|
64
|
+
contentHash: contentHash(input.source),
|
|
65
|
+
context,
|
|
66
|
+
placeholders: parseMessagePlaceholders(input.source),
|
|
67
|
+
confidence: "high",
|
|
68
|
+
locations: [
|
|
69
|
+
{
|
|
70
|
+
file: relative(input.root, input.filename).replaceAll("\\", "/"),
|
|
71
|
+
line: input.line,
|
|
72
|
+
column: input.column
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function explicitMessage(input) {
|
|
78
|
+
const context = { kind: "explicit" };
|
|
79
|
+
return {
|
|
80
|
+
id: stableMessageId({
|
|
81
|
+
sourceLocale: input.sourceLocale,
|
|
82
|
+
source: input.source,
|
|
83
|
+
context,
|
|
84
|
+
explicitId: input.explicitId
|
|
85
|
+
}),
|
|
86
|
+
source: input.source,
|
|
87
|
+
normalized: normalizeSource(input.source),
|
|
88
|
+
contentHash: contentHash(input.source),
|
|
89
|
+
context,
|
|
90
|
+
placeholders: parseMessagePlaceholders(input.source),
|
|
91
|
+
confidence: "explicit",
|
|
92
|
+
locations: [
|
|
93
|
+
{
|
|
94
|
+
file: relative(input.root, input.filename).replaceAll("\\", "/"),
|
|
95
|
+
line: input.line,
|
|
96
|
+
column: input.column
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function unsafeDiagnostic(item) {
|
|
102
|
+
if (/^(?:https?:\/\/|mailto:|tel:|www\.)/i.test(item.normalized))
|
|
103
|
+
return {
|
|
104
|
+
code: "unsafe-static-copy",
|
|
105
|
+
severity: "info",
|
|
106
|
+
message: "URL-like or machine-readable text was excluded from extraction.",
|
|
107
|
+
location: item.locations[0]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/svelte.ts
|
|
112
|
+
function position(code, offset) {
|
|
113
|
+
const before = code.slice(0, offset).split("\n");
|
|
114
|
+
return { line: before.length, column: (before.at(-1) ?? "").length };
|
|
115
|
+
}
|
|
116
|
+
function svelte() {
|
|
117
|
+
return {
|
|
118
|
+
id: "svelte",
|
|
119
|
+
version: "0.1.2",
|
|
120
|
+
extensions: [".svelte"],
|
|
121
|
+
runtimeModule: "@linguanahq/svelte/compiler-runtime",
|
|
122
|
+
async transform(input) {
|
|
123
|
+
const ast = parse(input.code, {
|
|
124
|
+
filename: input.filename,
|
|
125
|
+
modern: true
|
|
126
|
+
});
|
|
127
|
+
const output = new MagicString(input.code);
|
|
128
|
+
const messages = [];
|
|
129
|
+
const diagnostics = [];
|
|
130
|
+
const seen = /* @__PURE__ */ new Set();
|
|
131
|
+
const visit = (node, excluded = false, parentTag = "svelte:fragment") => {
|
|
132
|
+
if (!node || typeof node !== "object" || seen.has(node)) return;
|
|
133
|
+
seen.add(node);
|
|
134
|
+
const tag = ["RegularElement", "Component"].includes(node.type) ? String(node.name) : parentTag;
|
|
135
|
+
const nextExcluded = excluded || EXCLUDED_ELEMENTS.has(tag.toLowerCase()) || (node.attributes ?? []).some(
|
|
136
|
+
(attribute) => ["data-no-translate", "hidden"].includes(attribute.name)
|
|
137
|
+
);
|
|
138
|
+
if (node.type === "Component" && ["T", "LinguanaMessage"].includes(tag)) {
|
|
139
|
+
const literal = (name) => {
|
|
140
|
+
const attribute = (node.attributes ?? []).find(
|
|
141
|
+
(item) => item.type === "Attribute" && item.name === name
|
|
142
|
+
);
|
|
143
|
+
const value = attribute?.value;
|
|
144
|
+
if (Array.isArray(value) && value.length === 1 && value[0]?.type === "Text")
|
|
145
|
+
return value[0].data;
|
|
146
|
+
const expression = (Array.isArray(value) ? value[0] : value)?.expression;
|
|
147
|
+
return expression?.type === "Literal" && typeof expression.value === "string" ? expression.value : void 0;
|
|
148
|
+
};
|
|
149
|
+
const source = literal("defaultMessage");
|
|
150
|
+
if (source) {
|
|
151
|
+
const loc = position(input.code, node.start);
|
|
152
|
+
messages.push(
|
|
153
|
+
explicitMessage({
|
|
154
|
+
source,
|
|
155
|
+
explicitId: literal("id"),
|
|
156
|
+
filename: input.filename,
|
|
157
|
+
root: input.root,
|
|
158
|
+
line: loc.line,
|
|
159
|
+
column: loc.column,
|
|
160
|
+
sourceLocale: input.sourceLocale
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const fragmentChildren = node.fragment?.nodes ?? [];
|
|
167
|
+
if (node.type === "RegularElement" && fragmentChildren.some(
|
|
168
|
+
(child) => child.type === "Text" && Boolean(renderedText(child.data ?? ""))
|
|
169
|
+
) && fragmentChildren.some(
|
|
170
|
+
(child) => child.type !== "Text"
|
|
171
|
+
)) {
|
|
172
|
+
const loc = position(input.code, node.start);
|
|
173
|
+
diagnostics.push({
|
|
174
|
+
code: "ambiguous-rich-text",
|
|
175
|
+
severity: "warning",
|
|
176
|
+
message: "Mixed text and Svelte template nodes require an explicit <T> message.",
|
|
177
|
+
location: {
|
|
178
|
+
file: input.filename.slice(input.root.length + 1),
|
|
179
|
+
line: loc.line,
|
|
180
|
+
column: loc.column
|
|
181
|
+
},
|
|
182
|
+
remediation: "Use <T defaultMessage> or data-no-translate."
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (node.type === "RegularElement" && !nextExcluded) {
|
|
187
|
+
for (const attribute of node.attributes ?? []) {
|
|
188
|
+
const text = attribute.type === "Attribute" && Array.isArray(attribute.value) && attribute.value.length === 1 && attribute.value[0]?.type === "Text" ? attribute.value[0] : void 0;
|
|
189
|
+
if (!text || typeof text.data !== "string" || !TRANSLATABLE_ATTRIBUTES.has(attribute.name))
|
|
190
|
+
continue;
|
|
191
|
+
const loc = position(input.code, attribute.start);
|
|
192
|
+
const item = attributeMessage({
|
|
193
|
+
source: text.data,
|
|
194
|
+
filename: input.filename,
|
|
195
|
+
root: input.root,
|
|
196
|
+
line: loc.line,
|
|
197
|
+
column: loc.column,
|
|
198
|
+
sourceLocale: input.sourceLocale,
|
|
199
|
+
element: tag,
|
|
200
|
+
attribute: attribute.name
|
|
201
|
+
});
|
|
202
|
+
messages.push(item);
|
|
203
|
+
output.overwrite(
|
|
204
|
+
attribute.start,
|
|
205
|
+
attribute.end,
|
|
206
|
+
`${attribute.name}={String(__linguana.translate(${JSON.stringify(item.id)}, ${JSON.stringify(text.data)}))}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (node.type === "Text" && !nextExcluded) {
|
|
211
|
+
const source = renderedText(
|
|
212
|
+
node.data ?? input.code.slice(node.start, node.end)
|
|
213
|
+
);
|
|
214
|
+
if (source) {
|
|
215
|
+
const loc = position(input.code, node.start);
|
|
216
|
+
const item = message({
|
|
217
|
+
source,
|
|
218
|
+
filename: input.filename,
|
|
219
|
+
root: input.root,
|
|
220
|
+
line: loc.line,
|
|
221
|
+
column: loc.column,
|
|
222
|
+
sourceLocale: input.sourceLocale,
|
|
223
|
+
element: parentTag
|
|
224
|
+
});
|
|
225
|
+
const unsafe = unsafeDiagnostic(item);
|
|
226
|
+
if (unsafe) diagnostics.push(unsafe);
|
|
227
|
+
else {
|
|
228
|
+
messages.push(item);
|
|
229
|
+
output.overwrite(
|
|
230
|
+
node.start,
|
|
231
|
+
node.end,
|
|
232
|
+
`<LinguanaMessage id="${item.id}" defaultMessage="${escapeAttribute(source)}" />`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const [key, value] of Object.entries(node)) {
|
|
238
|
+
if (["parent", "metadata", "attributes"].includes(key)) continue;
|
|
239
|
+
if (Array.isArray(value))
|
|
240
|
+
for (const child of value)
|
|
241
|
+
visit(child, nextExcluded, tag);
|
|
242
|
+
else if (value && typeof value === "object" && "type" in value)
|
|
243
|
+
visit(value, nextExcluded, tag);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
visit(ast.fragment);
|
|
247
|
+
if (!messages.length && !diagnostics.length) return null;
|
|
248
|
+
if (messages.length) {
|
|
249
|
+
const importStatement = `
|
|
250
|
+
<script>
|
|
251
|
+
import LinguanaMessage from "@linguanahq/svelte/compiler-runtime";
|
|
252
|
+
import { getLinguana } from "@linguanahq/svelte";
|
|
253
|
+
const __linguana = getLinguana();
|
|
254
|
+
</script>
|
|
255
|
+
`;
|
|
256
|
+
const instance = ast.instance;
|
|
257
|
+
if (instance?.content?.start !== void 0)
|
|
258
|
+
output.appendLeft(
|
|
259
|
+
instance.content.start,
|
|
260
|
+
`
|
|
261
|
+
import LinguanaMessage from "@linguanahq/svelte/compiler-runtime";
|
|
262
|
+
import { getLinguana as __getLinguana } from "@linguanahq/svelte";
|
|
263
|
+
const __linguana = __getLinguana();`
|
|
264
|
+
);
|
|
265
|
+
else output.prepend(importStatement);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
code: output.toString(),
|
|
269
|
+
map: output.generateMap({
|
|
270
|
+
hires: true,
|
|
271
|
+
source: input.filename,
|
|
272
|
+
includeContent: true
|
|
273
|
+
}),
|
|
274
|
+
messages,
|
|
275
|
+
diagnostics
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
export {
|
|
281
|
+
svelte
|
|
282
|
+
};
|
package/dist/vue.d.ts
ADDED
package/dist/vue.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/vue.ts
|
|
2
|
+
import { NodeTypes } from "@vue/compiler-dom";
|
|
3
|
+
import { parse } from "@vue/compiler-sfc";
|
|
4
|
+
import MagicString from "magic-string";
|
|
5
|
+
|
|
6
|
+
// src/shared.ts
|
|
7
|
+
import { relative } from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
contentHash,
|
|
10
|
+
normalizeSource,
|
|
11
|
+
parseMessagePlaceholders,
|
|
12
|
+
stableMessageId
|
|
13
|
+
} from "@linguanahq/catalog";
|
|
14
|
+
var EXCLUDED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre"]);
|
|
15
|
+
var TRANSLATABLE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
16
|
+
"title",
|
|
17
|
+
"placeholder",
|
|
18
|
+
"aria-label",
|
|
19
|
+
"alt",
|
|
20
|
+
"label"
|
|
21
|
+
]);
|
|
22
|
+
function renderedText(value) {
|
|
23
|
+
return value.replace(/\s+/g, " ").trim();
|
|
24
|
+
}
|
|
25
|
+
function escapeAttribute(value) {
|
|
26
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<");
|
|
27
|
+
}
|
|
28
|
+
function message(input) {
|
|
29
|
+
const context = { kind: "text", element: input.element };
|
|
30
|
+
return {
|
|
31
|
+
id: stableMessageId({
|
|
32
|
+
sourceLocale: input.sourceLocale,
|
|
33
|
+
source: input.source,
|
|
34
|
+
context
|
|
35
|
+
}),
|
|
36
|
+
source: input.source,
|
|
37
|
+
normalized: normalizeSource(input.source),
|
|
38
|
+
contentHash: contentHash(input.source),
|
|
39
|
+
context,
|
|
40
|
+
placeholders: parseMessagePlaceholders(input.source),
|
|
41
|
+
confidence: "high",
|
|
42
|
+
locations: [
|
|
43
|
+
{
|
|
44
|
+
file: relative(input.root, input.filename).replaceAll("\\", "/"),
|
|
45
|
+
line: input.line,
|
|
46
|
+
column: input.column
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function attributeMessage(input) {
|
|
52
|
+
const context = {
|
|
53
|
+
kind: "attribute",
|
|
54
|
+
element: input.element,
|
|
55
|
+
attribute: input.attribute
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
id: stableMessageId({
|
|
59
|
+
sourceLocale: input.sourceLocale,
|
|
60
|
+
source: input.source,
|
|
61
|
+
context
|
|
62
|
+
}),
|
|
63
|
+
source: input.source,
|
|
64
|
+
normalized: normalizeSource(input.source),
|
|
65
|
+
contentHash: contentHash(input.source),
|
|
66
|
+
context,
|
|
67
|
+
placeholders: parseMessagePlaceholders(input.source),
|
|
68
|
+
confidence: "high",
|
|
69
|
+
locations: [
|
|
70
|
+
{
|
|
71
|
+
file: relative(input.root, input.filename).replaceAll("\\", "/"),
|
|
72
|
+
line: input.line,
|
|
73
|
+
column: input.column
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function explicitMessage(input) {
|
|
79
|
+
const context = { kind: "explicit" };
|
|
80
|
+
return {
|
|
81
|
+
id: stableMessageId({
|
|
82
|
+
sourceLocale: input.sourceLocale,
|
|
83
|
+
source: input.source,
|
|
84
|
+
context,
|
|
85
|
+
explicitId: input.explicitId
|
|
86
|
+
}),
|
|
87
|
+
source: input.source,
|
|
88
|
+
normalized: normalizeSource(input.source),
|
|
89
|
+
contentHash: contentHash(input.source),
|
|
90
|
+
context,
|
|
91
|
+
placeholders: parseMessagePlaceholders(input.source),
|
|
92
|
+
confidence: "explicit",
|
|
93
|
+
locations: [
|
|
94
|
+
{
|
|
95
|
+
file: relative(input.root, input.filename).replaceAll("\\", "/"),
|
|
96
|
+
line: input.line,
|
|
97
|
+
column: input.column
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function unsafeDiagnostic(item) {
|
|
103
|
+
if (/^(?:https?:\/\/|mailto:|tel:|www\.)/i.test(item.normalized))
|
|
104
|
+
return {
|
|
105
|
+
code: "unsafe-static-copy",
|
|
106
|
+
severity: "info",
|
|
107
|
+
message: "URL-like or machine-readable text was excluded from extraction.",
|
|
108
|
+
location: item.locations[0]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/vue.ts
|
|
113
|
+
function vue() {
|
|
114
|
+
return {
|
|
115
|
+
id: "vue",
|
|
116
|
+
version: "0.1.2",
|
|
117
|
+
extensions: [".vue"],
|
|
118
|
+
runtimeModule: "@linguanahq/vue/compiler-runtime",
|
|
119
|
+
async transform(input) {
|
|
120
|
+
const parsed = parse(input.code, {
|
|
121
|
+
filename: input.filename,
|
|
122
|
+
sourceMap: true
|
|
123
|
+
});
|
|
124
|
+
const template = parsed.descriptor.template;
|
|
125
|
+
if (!template?.ast) return null;
|
|
126
|
+
const output = new MagicString(input.code);
|
|
127
|
+
const messages = [];
|
|
128
|
+
const diagnostics = [];
|
|
129
|
+
const visit = (node, excluded = false, parentTag = "template") => {
|
|
130
|
+
const tag = node.type === NodeTypes.ELEMENT ? String(node.tag) : parentTag;
|
|
131
|
+
const nextExcluded = excluded || EXCLUDED_ELEMENTS.has(tag.toLowerCase()) || (node.props ?? []).some(
|
|
132
|
+
(prop) => prop.type === NodeTypes.ATTRIBUTE && ["data-no-translate", "hidden"].includes(prop.name)
|
|
133
|
+
);
|
|
134
|
+
if (node.type === NodeTypes.ELEMENT && ["T", "LinguanaMessage"].includes(tag)) {
|
|
135
|
+
const source = (node.props ?? []).find(
|
|
136
|
+
(prop) => prop.type === NodeTypes.ATTRIBUTE && ["default-message", "defaultMessage"].includes(prop.name)
|
|
137
|
+
)?.value?.content;
|
|
138
|
+
const explicitId = (node.props ?? []).find(
|
|
139
|
+
(prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === "id"
|
|
140
|
+
)?.value?.content;
|
|
141
|
+
if (source)
|
|
142
|
+
messages.push(
|
|
143
|
+
explicitMessage({
|
|
144
|
+
source,
|
|
145
|
+
explicitId,
|
|
146
|
+
filename: input.filename,
|
|
147
|
+
root: input.root,
|
|
148
|
+
line: node.loc.start.line,
|
|
149
|
+
column: node.loc.start.column - 1,
|
|
150
|
+
sourceLocale: input.sourceLocale
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (node.type === NodeTypes.ELEMENT && (node.children ?? []).some(
|
|
156
|
+
(child) => child.type === NodeTypes.TEXT && Boolean(renderedText(child.content ?? ""))
|
|
157
|
+
) && (node.children ?? []).some(
|
|
158
|
+
(child) => child.type !== NodeTypes.TEXT
|
|
159
|
+
)) {
|
|
160
|
+
diagnostics.push({
|
|
161
|
+
code: "ambiguous-rich-text",
|
|
162
|
+
severity: "warning",
|
|
163
|
+
message: "Mixed text and Vue template nodes require an explicit <T> message.",
|
|
164
|
+
location: {
|
|
165
|
+
file: input.filename.slice(input.root.length + 1),
|
|
166
|
+
line: node.loc.start.line,
|
|
167
|
+
column: node.loc.start.column - 1
|
|
168
|
+
},
|
|
169
|
+
remediation: "Use <T default-message> or data-no-translate."
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (node.type === NodeTypes.ELEMENT && !nextExcluded) {
|
|
174
|
+
for (const prop of node.props ?? []) {
|
|
175
|
+
if (prop.type !== NodeTypes.ATTRIBUTE || !prop.value || !TRANSLATABLE_ATTRIBUTES.has(prop.name))
|
|
176
|
+
continue;
|
|
177
|
+
const item = attributeMessage({
|
|
178
|
+
source: prop.value.content,
|
|
179
|
+
filename: input.filename,
|
|
180
|
+
root: input.root,
|
|
181
|
+
line: prop.loc.start.line,
|
|
182
|
+
column: prop.loc.start.column - 1,
|
|
183
|
+
sourceLocale: input.sourceLocale,
|
|
184
|
+
element: tag,
|
|
185
|
+
attribute: prop.name
|
|
186
|
+
});
|
|
187
|
+
messages.push(item);
|
|
188
|
+
const expression = `$tAttr(${JSON.stringify(item.id)}, ${JSON.stringify(prop.value.content)})`;
|
|
189
|
+
const replacement = `:${prop.name}='${expression.replaceAll("&", "&").replaceAll("'", "'")}'`;
|
|
190
|
+
output.overwrite(
|
|
191
|
+
prop.loc.start.offset,
|
|
192
|
+
prop.loc.end.offset,
|
|
193
|
+
replacement
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (node.type === NodeTypes.TEXT && !nextExcluded) {
|
|
198
|
+
const source = renderedText(node.content ?? "");
|
|
199
|
+
if (source) {
|
|
200
|
+
const item = message({
|
|
201
|
+
source,
|
|
202
|
+
filename: input.filename,
|
|
203
|
+
root: input.root,
|
|
204
|
+
line: node.loc.start.line,
|
|
205
|
+
column: node.loc.start.column - 1,
|
|
206
|
+
sourceLocale: input.sourceLocale,
|
|
207
|
+
element: parentTag
|
|
208
|
+
});
|
|
209
|
+
const unsafe = unsafeDiagnostic(item);
|
|
210
|
+
if (unsafe) diagnostics.push(unsafe);
|
|
211
|
+
else {
|
|
212
|
+
messages.push(item);
|
|
213
|
+
output.overwrite(
|
|
214
|
+
node.loc.start.offset,
|
|
215
|
+
node.loc.end.offset,
|
|
216
|
+
`<LinguanaMessage id="${item.id}" default-message="${escapeAttribute(source)}" />`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const child of node.children ?? [])
|
|
222
|
+
visit(child, nextExcluded, tag);
|
|
223
|
+
};
|
|
224
|
+
visit(template.ast);
|
|
225
|
+
if (!messages.length && !diagnostics.length) return null;
|
|
226
|
+
return {
|
|
227
|
+
code: output.toString(),
|
|
228
|
+
map: output.generateMap({
|
|
229
|
+
hires: true,
|
|
230
|
+
source: input.filename,
|
|
231
|
+
includeContent: true
|
|
232
|
+
}),
|
|
233
|
+
messages,
|
|
234
|
+
diagnostics
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
vue
|
|
241
|
+
};
|