@linguanahq/vite 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/solid.ts DELETED
@@ -1,21 +0,0 @@
1
- import type { SourceAdapter } from "@linguanahq/build-core";
2
- import { transformSource } from "@linguanahq/compiler";
3
-
4
- export function solid(): SourceAdapter {
5
- return {
6
- id: "solid",
7
- version: "0.1.0",
8
- extensions: [".jsx", ".tsx"],
9
- runtimeModule: "@linguanahq/solid/compiler-runtime",
10
- async transform(input) {
11
- return transformSource(input.code, input.filename, {
12
- sourceLocale: input.sourceLocale,
13
- root: input.root,
14
- runtimePackage: "@linguanahq/solid",
15
- compilerRuntime: "@linguanahq/solid/compiler-runtime",
16
- }) as ReturnType<SourceAdapter["transform"]> extends Promise<infer T>
17
- ? T
18
- : never;
19
- },
20
- };
21
- }
package/src/svelte.ts DELETED
@@ -1,226 +0,0 @@
1
- import type { SourceAdapter } from "@linguanahq/build-core";
2
- import type { Diagnostic, ManifestMessage } from "@linguanahq/catalog";
3
- import MagicString from "magic-string";
4
- import { parse } from "svelte/compiler";
5
-
6
- import {
7
- attributeMessage,
8
- EXCLUDED_ELEMENTS,
9
- escapeAttribute,
10
- explicitMessage,
11
- message,
12
- renderedText,
13
- TRANSLATABLE_ATTRIBUTES,
14
- unsafeDiagnostic,
15
- } from "#shared";
16
-
17
- type SvelteAttribute = {
18
- type: string;
19
- name: string;
20
- start: number;
21
- end: number;
22
- value?: Array<{ type: string; data?: string }>;
23
- };
24
-
25
- type SvelteNode = {
26
- type: string;
27
- name?: string;
28
- start: number;
29
- end: number;
30
- data?: string;
31
- attributes?: SvelteAttribute[];
32
- fragment?: { nodes?: SvelteNode[] };
33
- [key: string]: unknown;
34
- };
35
-
36
- type SvelteRoot = {
37
- fragment: SvelteNode;
38
- instance?: { content?: { start?: number } };
39
- };
40
-
41
- function position(code: string, offset: number) {
42
- const before = code.slice(0, offset).split("\n");
43
- return { line: before.length, column: (before.at(-1) ?? "").length };
44
- }
45
-
46
- export function svelte(): SourceAdapter {
47
- return {
48
- id: "svelte",
49
- version: "0.1.0",
50
- extensions: [".svelte"],
51
- runtimeModule: "@linguanahq/svelte/compiler-runtime",
52
- async transform(input) {
53
- const ast = parse(input.code, {
54
- filename: input.filename,
55
- modern: true,
56
- }) as unknown as SvelteRoot;
57
- const output = new MagicString(input.code);
58
- const messages: ManifestMessage[] = [];
59
- const diagnostics: Diagnostic[] = [];
60
- const seen = new Set<SvelteNode>();
61
- const visit = (
62
- node: SvelteNode,
63
- excluded = false,
64
- parentTag = "svelte:fragment",
65
- ) => {
66
- if (!node || typeof node !== "object" || seen.has(node)) return;
67
- seen.add(node);
68
- const tag = ["RegularElement", "Component"].includes(node.type)
69
- ? String(node.name)
70
- : parentTag;
71
- const nextExcluded =
72
- excluded ||
73
- EXCLUDED_ELEMENTS.has(tag.toLowerCase()) ||
74
- (node.attributes ?? []).some((attribute) =>
75
- ["data-no-translate", "hidden"].includes(attribute.name),
76
- );
77
- if (
78
- node.type === "Component" &&
79
- ["T", "LinguanaMessage"].includes(tag)
80
- ) {
81
- const literal = (name: string) => {
82
- const attribute = (node.attributes ?? []).find(
83
- (item) => item.type === "Attribute" && item.name === name,
84
- );
85
- return attribute?.value?.length === 1 &&
86
- attribute.value[0]?.type === "Text"
87
- ? attribute.value[0].data
88
- : undefined;
89
- };
90
- const source = literal("defaultMessage");
91
- if (source) {
92
- const loc = position(input.code, node.start);
93
- messages.push(
94
- explicitMessage({
95
- source,
96
- explicitId: literal("id"),
97
- filename: input.filename,
98
- root: input.root,
99
- line: loc.line,
100
- column: loc.column,
101
- sourceLocale: input.sourceLocale,
102
- }),
103
- );
104
- }
105
- return;
106
- }
107
- const fragmentChildren = node.fragment?.nodes ?? [];
108
- if (
109
- node.type === "RegularElement" &&
110
- fragmentChildren.some(
111
- (child: { type: string; data?: string }) =>
112
- child.type === "Text" && Boolean(renderedText(child.data ?? "")),
113
- ) &&
114
- fragmentChildren.some(
115
- (child: { type: string }) => child.type !== "Text",
116
- )
117
- ) {
118
- const loc = position(input.code, node.start);
119
- diagnostics.push({
120
- code: "ambiguous-rich-text",
121
- severity: "warning",
122
- message:
123
- "Mixed text and Svelte template nodes require an explicit <T> message.",
124
- location: {
125
- file: input.filename.slice(input.root.length + 1),
126
- line: loc.line,
127
- column: loc.column,
128
- },
129
- remediation: "Use <T defaultMessage> or data-no-translate.",
130
- });
131
- return;
132
- }
133
- if (node.type === "RegularElement" && !nextExcluded) {
134
- for (const attribute of node.attributes ?? []) {
135
- const text =
136
- attribute.type === "Attribute" &&
137
- attribute.value?.length === 1 &&
138
- attribute.value[0]?.type === "Text"
139
- ? attribute.value[0]
140
- : undefined;
141
- if (
142
- !text ||
143
- typeof text.data !== "string" ||
144
- !TRANSLATABLE_ATTRIBUTES.has(attribute.name)
145
- )
146
- continue;
147
- const loc = position(input.code, attribute.start);
148
- const item = attributeMessage({
149
- source: text.data,
150
- filename: input.filename,
151
- root: input.root,
152
- line: loc.line,
153
- column: loc.column,
154
- sourceLocale: input.sourceLocale,
155
- element: tag,
156
- attribute: attribute.name,
157
- });
158
- messages.push(item);
159
- output.overwrite(
160
- attribute.start,
161
- attribute.end,
162
- `${attribute.name}={String(__linguana.translate(${JSON.stringify(item.id)}, ${JSON.stringify(text.data)}))}`,
163
- );
164
- }
165
- }
166
- if (node.type === "Text" && !nextExcluded) {
167
- const source = renderedText(
168
- node.data ?? input.code.slice(node.start, node.end),
169
- );
170
- if (source) {
171
- const loc = position(input.code, node.start);
172
- const item = message({
173
- source,
174
- filename: input.filename,
175
- root: input.root,
176
- line: loc.line,
177
- column: loc.column,
178
- sourceLocale: input.sourceLocale,
179
- element: parentTag,
180
- });
181
- const unsafe = unsafeDiagnostic(item);
182
- if (unsafe) diagnostics.push(unsafe);
183
- else {
184
- messages.push(item);
185
- output.overwrite(
186
- node.start,
187
- node.end,
188
- `<LinguanaMessage id="${item.id}" defaultMessage="${escapeAttribute(source)}" />`,
189
- );
190
- }
191
- }
192
- }
193
- for (const [key, value] of Object.entries(node)) {
194
- if (["parent", "metadata", "attributes"].includes(key)) continue;
195
- if (Array.isArray(value))
196
- for (const child of value)
197
- visit(child as SvelteNode, nextExcluded, tag);
198
- else if (value && typeof value === "object" && "type" in value)
199
- visit(value as SvelteNode, nextExcluded, tag);
200
- }
201
- };
202
- visit(ast.fragment);
203
- if (!messages.length && !diagnostics.length) return null;
204
- if (messages.length) {
205
- const importStatement = `\n<script>\nimport LinguanaMessage from "@linguanahq/svelte/compiler-runtime";\nimport { getLinguana } from "@linguanahq/svelte";\nconst __linguana = getLinguana();\n</script>\n`;
206
- const instance = ast.instance;
207
- if (instance?.content?.start !== undefined)
208
- output.appendLeft(
209
- instance.content.start,
210
- `\nimport LinguanaMessage from "@linguanahq/svelte/compiler-runtime";\nimport { getLinguana as __getLinguana } from "@linguanahq/svelte";\nconst __linguana = __getLinguana();`,
211
- );
212
- else output.prepend(importStatement);
213
- }
214
- return {
215
- code: output.toString(),
216
- map: output.generateMap({
217
- hires: true,
218
- source: input.filename,
219
- includeContent: true,
220
- }) as unknown as Record<string, unknown>,
221
- messages,
222
- diagnostics,
223
- };
224
- },
225
- };
226
- }
package/src/vue.ts DELETED
@@ -1,191 +0,0 @@
1
- import type { SourceAdapter } from "@linguanahq/build-core";
2
- import type { Diagnostic, ManifestMessage } from "@linguanahq/catalog";
3
- import { NodeTypes } from "@vue/compiler-dom";
4
- import { parse } from "@vue/compiler-sfc";
5
- import MagicString from "magic-string";
6
-
7
- import {
8
- attributeMessage,
9
- EXCLUDED_ELEMENTS,
10
- escapeAttribute,
11
- explicitMessage,
12
- message,
13
- renderedText,
14
- TRANSLATABLE_ATTRIBUTES,
15
- unsafeDiagnostic,
16
- } from "#shared";
17
-
18
- type VueAttribute = {
19
- type: number;
20
- name: string;
21
- value?: { content: string };
22
- loc: {
23
- start: { offset: number; line: number; column: number };
24
- end: { offset: number };
25
- };
26
- };
27
-
28
- type VueNode = {
29
- type: number;
30
- tag?: string;
31
- content?: string;
32
- props?: VueAttribute[];
33
- children?: VueNode[];
34
- loc: {
35
- start: { offset: number; line: number; column: number };
36
- end: { offset: number };
37
- };
38
- };
39
-
40
- export function vue(): SourceAdapter {
41
- return {
42
- id: "vue",
43
- version: "0.1.0",
44
- extensions: [".vue"],
45
- runtimeModule: "@linguanahq/vue/compiler-runtime",
46
- async transform(input) {
47
- const parsed = parse(input.code, {
48
- filename: input.filename,
49
- sourceMap: true,
50
- });
51
- const template = parsed.descriptor.template;
52
- if (!template?.ast) return null;
53
- const output = new MagicString(input.code);
54
- const messages: ManifestMessage[] = [];
55
- const diagnostics: Diagnostic[] = [];
56
- const visit = (
57
- node: VueNode,
58
- excluded = false,
59
- parentTag = "template",
60
- ) => {
61
- const tag =
62
- node.type === NodeTypes.ELEMENT ? String(node.tag) : parentTag;
63
- const nextExcluded =
64
- excluded ||
65
- EXCLUDED_ELEMENTS.has(tag.toLowerCase()) ||
66
- (node.props ?? []).some(
67
- (prop) =>
68
- prop.type === NodeTypes.ATTRIBUTE &&
69
- ["data-no-translate", "hidden"].includes(prop.name),
70
- );
71
- if (
72
- node.type === NodeTypes.ELEMENT &&
73
- ["T", "LinguanaMessage"].includes(tag)
74
- ) {
75
- const source = (node.props ?? []).find(
76
- (prop) =>
77
- prop.type === NodeTypes.ATTRIBUTE &&
78
- ["default-message", "defaultMessage"].includes(prop.name),
79
- )?.value?.content;
80
- const explicitId = (node.props ?? []).find(
81
- (prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === "id",
82
- )?.value?.content;
83
- if (source)
84
- messages.push(
85
- explicitMessage({
86
- source,
87
- explicitId,
88
- filename: input.filename,
89
- root: input.root,
90
- line: node.loc.start.line,
91
- column: node.loc.start.column - 1,
92
- sourceLocale: input.sourceLocale,
93
- }),
94
- );
95
- return;
96
- }
97
- if (
98
- node.type === NodeTypes.ELEMENT &&
99
- (node.children ?? []).some(
100
- (child: { type: number; content?: string }) =>
101
- child.type === NodeTypes.TEXT &&
102
- Boolean(renderedText(child.content ?? "")),
103
- ) &&
104
- (node.children ?? []).some(
105
- (child: { type: number }) => child.type !== NodeTypes.TEXT,
106
- )
107
- ) {
108
- diagnostics.push({
109
- code: "ambiguous-rich-text",
110
- severity: "warning",
111
- message:
112
- "Mixed text and Vue template nodes require an explicit <T> message.",
113
- location: {
114
- file: input.filename.slice(input.root.length + 1),
115
- line: node.loc.start.line,
116
- column: node.loc.start.column - 1,
117
- },
118
- remediation: "Use <T default-message> or data-no-translate.",
119
- });
120
- return;
121
- }
122
- if (node.type === NodeTypes.ELEMENT && !nextExcluded) {
123
- for (const prop of node.props ?? []) {
124
- if (
125
- prop.type !== NodeTypes.ATTRIBUTE ||
126
- !prop.value ||
127
- !TRANSLATABLE_ATTRIBUTES.has(prop.name)
128
- )
129
- continue;
130
- const item = attributeMessage({
131
- source: prop.value.content,
132
- filename: input.filename,
133
- root: input.root,
134
- line: prop.loc.start.line,
135
- column: prop.loc.start.column - 1,
136
- sourceLocale: input.sourceLocale,
137
- element: tag,
138
- attribute: prop.name,
139
- });
140
- messages.push(item);
141
- const expression = `$tAttr(${JSON.stringify(item.id)}, ${JSON.stringify(prop.value.content)})`;
142
- const replacement = `:${prop.name}='${expression.replaceAll("&", "&amp;").replaceAll("'", "&#39;")}'`;
143
- output.overwrite(
144
- prop.loc.start.offset,
145
- prop.loc.end.offset,
146
- replacement,
147
- );
148
- }
149
- }
150
- if (node.type === NodeTypes.TEXT && !nextExcluded) {
151
- const source = renderedText(node.content ?? "");
152
- if (source) {
153
- const item = message({
154
- source,
155
- filename: input.filename,
156
- root: input.root,
157
- line: node.loc.start.line,
158
- column: node.loc.start.column - 1,
159
- sourceLocale: input.sourceLocale,
160
- element: parentTag,
161
- });
162
- const unsafe = unsafeDiagnostic(item);
163
- if (unsafe) diagnostics.push(unsafe);
164
- else {
165
- messages.push(item);
166
- output.overwrite(
167
- node.loc.start.offset,
168
- node.loc.end.offset,
169
- `<LinguanaMessage id="${item.id}" default-message="${escapeAttribute(source)}" />`,
170
- );
171
- }
172
- }
173
- }
174
- for (const child of node.children ?? [])
175
- visit(child, nextExcluded, tag);
176
- };
177
- visit(template.ast as unknown as VueNode);
178
- if (!messages.length && !diagnostics.length) return null;
179
- return {
180
- code: output.toString(),
181
- map: output.generateMap({
182
- hires: true,
183
- source: input.filename,
184
- includeContent: true,
185
- }) as unknown as Record<string, unknown>,
186
- messages,
187
- diagnostics,
188
- };
189
- },
190
- };
191
- }