@let-value/translate-extract 1.0.30 → 1.0.31
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/bin/cli.cjs +7 -6
- package/dist/bin/cli.js +1 -1
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/configuration-CFa8J7EM.d.ts +240 -0
- package/dist/configuration-CFa8J7EM.d.ts.map +1 -0
- package/dist/configuration-nsGqA5N9.d.cts +239 -0
- package/dist/configuration-nsGqA5N9.d.cts.map +1 -0
- package/dist/core-ACuLoi2B.d.ts +1 -0
- package/dist/core-CHb_Xdzl.cjs +65 -0
- package/dist/core-CPg6_re5.d.cts +1 -0
- package/dist/core-DR3oxhSq.js +59 -0
- package/dist/core-DR3oxhSq.js.map +1 -0
- package/dist/run-BacOPd9p.cjs +1479 -0
- package/dist/run-DcO6U79B.js +1439 -0
- package/dist/run-DcO6U79B.js.map +1 -0
- package/dist/src/core.cjs +4 -0
- package/dist/src/core.d.cts +3 -0
- package/dist/src/core.d.ts +3 -0
- package/dist/src/core.js +4 -0
- package/dist/src/index.cjs +9 -1320
- package/dist/src/index.d.cts +3 -209
- package/dist/src/index.d.cts.map +1 -1
- package/dist/src/index.d.ts +3 -210
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1306
- package/dist/src/static.cjs +6 -0
- package/dist/src/static.d.cts +2 -0
- package/dist/src/static.d.ts +2 -0
- package/dist/src/static.js +3 -0
- package/dist/static-CNiWpXhx.cjs +52 -0
- package/dist/static-DQHT7uqP.js +29 -0
- package/dist/static-DQHT7uqP.js.map +1 -0
- package/package.json +12 -2
- package/dist/run-BonabcP6.cjs +0 -209
- package/dist/run-C_bYec-q.js +0 -175
- package/dist/run-C_bYec-q.js.map +0 -1
- package/dist/src/index.js.map +0 -1
package/dist/src/index.js
CHANGED
|
@@ -1,1307 +1,5 @@
|
|
|
1
|
-
import { run } from "../run-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import fs, { readFile } from "node:fs/promises";
|
|
5
|
-
import * as gettextParser from "gettext-parser";
|
|
6
|
-
import fs$1 from "node:fs";
|
|
7
|
-
import Parser from "tree-sitter";
|
|
8
|
-
import JavaScript from "tree-sitter-javascript";
|
|
9
|
-
import TS from "tree-sitter-typescript";
|
|
10
|
-
import { ResolverFactory } from "oxc-resolver";
|
|
11
|
-
import { getFormula, getNPlurals } from "plural-forms";
|
|
1
|
+
import { cleanup, core, po, react, run } from "../run-DcO6U79B.js";
|
|
2
|
+
import "../static-DQHT7uqP.js";
|
|
3
|
+
import { defineConfig } from "../core-DR3oxhSq.js";
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
const namespace$2 = "cleanup";
|
|
15
|
-
function cleanup() {
|
|
16
|
-
return {
|
|
17
|
-
name: "cleanup",
|
|
18
|
-
setup(build) {
|
|
19
|
-
build.context.logger?.debug("cleanup plugin initialized");
|
|
20
|
-
const processed = /* @__PURE__ */ new Set();
|
|
21
|
-
const generated = /* @__PURE__ */ new Set();
|
|
22
|
-
const dirs = /* @__PURE__ */ new Set();
|
|
23
|
-
let dispatched = false;
|
|
24
|
-
build.onResolve({
|
|
25
|
-
namespace: namespace$2,
|
|
26
|
-
filter: /.*/
|
|
27
|
-
}, ({ path: path$1 }) => {
|
|
28
|
-
generated.add(path$1);
|
|
29
|
-
dirs.add(dirname(path$1));
|
|
30
|
-
Promise.all([
|
|
31
|
-
build.defer("source"),
|
|
32
|
-
build.defer("translate"),
|
|
33
|
-
build.defer(namespace$2)
|
|
34
|
-
]).then(() => {
|
|
35
|
-
if (dispatched) return;
|
|
36
|
-
dispatched = true;
|
|
37
|
-
for (const path$2 of dirs.values()) build.process({
|
|
38
|
-
entrypoint: path$2,
|
|
39
|
-
path: path$2,
|
|
40
|
-
namespace: namespace$2,
|
|
41
|
-
data: void 0
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
build.onProcess({
|
|
46
|
-
namespace: namespace$2,
|
|
47
|
-
filter: /.*/
|
|
48
|
-
}, async ({ path: path$1 }) => {
|
|
49
|
-
if (processed.has(path$1)) return;
|
|
50
|
-
processed.add(path$1);
|
|
51
|
-
const files = await fs.readdir(path$1).catch(() => []);
|
|
52
|
-
for (const f of files.filter((p) => p.endsWith(".po"))) {
|
|
53
|
-
const full = join(path$1, f);
|
|
54
|
-
const contents = await fs.readFile(full).catch(() => void 0);
|
|
55
|
-
if (!contents) continue;
|
|
56
|
-
const parsed = gettextParser.po.parse(contents);
|
|
57
|
-
const hasTranslations = Object.entries(parsed.translations || {}).some(([ctx, msgs]) => Object.keys(msgs).some((id) => !(ctx === "" && id === "")));
|
|
58
|
-
if (!hasTranslations && generated.has(full)) await fs.unlink(full);
|
|
59
|
-
if (hasTranslations && !generated.has(full)) build.context.logger?.warn({ path: full }, "stray translation file");
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/plugins/core/queries/comment.ts
|
|
68
|
-
function getReference(node, { path: path$1 }) {
|
|
69
|
-
const line = node.startPosition.row + 1;
|
|
70
|
-
return `${relative(process.cwd(), path$1).replace(/\\+/g, "/")}:${line}`;
|
|
71
|
-
}
|
|
72
|
-
function getComment(node) {
|
|
73
|
-
const text = node.text;
|
|
74
|
-
if (text.startsWith("/*")) return text.slice(2, -2).replace(/^\s*\*?\s*/gm, "").trim();
|
|
75
|
-
return text.replace(/^\/\/\s?/, "").trim();
|
|
76
|
-
}
|
|
77
|
-
const withComment = (query) => ({
|
|
78
|
-
pattern: `(
|
|
79
|
-
((comment) @comment)?
|
|
80
|
-
.
|
|
81
|
-
(_ ${query.pattern})
|
|
82
|
-
)`,
|
|
83
|
-
extract(match) {
|
|
84
|
-
const result = query.extract(match);
|
|
85
|
-
if (!result?.translation) return result;
|
|
86
|
-
const comment = match.captures.find((c) => c.name === "comment")?.node;
|
|
87
|
-
if (!comment) return result;
|
|
88
|
-
if (comment) result.translation.comments = {
|
|
89
|
-
...result.translation.comments,
|
|
90
|
-
extracted: getComment(comment)
|
|
91
|
-
};
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/plugins/core/queries/import.ts
|
|
98
|
-
const importQuery = {
|
|
99
|
-
pattern: `
|
|
100
|
-
[
|
|
101
|
-
(import_statement
|
|
102
|
-
source: (string (string_fragment) @import))
|
|
103
|
-
(export_statement
|
|
104
|
-
source: (string (string_fragment) @import))
|
|
105
|
-
(call_expression
|
|
106
|
-
function: (identifier) @func
|
|
107
|
-
arguments: (arguments (string (string_fragment) @import))
|
|
108
|
-
(#eq? @func "require"))
|
|
109
|
-
(call_expression
|
|
110
|
-
function: (member_expression
|
|
111
|
-
object: (identifier) @obj
|
|
112
|
-
property: (property_identifier) @method)
|
|
113
|
-
arguments: (arguments (string (string_fragment) @import))
|
|
114
|
-
(#eq? @obj "require")
|
|
115
|
-
(#eq? @method "resolve"))
|
|
116
|
-
(call_expression
|
|
117
|
-
function: (import)
|
|
118
|
-
arguments: (arguments (string (string_fragment) @import)))
|
|
119
|
-
]
|
|
120
|
-
`,
|
|
121
|
-
extract(match) {
|
|
122
|
-
return (match.captures.find((c) => c.name === "import")?.node)?.text;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/plugins/core/queries/utils.ts
|
|
128
|
-
const callPattern = (fnName, args, allowMember = true) => `(
|
|
129
|
-
(call_expression
|
|
130
|
-
function: ${allowMember ? `[
|
|
131
|
-
(identifier) @func
|
|
132
|
-
(member_expression property: (property_identifier) @func)
|
|
133
|
-
]` : `(identifier) @func`}
|
|
134
|
-
arguments: ${args}
|
|
135
|
-
) @call
|
|
136
|
-
(#eq? @func "${fnName}")
|
|
137
|
-
)`;
|
|
138
|
-
function isDescendant(node, ancestor) {
|
|
139
|
-
let current = node;
|
|
140
|
-
while (current) {
|
|
141
|
-
if (current.id === ancestor.id) return true;
|
|
142
|
-
current = current.parent;
|
|
143
|
-
}
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/plugins/core/queries/message.ts
|
|
149
|
-
const notInPlural = (query) => ({
|
|
150
|
-
pattern: query.pattern,
|
|
151
|
-
extract(match) {
|
|
152
|
-
const result = query.extract(match);
|
|
153
|
-
if (!result?.node) return result;
|
|
154
|
-
let parent = result.node.parent;
|
|
155
|
-
if (parent && parent.type === "arguments") parent = parent.parent;
|
|
156
|
-
if (parent && parent.type === "call_expression") {
|
|
157
|
-
const fn = parent.childForFieldName("function");
|
|
158
|
-
if (fn) {
|
|
159
|
-
if (fn.type === "identifier" && (fn.text === "plural" || fn.text === "ngettext" || fn.text === "pgettext" || fn.text === "npgettext") || fn.type === "member_expression" && [
|
|
160
|
-
"plural",
|
|
161
|
-
"ngettext",
|
|
162
|
-
"pgettext",
|
|
163
|
-
"npgettext"
|
|
164
|
-
].includes(fn.childForFieldName("property")?.text ?? "")) return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return result;
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
const messageArg = `[
|
|
171
|
-
(string (string_fragment) @msgid)
|
|
172
|
-
(object
|
|
173
|
-
(_)*
|
|
174
|
-
(pair
|
|
175
|
-
key: (property_identifier) @id_key
|
|
176
|
-
value: (string (string_fragment) @id)
|
|
177
|
-
(#eq? @id_key "id")
|
|
178
|
-
)?
|
|
179
|
-
(_)*
|
|
180
|
-
(pair
|
|
181
|
-
key: (property_identifier) @msg_key
|
|
182
|
-
value: (string (string_fragment) @message)
|
|
183
|
-
(#eq? @msg_key "message")
|
|
184
|
-
)?
|
|
185
|
-
(_)*
|
|
186
|
-
)
|
|
187
|
-
(template_string) @tpl
|
|
188
|
-
]`;
|
|
189
|
-
const messageArgs = `[ (arguments ${messageArg}) (template_string) @tpl ]`;
|
|
190
|
-
const extractMessage = (name) => (match) => {
|
|
191
|
-
const node = match.captures.find((c) => c.name === "call")?.node;
|
|
192
|
-
if (!node) return;
|
|
193
|
-
const msgid = match.captures.find((c) => c.name === "msgid")?.node.text;
|
|
194
|
-
if (msgid) return {
|
|
195
|
-
node,
|
|
196
|
-
translation: {
|
|
197
|
-
id: msgid,
|
|
198
|
-
message: [msgid]
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
const tpl = match.captures.find((c) => c.name === "tpl")?.node;
|
|
202
|
-
if (tpl) {
|
|
203
|
-
for (const child of tpl.children) {
|
|
204
|
-
if (child.type !== "template_substitution") continue;
|
|
205
|
-
const expr = child.namedChildren[0];
|
|
206
|
-
if (!expr || expr.type !== "identifier") return {
|
|
207
|
-
node,
|
|
208
|
-
error: `${name}() template expressions must be simple identifiers`
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
const text = tpl.text.slice(1, -1);
|
|
212
|
-
return {
|
|
213
|
-
node,
|
|
214
|
-
translation: {
|
|
215
|
-
id: text,
|
|
216
|
-
message: [text]
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
const id = match.captures.find((c) => c.name === "id")?.node.text;
|
|
221
|
-
const message = match.captures.find((c) => c.name === "message")?.node.text;
|
|
222
|
-
const msgId = id ?? message;
|
|
223
|
-
if (!msgId) return;
|
|
224
|
-
return {
|
|
225
|
-
node,
|
|
226
|
-
translation: {
|
|
227
|
-
id: msgId,
|
|
228
|
-
message: [message ?? id ?? ""]
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
};
|
|
232
|
-
const messageQuery$1 = notInPlural(withComment({
|
|
233
|
-
pattern: callPattern("message", messageArgs),
|
|
234
|
-
extract: extractMessage("message")
|
|
235
|
-
}));
|
|
236
|
-
const allowed$1 = new Set([
|
|
237
|
-
"string",
|
|
238
|
-
"object",
|
|
239
|
-
"template_string"
|
|
240
|
-
]);
|
|
241
|
-
const messageInvalidQuery = notInPlural({
|
|
242
|
-
pattern: callPattern("message", "(arguments (_) @arg)"),
|
|
243
|
-
extract(match) {
|
|
244
|
-
const call = match.captures.find((c) => c.name === "call")?.node;
|
|
245
|
-
const node = match.captures.find((c) => c.name === "arg")?.node;
|
|
246
|
-
if (!call || !node) return;
|
|
247
|
-
if (allowed$1.has(node.type)) return;
|
|
248
|
-
return {
|
|
249
|
-
node,
|
|
250
|
-
error: "message() argument must be a string literal, object literal, or template literal"
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region src/plugins/core/queries/plural-utils.ts
|
|
257
|
-
const extractPluralForms = (name) => (match) => {
|
|
258
|
-
const call = match.captures.find((c) => c.name === "call")?.node;
|
|
259
|
-
const n = match.captures.find((c) => c.name === "n")?.node;
|
|
260
|
-
if (!call || !n || n.nextNamedSibling) return;
|
|
261
|
-
const msgctxt = match.captures.find((c) => c.name === "msgctxt")?.node?.text;
|
|
262
|
-
const msgNodes = match.captures.filter((c) => c.name === "msg").map((c) => c.node);
|
|
263
|
-
const ids = [];
|
|
264
|
-
const strs = [];
|
|
265
|
-
for (const node of msgNodes) {
|
|
266
|
-
const relevant = match.captures.filter((c) => [
|
|
267
|
-
"msgid",
|
|
268
|
-
"id",
|
|
269
|
-
"message",
|
|
270
|
-
"tpl"
|
|
271
|
-
].includes(c.name) && isDescendant(c.node, node));
|
|
272
|
-
const subMatch = {
|
|
273
|
-
pattern: 0,
|
|
274
|
-
captures: [{
|
|
275
|
-
name: "call",
|
|
276
|
-
node
|
|
277
|
-
}, ...relevant]
|
|
278
|
-
};
|
|
279
|
-
const result = extractMessage(name)(subMatch);
|
|
280
|
-
if (!result) continue;
|
|
281
|
-
if (result.error) return {
|
|
282
|
-
node: call,
|
|
283
|
-
error: result.error
|
|
284
|
-
};
|
|
285
|
-
if (result.translation) {
|
|
286
|
-
ids.push(result.translation.id);
|
|
287
|
-
strs.push(result.translation.message[0] ?? "");
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (ids.length === 0) return;
|
|
291
|
-
const translation = {
|
|
292
|
-
id: ids[0],
|
|
293
|
-
plural: ids[1],
|
|
294
|
-
message: strs
|
|
295
|
-
};
|
|
296
|
-
if (msgctxt) translation.context = msgctxt;
|
|
297
|
-
return {
|
|
298
|
-
node: call,
|
|
299
|
-
translation
|
|
300
|
-
};
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
//#endregion
|
|
304
|
-
//#region src/plugins/core/queries/context.ts
|
|
305
|
-
const ctxCall = callPattern("context", `[
|
|
306
|
-
(arguments (string (string_fragment) @msgctxt))
|
|
307
|
-
(template_string) @msgctxt
|
|
308
|
-
]`).replace(/@call/g, "@ctx").replace(/@func/g, "@ctxfn");
|
|
309
|
-
const contextMsgQuery = withComment({
|
|
310
|
-
pattern: `(
|
|
311
|
-
(call_expression
|
|
312
|
-
function: (member_expression
|
|
313
|
-
object: ${ctxCall}
|
|
314
|
-
property: (property_identifier) @func
|
|
315
|
-
)
|
|
316
|
-
arguments: ${messageArgs}
|
|
317
|
-
) @call
|
|
318
|
-
(#eq? @func "message")
|
|
319
|
-
)`,
|
|
320
|
-
extract(match) {
|
|
321
|
-
const result = extractMessage("context.message")(match);
|
|
322
|
-
const contextNode = match.captures.find((c) => c.name === "msgctxt")?.node;
|
|
323
|
-
if (!result || !result.translation || !contextNode) return result;
|
|
324
|
-
if (contextNode.type === "template_string") for (const child of contextNode.children) {
|
|
325
|
-
if (child.type !== "template_substitution") continue;
|
|
326
|
-
const expr = child.namedChildren[0];
|
|
327
|
-
if (!expr || expr.type !== "identifier") return {
|
|
328
|
-
node: contextNode,
|
|
329
|
-
error: "context() template expressions must be simple identifiers"
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
const contextText = contextNode.type === "template_string" ? contextNode.text.slice(1, -1) : contextNode.text;
|
|
333
|
-
return {
|
|
334
|
-
node: result.node,
|
|
335
|
-
translation: {
|
|
336
|
-
...result.translation,
|
|
337
|
-
context: contextText
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
const msgCall$3 = callPattern("message", messageArgs, false).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
|
|
343
|
-
const contextPluralQuery = withComment({
|
|
344
|
-
pattern: `(
|
|
345
|
-
(call_expression
|
|
346
|
-
function: (member_expression
|
|
347
|
-
object: ${ctxCall}
|
|
348
|
-
property: (property_identifier) @func
|
|
349
|
-
)
|
|
350
|
-
arguments: (arguments (
|
|
351
|
-
(${msgCall$3} ("," )?)+
|
|
352
|
-
(number) @n
|
|
353
|
-
))
|
|
354
|
-
) @call
|
|
355
|
-
(#eq? @func "plural")
|
|
356
|
-
)`,
|
|
357
|
-
extract: extractPluralForms("context.plural")
|
|
358
|
-
});
|
|
359
|
-
const contextInvalidQuery = withComment({
|
|
360
|
-
pattern: ctxCall,
|
|
361
|
-
extract(match) {
|
|
362
|
-
const call = match.captures.find((c) => c.name === "ctx")?.node;
|
|
363
|
-
if (!call) return;
|
|
364
|
-
const parent = call.parent;
|
|
365
|
-
if (parent && parent.type === "member_expression" && parent.childForFieldName("object")?.id === call.id) {
|
|
366
|
-
const property = parent.childForFieldName("property")?.text;
|
|
367
|
-
const grandparent = parent.parent;
|
|
368
|
-
if (grandparent && grandparent.type === "call_expression" && grandparent.childForFieldName("function")?.id === parent.id && (property === "message" || property === "plural")) return;
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
node: call,
|
|
372
|
-
error: "context() must be used with message() or plural() in the same expression"
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
//#endregion
|
|
378
|
-
//#region src/plugins/core/queries/gettext.ts
|
|
379
|
-
const gettextQuery = withComment({
|
|
380
|
-
pattern: callPattern("gettext", messageArgs),
|
|
381
|
-
extract: extractMessage("gettext")
|
|
382
|
-
});
|
|
383
|
-
const allowed = new Set([
|
|
384
|
-
"string",
|
|
385
|
-
"object",
|
|
386
|
-
"template_string",
|
|
387
|
-
"identifier",
|
|
388
|
-
"call_expression"
|
|
389
|
-
]);
|
|
390
|
-
const gettextInvalidQuery = {
|
|
391
|
-
pattern: callPattern("gettext", "(arguments (_) @arg)"),
|
|
392
|
-
extract(match) {
|
|
393
|
-
const call = match.captures.find((c) => c.name === "call")?.node;
|
|
394
|
-
const node = match.captures.find((c) => c.name === "arg")?.node;
|
|
395
|
-
if (!call || !node) return;
|
|
396
|
-
if (allowed.has(node.type)) return;
|
|
397
|
-
return {
|
|
398
|
-
node,
|
|
399
|
-
error: "gettext() argument must be a string literal, object literal, or template literal"
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
//#endregion
|
|
405
|
-
//#region src/plugins/core/queries/ngettext.ts
|
|
406
|
-
const msgCall$2 = callPattern("message", messageArgs).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
|
|
407
|
-
const plainMsg$1 = `(${messageArg}) @msg`;
|
|
408
|
-
const msgArg$1 = `[${msgCall$2} ${plainMsg$1}]`;
|
|
409
|
-
const ngettextQuery = withComment({
|
|
410
|
-
pattern: callPattern("ngettext", `(arguments ${msgArg$1} "," ${msgArg$1} ("," ${msgArg$1})* "," (_) @n)`),
|
|
411
|
-
extract: extractPluralForms("ngettext")
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
//#endregion
|
|
415
|
-
//#region src/plugins/core/queries/npgettext.ts
|
|
416
|
-
const msgCall$1 = callPattern("message", messageArgs).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
|
|
417
|
-
const plainMsg = `(${messageArg}) @msg`;
|
|
418
|
-
const msgArg = `[${msgCall$1} ${plainMsg}]`;
|
|
419
|
-
const npgettextQuery = withComment({
|
|
420
|
-
pattern: callPattern("npgettext", `(arguments (string (string_fragment) @msgctxt) "," ${msgArg} "," ${msgArg} ("," ${msgArg})* "," (_) @n)`),
|
|
421
|
-
extract: extractPluralForms("npgettext")
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
//#endregion
|
|
425
|
-
//#region src/plugins/core/queries/pgettext.ts
|
|
426
|
-
const pgettextQuery = withComment({
|
|
427
|
-
pattern: callPattern("pgettext", `(arguments (string (string_fragment) @msgctxt) "," ${messageArg})`),
|
|
428
|
-
extract(match) {
|
|
429
|
-
const result = extractMessage("pgettext")(match);
|
|
430
|
-
const contextNode = match.captures.find((c) => c.name === "msgctxt")?.node;
|
|
431
|
-
if (!result || !contextNode || !result.translation) return result;
|
|
432
|
-
return {
|
|
433
|
-
node: result.node,
|
|
434
|
-
translation: {
|
|
435
|
-
...result.translation,
|
|
436
|
-
context: contextNode.text
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
//#endregion
|
|
443
|
-
//#region src/plugins/core/queries/plural.ts
|
|
444
|
-
const msgCall = callPattern("message", messageArgs, false).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
|
|
445
|
-
const pluralQuery$1 = withComment({
|
|
446
|
-
pattern: callPattern("plural", `(arguments (
|
|
447
|
-
(${msgCall} ("," )?)+
|
|
448
|
-
(number) @n
|
|
449
|
-
))`, false),
|
|
450
|
-
extract: extractPluralForms("plural")
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
//#endregion
|
|
454
|
-
//#region src/plugins/core/queries/index.ts
|
|
455
|
-
const queries$1 = [
|
|
456
|
-
contextMsgQuery,
|
|
457
|
-
contextPluralQuery,
|
|
458
|
-
contextInvalidQuery,
|
|
459
|
-
messageQuery$1,
|
|
460
|
-
messageInvalidQuery,
|
|
461
|
-
gettextQuery,
|
|
462
|
-
gettextInvalidQuery,
|
|
463
|
-
pluralQuery$1,
|
|
464
|
-
ngettextQuery,
|
|
465
|
-
pgettextQuery,
|
|
466
|
-
npgettextQuery
|
|
467
|
-
];
|
|
468
|
-
|
|
469
|
-
//#endregion
|
|
470
|
-
//#region src/plugins/core/parse.ts
|
|
471
|
-
function getLanguage(ext) {
|
|
472
|
-
switch (ext) {
|
|
473
|
-
case ".ts": return TS.typescript;
|
|
474
|
-
case ".tsx": return TS.tsx;
|
|
475
|
-
default: return JavaScript;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
const parserCache = /* @__PURE__ */ new Map();
|
|
479
|
-
const queryCache = /* @__PURE__ */ new WeakMap();
|
|
480
|
-
function getCachedParser(ext) {
|
|
481
|
-
let cached = parserCache.get(ext);
|
|
482
|
-
if (!cached) {
|
|
483
|
-
const parser = new Parser();
|
|
484
|
-
const language = getLanguage(ext);
|
|
485
|
-
parser.setLanguage(language);
|
|
486
|
-
cached = {
|
|
487
|
-
parser,
|
|
488
|
-
language
|
|
489
|
-
};
|
|
490
|
-
parserCache.set(ext, cached);
|
|
491
|
-
}
|
|
492
|
-
return cached;
|
|
493
|
-
}
|
|
494
|
-
function getCachedQuery(language, pattern) {
|
|
495
|
-
let cache = queryCache.get(language);
|
|
496
|
-
if (!cache) {
|
|
497
|
-
cache = /* @__PURE__ */ new Map();
|
|
498
|
-
queryCache.set(language, cache);
|
|
499
|
-
}
|
|
500
|
-
let query = cache.get(pattern);
|
|
501
|
-
if (!query) {
|
|
502
|
-
query = new Parser.Query(language, pattern);
|
|
503
|
-
cache.set(pattern, query);
|
|
504
|
-
}
|
|
505
|
-
return query;
|
|
506
|
-
}
|
|
507
|
-
function getParser(path$1) {
|
|
508
|
-
const ext = extname(path$1);
|
|
509
|
-
return getCachedParser(ext);
|
|
510
|
-
}
|
|
511
|
-
function getQuery(language, pattern) {
|
|
512
|
-
return getCachedQuery(language, pattern);
|
|
513
|
-
}
|
|
514
|
-
function parseSource$1(source, path$1) {
|
|
515
|
-
const context = { path: path$1 };
|
|
516
|
-
const { parser, language } = getParser(path$1);
|
|
517
|
-
const tree = parser.parse(source);
|
|
518
|
-
const translations = [];
|
|
519
|
-
const warnings = [];
|
|
520
|
-
const imports = [];
|
|
521
|
-
const seen = /* @__PURE__ */ new Set();
|
|
522
|
-
for (const spec of queries$1) {
|
|
523
|
-
const query = getCachedQuery(language, spec.pattern);
|
|
524
|
-
for (const match of query.matches(tree.rootNode)) {
|
|
525
|
-
const message = spec.extract(match);
|
|
526
|
-
if (!message) continue;
|
|
527
|
-
const { node, translation, error } = message;
|
|
528
|
-
if (seen.has(node.id)) continue;
|
|
529
|
-
seen.add(node.id);
|
|
530
|
-
const reference = getReference(node, context);
|
|
531
|
-
if (translation) translations.push({
|
|
532
|
-
...translation,
|
|
533
|
-
comments: {
|
|
534
|
-
...translation.comments,
|
|
535
|
-
reference
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
if (error) warnings.push({
|
|
539
|
-
error,
|
|
540
|
-
reference
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
const importTreeQuery = getCachedQuery(language, importQuery.pattern);
|
|
545
|
-
for (const match of importTreeQuery.matches(tree.rootNode)) {
|
|
546
|
-
const imp = importQuery.extract(match);
|
|
547
|
-
if (imp) imports.push(imp);
|
|
548
|
-
}
|
|
549
|
-
return {
|
|
550
|
-
translations,
|
|
551
|
-
imports,
|
|
552
|
-
warnings
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
//#endregion
|
|
557
|
-
//#region src/plugins/core/resolve.ts
|
|
558
|
-
function findTsconfig(dir) {
|
|
559
|
-
let current = dir;
|
|
560
|
-
while (true) {
|
|
561
|
-
const config = path.join(current, "tsconfig.json");
|
|
562
|
-
if (fs$1.existsSync(config)) return config;
|
|
563
|
-
const parent = path.dirname(current);
|
|
564
|
-
if (parent === current) return;
|
|
565
|
-
current = parent;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
const resolverCache = /* @__PURE__ */ new Map();
|
|
569
|
-
function getResolver(dir) {
|
|
570
|
-
const tsconfig = findTsconfig(dir);
|
|
571
|
-
const key = tsconfig ?? "__default__";
|
|
572
|
-
let resolver = resolverCache.get(key);
|
|
573
|
-
if (!resolver) {
|
|
574
|
-
resolver = new ResolverFactory({
|
|
575
|
-
extensions: [
|
|
576
|
-
".ts",
|
|
577
|
-
".tsx",
|
|
578
|
-
".js",
|
|
579
|
-
".jsx",
|
|
580
|
-
".mjs",
|
|
581
|
-
".cjs",
|
|
582
|
-
".json"
|
|
583
|
-
],
|
|
584
|
-
conditionNames: [
|
|
585
|
-
"import",
|
|
586
|
-
"require",
|
|
587
|
-
"node"
|
|
588
|
-
],
|
|
589
|
-
...tsconfig ? { tsconfig: { configFile: tsconfig } } : {}
|
|
590
|
-
});
|
|
591
|
-
resolverCache.set(key, resolver);
|
|
592
|
-
}
|
|
593
|
-
return resolver;
|
|
594
|
-
}
|
|
595
|
-
function resolveImports(file, imports) {
|
|
596
|
-
const dir = path.dirname(path.resolve(file));
|
|
597
|
-
const resolver = getResolver(dir);
|
|
598
|
-
const resolved = [];
|
|
599
|
-
for (const spec of imports) {
|
|
600
|
-
const res = resolver.sync(dir, spec);
|
|
601
|
-
if (res.path) resolved.push(res.path);
|
|
602
|
-
}
|
|
603
|
-
return resolved;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
//#endregion
|
|
607
|
-
//#region src/plugins/core/core.ts
|
|
608
|
-
const filter$1 = /\.([cm]?tsx?|jsx?)$/;
|
|
609
|
-
const namespace$1 = "source";
|
|
610
|
-
function core() {
|
|
611
|
-
return {
|
|
612
|
-
name: "core",
|
|
613
|
-
setup(build) {
|
|
614
|
-
build.context.logger?.debug("core plugin initialized");
|
|
615
|
-
build.onResolve({
|
|
616
|
-
filter: filter$1,
|
|
617
|
-
namespace: namespace$1
|
|
618
|
-
}, ({ entrypoint, path: path$1 }) => {
|
|
619
|
-
return {
|
|
620
|
-
entrypoint,
|
|
621
|
-
namespace: namespace$1,
|
|
622
|
-
path: resolve(path$1)
|
|
623
|
-
};
|
|
624
|
-
});
|
|
625
|
-
build.onLoad({
|
|
626
|
-
filter: filter$1,
|
|
627
|
-
namespace: namespace$1
|
|
628
|
-
}, async ({ entrypoint, path: path$1 }) => {
|
|
629
|
-
const data = await readFile(path$1, "utf8");
|
|
630
|
-
return {
|
|
631
|
-
entrypoint,
|
|
632
|
-
path: path$1,
|
|
633
|
-
namespace: namespace$1,
|
|
634
|
-
data
|
|
635
|
-
};
|
|
636
|
-
});
|
|
637
|
-
build.onProcess({
|
|
638
|
-
filter: filter$1,
|
|
639
|
-
namespace: namespace$1
|
|
640
|
-
}, ({ entrypoint, path: path$1, data }) => {
|
|
641
|
-
const { translations, imports, warnings } = parseSource$1(data, path$1);
|
|
642
|
-
if (build.context.config.walk) {
|
|
643
|
-
const paths = resolveImports(path$1, imports);
|
|
644
|
-
for (const path$2 of paths) build.resolve({
|
|
645
|
-
entrypoint,
|
|
646
|
-
path: path$2,
|
|
647
|
-
namespace: namespace$1
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
|
|
651
|
-
build.resolve({
|
|
652
|
-
entrypoint,
|
|
653
|
-
path: path$1,
|
|
654
|
-
namespace: "translate",
|
|
655
|
-
data: translations
|
|
656
|
-
});
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
//#endregion
|
|
663
|
-
//#region src/plugins/po/collect.ts
|
|
664
|
-
function collect(source, locale) {
|
|
665
|
-
const translations = { "": {} };
|
|
666
|
-
const nplurals = locale ? getNPlurals(locale) : void 0;
|
|
667
|
-
for (const { context, id, message, comments, obsolete, plural } of source) {
|
|
668
|
-
const ctx = context || "";
|
|
669
|
-
if (!translations[ctx]) translations[ctx] = {};
|
|
670
|
-
const length = plural ? nplurals ?? message.length : 1;
|
|
671
|
-
const existing = translations[ctx][id];
|
|
672
|
-
const refs = /* @__PURE__ */ new Set();
|
|
673
|
-
if (existing?.comments?.reference) existing.comments.reference.split(/\r?\n|\r/).forEach((r) => {
|
|
674
|
-
refs.add(r);
|
|
675
|
-
});
|
|
676
|
-
if (comments?.reference) comments.reference.split(/\r?\n|\r/).forEach((r) => {
|
|
677
|
-
refs.add(r);
|
|
678
|
-
});
|
|
679
|
-
const msgstr = existing?.msgstr ? existing.msgstr.slice(0, length) : Array.from({ length }, () => "");
|
|
680
|
-
while (msgstr.length < length) msgstr.push("");
|
|
681
|
-
translations[ctx][id] = {
|
|
682
|
-
msgctxt: context || void 0,
|
|
683
|
-
msgid: id,
|
|
684
|
-
msgid_plural: plural,
|
|
685
|
-
msgstr,
|
|
686
|
-
comments: {
|
|
687
|
-
...existing?.comments,
|
|
688
|
-
...comments,
|
|
689
|
-
reference: refs.size ? Array.from(refs).join("\n") : void 0
|
|
690
|
-
},
|
|
691
|
-
obsolete: existing?.obsolete ?? obsolete
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
return translations;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
//#endregion
|
|
698
|
-
//#region src/plugins/po/hasChanges.ts
|
|
699
|
-
const IGNORED_HEADER_KEYS = new Set(["pot-creation-date", "po-revision-date"]);
|
|
700
|
-
const IGNORED_HEADER_LINE_PREFIXES = ["pot-creation-date:", "po-revision-date:"];
|
|
701
|
-
function normalizeHeaderString(value) {
|
|
702
|
-
const lines = value.split("\n");
|
|
703
|
-
const hadTrailingNewline = value.endsWith("\n");
|
|
704
|
-
const filtered = lines.filter((line) => {
|
|
705
|
-
const trimmed = line.trimStart().toLowerCase();
|
|
706
|
-
return !IGNORED_HEADER_LINE_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
|
|
707
|
-
}).map((line) => {
|
|
708
|
-
const separatorIndex = line.indexOf(":");
|
|
709
|
-
if (separatorIndex === -1) return line;
|
|
710
|
-
const key = line.slice(0, separatorIndex).trim().toLowerCase();
|
|
711
|
-
const value$1 = line.slice(separatorIndex + 1);
|
|
712
|
-
return `${key}:${value$1}`;
|
|
713
|
-
});
|
|
714
|
-
if (hadTrailingNewline && filtered[filtered.length - 1] !== "") filtered.push("");
|
|
715
|
-
return filtered.join("\n");
|
|
716
|
-
}
|
|
717
|
-
function normalize(translations) {
|
|
718
|
-
const compiled = gettextParser.po.compile(translations);
|
|
719
|
-
const parsed = gettextParser.po.parse(compiled);
|
|
720
|
-
if (parsed.headers) {
|
|
721
|
-
const normalizedHeaders = {};
|
|
722
|
-
for (const [key, value] of Object.entries(parsed.headers)) if (!IGNORED_HEADER_KEYS.has(key.toLowerCase())) normalizedHeaders[key.toLowerCase()] = value;
|
|
723
|
-
parsed.headers = normalizedHeaders;
|
|
724
|
-
}
|
|
725
|
-
const headerMessage = parsed.translations?.[""]?.[""];
|
|
726
|
-
if (headerMessage?.msgstr) headerMessage.msgstr = headerMessage.msgstr.map((item) => normalizeHeaderString(item));
|
|
727
|
-
return parsed;
|
|
728
|
-
}
|
|
729
|
-
function hasChanges(left, right) {
|
|
730
|
-
if (!right) return true;
|
|
731
|
-
const normalizedLeft = normalize(left);
|
|
732
|
-
const normalizedRight = normalize(right);
|
|
733
|
-
return !isDeepStrictEqual(normalizedLeft, normalizedRight);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
//#endregion
|
|
737
|
-
//#region src/plugins/po/merge.ts
|
|
738
|
-
function formatDate(date) {
|
|
739
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
740
|
-
const year = date.getFullYear();
|
|
741
|
-
const month = pad(date.getMonth() + 1);
|
|
742
|
-
const day = pad(date.getDate());
|
|
743
|
-
const hours = pad(date.getHours());
|
|
744
|
-
const minutes = pad(date.getMinutes());
|
|
745
|
-
const tzo = -date.getTimezoneOffset();
|
|
746
|
-
const sign = tzo >= 0 ? "+" : "-";
|
|
747
|
-
const offsetHours = pad(Math.floor(Math.abs(tzo) / 60));
|
|
748
|
-
const offsetMinutes = pad(Math.abs(tzo) % 60);
|
|
749
|
-
return `${year}-${month}-${day} ${hours}:${minutes}${sign}${offsetHours}${offsetMinutes}`;
|
|
750
|
-
}
|
|
751
|
-
function merge(sources, existing, obsolete, locale, generatedAt) {
|
|
752
|
-
let headers = {};
|
|
753
|
-
let translations = { "": {} };
|
|
754
|
-
let obsoleteTranslations = {};
|
|
755
|
-
const nplurals = getNPlurals(locale);
|
|
756
|
-
if (existing) {
|
|
757
|
-
headers = existing.headers ? structuredClone(existing.headers) : {};
|
|
758
|
-
translations = existing.translations ? structuredClone(existing.translations) : { "": {} };
|
|
759
|
-
obsoleteTranslations = existing.obsolete ? structuredClone(existing.obsolete) : {};
|
|
760
|
-
for (const ctx of Object.keys(translations)) for (const id of Object.keys(translations[ctx])) {
|
|
761
|
-
if (ctx === "" && id === "") continue;
|
|
762
|
-
translations[ctx][id].obsolete = true;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
const collected = { "": {} };
|
|
766
|
-
for (const { translations: translations$1 } of sources) for (const [ctx, msgs] of Object.entries(translations$1)) {
|
|
767
|
-
if (!collected[ctx]) collected[ctx] = {};
|
|
768
|
-
for (const [id, entry] of Object.entries(msgs)) {
|
|
769
|
-
const existing$1 = collected[ctx][id];
|
|
770
|
-
const refs = /* @__PURE__ */ new Set();
|
|
771
|
-
if (existing$1?.comments?.reference) existing$1.comments.reference.split(/\r?\n|\r/).forEach((r) => {
|
|
772
|
-
refs.add(r);
|
|
773
|
-
});
|
|
774
|
-
if (entry.comments?.reference) entry.comments.reference.split(/\r?\n|\r/).forEach((r) => {
|
|
775
|
-
refs.add(r);
|
|
776
|
-
});
|
|
777
|
-
collected[ctx][id] = {
|
|
778
|
-
...existing$1,
|
|
779
|
-
...entry,
|
|
780
|
-
comments: {
|
|
781
|
-
...existing$1?.comments,
|
|
782
|
-
...entry.comments,
|
|
783
|
-
reference: refs.size ? Array.from(refs).join("\n") : void 0
|
|
784
|
-
}
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
for (const [ctx, msgs] of Object.entries(collected)) {
|
|
789
|
-
if (!translations[ctx]) translations[ctx] = {};
|
|
790
|
-
for (const [id, entry] of Object.entries(msgs)) {
|
|
791
|
-
const existingEntry = translations[ctx][id] ?? obsoleteTranslations[ctx]?.[id];
|
|
792
|
-
if (existingEntry) {
|
|
793
|
-
entry.msgstr = existingEntry.msgstr;
|
|
794
|
-
entry.comments = {
|
|
795
|
-
...entry.comments,
|
|
796
|
-
translator: existingEntry.comments?.translator
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
delete entry.obsolete;
|
|
800
|
-
entry.msgstr = entry.msgstr.slice(0, nplurals);
|
|
801
|
-
while (entry.msgstr.length < nplurals) entry.msgstr.push("");
|
|
802
|
-
translations[ctx][id] = entry;
|
|
803
|
-
if (obsoleteTranslations[ctx]) delete obsoleteTranslations[ctx][id];
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
for (const ctx of Object.keys(translations)) for (const id of Object.keys(translations[ctx])) {
|
|
807
|
-
if (ctx === "" && id === "") continue;
|
|
808
|
-
const entry = translations[ctx][id];
|
|
809
|
-
if (entry.obsolete) {
|
|
810
|
-
if (!obsoleteTranslations[ctx]) obsoleteTranslations[ctx] = {};
|
|
811
|
-
obsoleteTranslations[ctx][id] = entry;
|
|
812
|
-
delete translations[ctx][id];
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
headers = {
|
|
816
|
-
...headers,
|
|
817
|
-
"Content-Type": headers["Content-Type"] || "text/plain; charset=UTF-8",
|
|
818
|
-
"Plural-Forms": `nplurals=${nplurals}; plural=${getFormula(locale)};`,
|
|
819
|
-
language: locale,
|
|
820
|
-
"MIME-Version": "1.0",
|
|
821
|
-
"Content-Transfer-Encoding": "8bit",
|
|
822
|
-
"POT-Creation-Date": formatDate(generatedAt),
|
|
823
|
-
"x-generator": "@let-value/translate-extract"
|
|
824
|
-
};
|
|
825
|
-
return {
|
|
826
|
-
charset: "utf-8",
|
|
827
|
-
headers,
|
|
828
|
-
translations,
|
|
829
|
-
...obsolete === "mark" && Object.keys(obsoleteTranslations).length ? { obsolete: obsoleteTranslations } : {}
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
//#endregion
|
|
834
|
-
//#region src/plugins/po/po.ts
|
|
835
|
-
const namespace = "translate";
|
|
836
|
-
function po() {
|
|
837
|
-
return {
|
|
838
|
-
name: "po",
|
|
839
|
-
setup(build) {
|
|
840
|
-
build.context.logger?.debug("po plugin initialized");
|
|
841
|
-
const collections = /* @__PURE__ */ new Map();
|
|
842
|
-
let dispatched = false;
|
|
843
|
-
build.onResolve({
|
|
844
|
-
filter: /.*/,
|
|
845
|
-
namespace
|
|
846
|
-
}, async ({ entrypoint, path: path$1, data }) => {
|
|
847
|
-
if (!data || !Array.isArray(data)) return;
|
|
848
|
-
for (const locale of build.context.config.locales) {
|
|
849
|
-
const destination = build.context.config.destination({
|
|
850
|
-
entrypoint,
|
|
851
|
-
locale,
|
|
852
|
-
path: path$1
|
|
853
|
-
});
|
|
854
|
-
if (!collections.has(destination)) collections.set(destination, {
|
|
855
|
-
locale,
|
|
856
|
-
translations: []
|
|
857
|
-
});
|
|
858
|
-
collections.get(destination)?.translations.push(...data);
|
|
859
|
-
}
|
|
860
|
-
Promise.all([build.defer("source"), build.defer(namespace)]).then(() => {
|
|
861
|
-
if (dispatched) return;
|
|
862
|
-
dispatched = true;
|
|
863
|
-
for (const path$2 of collections.keys()) build.load({
|
|
864
|
-
entrypoint,
|
|
865
|
-
path: path$2,
|
|
866
|
-
namespace
|
|
867
|
-
});
|
|
868
|
-
});
|
|
869
|
-
});
|
|
870
|
-
build.onLoad({
|
|
871
|
-
filter: /.*\.po$/,
|
|
872
|
-
namespace
|
|
873
|
-
}, async ({ entrypoint, path: path$1 }) => {
|
|
874
|
-
const contents = await fs.readFile(path$1).catch(() => void 0);
|
|
875
|
-
const data = contents ? gettextParser.po.parse(contents) : void 0;
|
|
876
|
-
return {
|
|
877
|
-
entrypoint,
|
|
878
|
-
path: path$1,
|
|
879
|
-
namespace,
|
|
880
|
-
data
|
|
881
|
-
};
|
|
882
|
-
});
|
|
883
|
-
build.onProcess({
|
|
884
|
-
filter: /.*\.po$/,
|
|
885
|
-
namespace
|
|
886
|
-
}, async ({ entrypoint, path: path$1, data }) => {
|
|
887
|
-
const collected = collections.get(path$1);
|
|
888
|
-
if (!collected) {
|
|
889
|
-
build.context.logger?.warn({ path: path$1 }, "no translations collected for this path");
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
const { locale, translations } = collected;
|
|
893
|
-
const record = collect(translations, locale);
|
|
894
|
-
const out = merge([{ translations: record }], data, build.context.config.obsolete, locale, build.context.generatedAt);
|
|
895
|
-
if (hasChanges(out, data)) {
|
|
896
|
-
await fs.mkdir(dirname(path$1), { recursive: true });
|
|
897
|
-
await fs.writeFile(path$1, gettextParser.po.compile(out));
|
|
898
|
-
}
|
|
899
|
-
build.resolve({
|
|
900
|
-
entrypoint,
|
|
901
|
-
path: path$1,
|
|
902
|
-
namespace: "cleanup",
|
|
903
|
-
data: translations
|
|
904
|
-
});
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
//#endregion
|
|
911
|
-
//#region src/configuration.ts
|
|
912
|
-
const defaultPlugins = {
|
|
913
|
-
core,
|
|
914
|
-
po,
|
|
915
|
-
cleanup
|
|
916
|
-
};
|
|
917
|
-
const defaultDestination = ({ entrypoint, locale }) => join(dirname(entrypoint), "translations", `${basename(entrypoint, extname(entrypoint))}.${locale}.po`);
|
|
918
|
-
const defaultExclude = [
|
|
919
|
-
/(?:^|[\\/])node_modules(?:[\\/]|$)/,
|
|
920
|
-
/(?:^|[\\/])dist(?:[\\/]|$)/,
|
|
921
|
-
/(?:^|[\\/])build(?:[\\/]|$)/
|
|
922
|
-
];
|
|
923
|
-
function normalizeExclude(exclude) {
|
|
924
|
-
if (!exclude) return [];
|
|
925
|
-
return Array.isArray(exclude) ? exclude : [exclude];
|
|
926
|
-
}
|
|
927
|
-
function resolveEntrypoint(ep) {
|
|
928
|
-
if (typeof ep === "string") return { entrypoint: ep };
|
|
929
|
-
const { entrypoint, destination, obsolete, walk, exclude } = ep;
|
|
930
|
-
return {
|
|
931
|
-
entrypoint,
|
|
932
|
-
destination,
|
|
933
|
-
obsolete,
|
|
934
|
-
walk,
|
|
935
|
-
exclude: exclude ? normalizeExclude(exclude) : void 0
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
function resolvePlugins(user) {
|
|
939
|
-
if (typeof user === "function") return user(defaultPlugins);
|
|
940
|
-
if (Array.isArray(user)) return [...Object.values(defaultPlugins).map((plugin) => plugin()), ...user];
|
|
941
|
-
return Object.values(defaultPlugins).map((plugin) => plugin());
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Type helper to make it easier to use translate.config.ts
|
|
945
|
-
* @param config - {@link UserConfig}.
|
|
946
|
-
*/
|
|
947
|
-
function defineConfig(config) {
|
|
948
|
-
const defaultLocale = config.defaultLocale ?? "en";
|
|
949
|
-
const plugins = resolvePlugins(config.plugins);
|
|
950
|
-
const entrypoints = (Array.isArray(config.entrypoints) ? config.entrypoints : [config.entrypoints]).map(resolveEntrypoint);
|
|
951
|
-
return {
|
|
952
|
-
plugins,
|
|
953
|
-
entrypoints,
|
|
954
|
-
defaultLocale,
|
|
955
|
-
locales: config.locales ?? [defaultLocale],
|
|
956
|
-
destination: config.destination ?? defaultDestination,
|
|
957
|
-
obsolete: config.obsolete ?? "mark",
|
|
958
|
-
walk: config.walk ?? true,
|
|
959
|
-
logLevel: config.logLevel ?? "info",
|
|
960
|
-
exclude: config.exclude ? normalizeExclude(config.exclude) : defaultExclude
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
//#endregion
|
|
965
|
-
//#region src/plugins/react/queries/utils.ts
|
|
966
|
-
function buildTemplate(node) {
|
|
967
|
-
const source = node.tree.rootNode.text;
|
|
968
|
-
const open = node.childForFieldName("open_tag");
|
|
969
|
-
const close = node.childForFieldName("close_tag");
|
|
970
|
-
const contentStart = open?.endIndex ?? node.startIndex;
|
|
971
|
-
const contentEnd = close?.startIndex ?? node.endIndex;
|
|
972
|
-
const parts = [];
|
|
973
|
-
let segmentStart = contentStart;
|
|
974
|
-
const pushRawText = (endIndex) => {
|
|
975
|
-
if (endIndex <= segmentStart) {
|
|
976
|
-
segmentStart = Math.max(segmentStart, endIndex);
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
const text$1 = source.slice(segmentStart, endIndex);
|
|
980
|
-
if (text$1) parts.push({
|
|
981
|
-
kind: "text",
|
|
982
|
-
text: text$1,
|
|
983
|
-
raw: true
|
|
984
|
-
});
|
|
985
|
-
segmentStart = endIndex;
|
|
986
|
-
};
|
|
987
|
-
const children = node.namedChildren.slice(1, -1);
|
|
988
|
-
for (const child of children) if (child.type === "jsx_expression") {
|
|
989
|
-
pushRawText(child.startIndex);
|
|
990
|
-
const expr = child.namedChildren[0];
|
|
991
|
-
if (!expr) return {
|
|
992
|
-
text: "",
|
|
993
|
-
error: "Empty JSX expression"
|
|
994
|
-
};
|
|
995
|
-
if (expr.type === "identifier") parts.push({
|
|
996
|
-
kind: "expr",
|
|
997
|
-
value: expr.text
|
|
998
|
-
});
|
|
999
|
-
else if (expr.type === "string") parts.push({
|
|
1000
|
-
kind: "text",
|
|
1001
|
-
text: expr.text.slice(1, -1),
|
|
1002
|
-
raw: false
|
|
1003
|
-
});
|
|
1004
|
-
else if (expr.type === "template_string") {
|
|
1005
|
-
if (expr.children.some((c) => c.type === "template_substitution")) return {
|
|
1006
|
-
text: "",
|
|
1007
|
-
error: "JSX expressions with template substitutions are not supported"
|
|
1008
|
-
};
|
|
1009
|
-
parts.push({
|
|
1010
|
-
kind: "text",
|
|
1011
|
-
text: expr.text.slice(1, -1),
|
|
1012
|
-
raw: false
|
|
1013
|
-
});
|
|
1014
|
-
} else return {
|
|
1015
|
-
text: "",
|
|
1016
|
-
error: "JSX expressions must be simple identifiers, strings, or template literals"
|
|
1017
|
-
};
|
|
1018
|
-
segmentStart = child.endIndex;
|
|
1019
|
-
} else if (child.type === "string") {
|
|
1020
|
-
pushRawText(child.startIndex);
|
|
1021
|
-
parts.push({
|
|
1022
|
-
kind: "text",
|
|
1023
|
-
text: child.text.slice(1, -1),
|
|
1024
|
-
raw: false
|
|
1025
|
-
});
|
|
1026
|
-
segmentStart = child.endIndex;
|
|
1027
|
-
} else if (child.type === "jsx_text" || child.type === "html_character_reference" || child.isError) {} else return {
|
|
1028
|
-
text: "",
|
|
1029
|
-
error: "Unsupported JSX child"
|
|
1030
|
-
};
|
|
1031
|
-
pushRawText(contentEnd);
|
|
1032
|
-
const firstRawIndex = parts.findIndex((part) => part.kind === "text" && part.raw);
|
|
1033
|
-
if (firstRawIndex === 0) {
|
|
1034
|
-
const part = parts[firstRawIndex];
|
|
1035
|
-
part.text = part.text.replace(/^\s+/, "");
|
|
1036
|
-
}
|
|
1037
|
-
let lastRawIndex = -1;
|
|
1038
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1039
|
-
const part = parts[i];
|
|
1040
|
-
if (part.kind === "text" && part.raw) {
|
|
1041
|
-
lastRawIndex = i;
|
|
1042
|
-
break;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
if (lastRawIndex !== -1 && lastRawIndex === parts.length - 1) {
|
|
1046
|
-
const part = parts[lastRawIndex];
|
|
1047
|
-
part.text = part.text.replace(/\s+$/, "");
|
|
1048
|
-
}
|
|
1049
|
-
const strings = [""];
|
|
1050
|
-
const values = [];
|
|
1051
|
-
for (const part of parts) if (part.kind === "text") {
|
|
1052
|
-
if (part.text) strings[strings.length - 1] += part.text;
|
|
1053
|
-
} else {
|
|
1054
|
-
values.push(part.value);
|
|
1055
|
-
strings.push("");
|
|
1056
|
-
}
|
|
1057
|
-
let text = "";
|
|
1058
|
-
for (let i = 0; i < strings.length; i++) {
|
|
1059
|
-
text += strings[i];
|
|
1060
|
-
if (values[i]) text += `\${${values[i]}}`;
|
|
1061
|
-
}
|
|
1062
|
-
return { text };
|
|
1063
|
-
}
|
|
1064
|
-
function buildAttrValue(node) {
|
|
1065
|
-
if (node.type === "string") return { text: node.text.slice(1, -1) };
|
|
1066
|
-
if (node.type === "jsx_expression") {
|
|
1067
|
-
const expr = node.namedChildren[0];
|
|
1068
|
-
if (!expr) return {
|
|
1069
|
-
text: "",
|
|
1070
|
-
error: "Empty JSX expression"
|
|
1071
|
-
};
|
|
1072
|
-
if (expr.type === "identifier") return { text: `\${${expr.text}}` };
|
|
1073
|
-
else if (expr.type === "string") return { text: expr.text.slice(1, -1) };
|
|
1074
|
-
else if (expr.type === "template_string") {
|
|
1075
|
-
if (expr.children.some((c) => c.type === "template_substitution")) return {
|
|
1076
|
-
text: "",
|
|
1077
|
-
error: "JSX expressions with template substitutions are not supported"
|
|
1078
|
-
};
|
|
1079
|
-
return { text: expr.text.slice(1, -1) };
|
|
1080
|
-
} else return {
|
|
1081
|
-
text: "",
|
|
1082
|
-
error: "JSX expressions must be simple identifiers, strings, or template literals"
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
return {
|
|
1086
|
-
text: "",
|
|
1087
|
-
error: "Unsupported JSX child"
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
//#endregion
|
|
1092
|
-
//#region src/plugins/react/queries/message.ts
|
|
1093
|
-
const messageQuery = withComment({
|
|
1094
|
-
pattern: `(
|
|
1095
|
-
[
|
|
1096
|
-
(jsx_element (jsx_opening_element name: (identifier) @name)) @call
|
|
1097
|
-
(jsx_self_closing_element name: (identifier) @name) @call
|
|
1098
|
-
(lexical_declaration
|
|
1099
|
-
(variable_declarator
|
|
1100
|
-
value: [
|
|
1101
|
-
(jsx_element (jsx_opening_element name: (identifier) @name)) @call
|
|
1102
|
-
(jsx_self_closing_element name: (identifier) @name) @call
|
|
1103
|
-
]
|
|
1104
|
-
)
|
|
1105
|
-
)
|
|
1106
|
-
]
|
|
1107
|
-
(#eq? @name "Message")
|
|
1108
|
-
)`,
|
|
1109
|
-
extract(match) {
|
|
1110
|
-
const node = match.captures.find((c) => c.name === "call")?.node;
|
|
1111
|
-
if (!node) return void 0;
|
|
1112
|
-
let attrs = [];
|
|
1113
|
-
if (node.type === "jsx_element") {
|
|
1114
|
-
const open = node.childForFieldName("open_tag");
|
|
1115
|
-
if (open) attrs = open.namedChildren;
|
|
1116
|
-
} else if (node.type === "jsx_self_closing_element") attrs = node.namedChildren.slice(1);
|
|
1117
|
-
let msgctxt;
|
|
1118
|
-
let childValue;
|
|
1119
|
-
for (const child of attrs) {
|
|
1120
|
-
if (child.type !== "jsx_attribute") continue;
|
|
1121
|
-
const name = child.child(0);
|
|
1122
|
-
const value = child.child(child.childCount - 1);
|
|
1123
|
-
if (name?.text === "context" && value?.type === "string") msgctxt = value.text.slice(1, -1);
|
|
1124
|
-
else if (name?.text === "children" && value) childValue = value;
|
|
1125
|
-
}
|
|
1126
|
-
let text = "";
|
|
1127
|
-
let error;
|
|
1128
|
-
if (node.type === "jsx_element") ({text, error} = buildTemplate(node));
|
|
1129
|
-
else if (childValue) ({text, error} = buildAttrValue(childValue));
|
|
1130
|
-
if (error) return {
|
|
1131
|
-
node,
|
|
1132
|
-
error
|
|
1133
|
-
};
|
|
1134
|
-
if (!text) return void 0;
|
|
1135
|
-
const translation = {
|
|
1136
|
-
id: text,
|
|
1137
|
-
message: [text]
|
|
1138
|
-
};
|
|
1139
|
-
if (msgctxt) translation.context = msgctxt;
|
|
1140
|
-
return {
|
|
1141
|
-
node,
|
|
1142
|
-
translation
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
|
|
1147
|
-
//#endregion
|
|
1148
|
-
//#region src/plugins/react/queries/plural.ts
|
|
1149
|
-
function parseForms(node) {
|
|
1150
|
-
const forms = [];
|
|
1151
|
-
if (node.type === "jsx_expression") {
|
|
1152
|
-
const arr = node.namedChildren[0];
|
|
1153
|
-
if (!arr || arr.type !== "array") return {
|
|
1154
|
-
forms: [],
|
|
1155
|
-
error: "Plural forms must be an array"
|
|
1156
|
-
};
|
|
1157
|
-
for (const el of arr.namedChildren) if (el.type === "jsx_element" || el.type === "jsx_fragment") {
|
|
1158
|
-
const { text, error } = buildTemplate(el);
|
|
1159
|
-
if (error) return {
|
|
1160
|
-
forms: [],
|
|
1161
|
-
error
|
|
1162
|
-
};
|
|
1163
|
-
forms.push(text);
|
|
1164
|
-
} else if (el.type === "string") forms.push(el.text.slice(1, -1));
|
|
1165
|
-
else return {
|
|
1166
|
-
forms: [],
|
|
1167
|
-
error: "Unsupported plural form"
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
return { forms };
|
|
1171
|
-
}
|
|
1172
|
-
const pluralQuery = withComment({
|
|
1173
|
-
pattern: `(
|
|
1174
|
-
[
|
|
1175
|
-
(jsx_element (jsx_opening_element name: (identifier) @name))
|
|
1176
|
-
(jsx_self_closing_element name: (identifier) @name)
|
|
1177
|
-
] @call
|
|
1178
|
-
(#eq? @name "Plural")
|
|
1179
|
-
)`,
|
|
1180
|
-
extract(match) {
|
|
1181
|
-
const node = match.captures.find((c) => c.name === "call")?.node;
|
|
1182
|
-
if (!node) return void 0;
|
|
1183
|
-
let attrs = [];
|
|
1184
|
-
if (node.type === "jsx_element") {
|
|
1185
|
-
const open = node.childForFieldName("open_tag");
|
|
1186
|
-
if (open) attrs = open.namedChildren;
|
|
1187
|
-
} else if (node.type === "jsx_self_closing_element") attrs = node.namedChildren.slice(1);
|
|
1188
|
-
let msgctxt;
|
|
1189
|
-
let formsNode;
|
|
1190
|
-
for (const child of attrs) {
|
|
1191
|
-
if (child.type !== "jsx_attribute") continue;
|
|
1192
|
-
const name = child.child(0);
|
|
1193
|
-
const value = child.child(child.childCount - 1);
|
|
1194
|
-
if (name?.text === "context" && value?.type === "string") msgctxt = value.text.slice(1, -1);
|
|
1195
|
-
else if (name?.text === "forms" && value) formsNode = value;
|
|
1196
|
-
}
|
|
1197
|
-
if (!formsNode) return void 0;
|
|
1198
|
-
const { forms, error } = parseForms(formsNode);
|
|
1199
|
-
if (error) return {
|
|
1200
|
-
node,
|
|
1201
|
-
error
|
|
1202
|
-
};
|
|
1203
|
-
if (forms.length === 0) return void 0;
|
|
1204
|
-
const translation = {
|
|
1205
|
-
id: forms[0],
|
|
1206
|
-
plural: forms[1],
|
|
1207
|
-
message: forms
|
|
1208
|
-
};
|
|
1209
|
-
if (msgctxt) translation.context = msgctxt;
|
|
1210
|
-
return {
|
|
1211
|
-
node,
|
|
1212
|
-
translation
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
});
|
|
1216
|
-
|
|
1217
|
-
//#endregion
|
|
1218
|
-
//#region src/plugins/react/queries/index.ts
|
|
1219
|
-
const queries = [messageQuery, pluralQuery];
|
|
1220
|
-
|
|
1221
|
-
//#endregion
|
|
1222
|
-
//#region src/plugins/react/parse.ts
|
|
1223
|
-
function parseSource(source, path$1) {
|
|
1224
|
-
const context = { path: path$1 };
|
|
1225
|
-
const { parser, language } = getParser(path$1);
|
|
1226
|
-
const tree = parser.parse(source);
|
|
1227
|
-
const translations = [];
|
|
1228
|
-
const warnings = [];
|
|
1229
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1230
|
-
for (const spec of queries) {
|
|
1231
|
-
const query = getQuery(language, spec.pattern);
|
|
1232
|
-
for (const match of query.matches(tree.rootNode)) {
|
|
1233
|
-
const message = spec.extract(match);
|
|
1234
|
-
if (!message) continue;
|
|
1235
|
-
const { node, translation, error } = message;
|
|
1236
|
-
if (seen.has(node.id)) continue;
|
|
1237
|
-
seen.add(node.id);
|
|
1238
|
-
const reference = getReference(node, context);
|
|
1239
|
-
if (translation) translations.push({
|
|
1240
|
-
...translation,
|
|
1241
|
-
comments: {
|
|
1242
|
-
...translation.comments,
|
|
1243
|
-
reference
|
|
1244
|
-
}
|
|
1245
|
-
});
|
|
1246
|
-
if (error) warnings.push({
|
|
1247
|
-
error,
|
|
1248
|
-
reference
|
|
1249
|
-
});
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
return {
|
|
1253
|
-
translations,
|
|
1254
|
-
warnings
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
//#endregion
|
|
1259
|
-
//#region src/plugins/react/react.ts
|
|
1260
|
-
const filter = /\.[cm]?[jt]sx$/;
|
|
1261
|
-
function react() {
|
|
1262
|
-
return {
|
|
1263
|
-
name: "react",
|
|
1264
|
-
setup(build) {
|
|
1265
|
-
build.context.logger?.debug("react plugin initialized");
|
|
1266
|
-
build.onResolve({
|
|
1267
|
-
filter: /.*/,
|
|
1268
|
-
namespace: "source"
|
|
1269
|
-
}, ({ entrypoint, path: path$1, namespace: namespace$3 }) => {
|
|
1270
|
-
return {
|
|
1271
|
-
entrypoint,
|
|
1272
|
-
namespace: namespace$3,
|
|
1273
|
-
path: resolve(path$1)
|
|
1274
|
-
};
|
|
1275
|
-
});
|
|
1276
|
-
build.onLoad({
|
|
1277
|
-
filter,
|
|
1278
|
-
namespace: "source"
|
|
1279
|
-
}, async ({ entrypoint, path: path$1, namespace: namespace$3 }) => {
|
|
1280
|
-
const data = await readFile(path$1, "utf8");
|
|
1281
|
-
return {
|
|
1282
|
-
entrypoint,
|
|
1283
|
-
path: path$1,
|
|
1284
|
-
namespace: namespace$3,
|
|
1285
|
-
data
|
|
1286
|
-
};
|
|
1287
|
-
});
|
|
1288
|
-
build.onProcess({
|
|
1289
|
-
filter,
|
|
1290
|
-
namespace: "source"
|
|
1291
|
-
}, ({ entrypoint, path: path$1, data }) => {
|
|
1292
|
-
const { translations, warnings } = parseSource(data, path$1);
|
|
1293
|
-
for (const warning of warnings) build.context.logger?.warn(`${warning.error} at ${warning.reference}`);
|
|
1294
|
-
build.resolve({
|
|
1295
|
-
entrypoint,
|
|
1296
|
-
path: path$1,
|
|
1297
|
-
namespace: "translate",
|
|
1298
|
-
data: translations
|
|
1299
|
-
});
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
//#endregion
|
|
1306
|
-
export { cleanup, core, defineConfig, po, react, run };
|
|
1307
|
-
//# sourceMappingURL=index.js.map
|
|
5
|
+
export { cleanup, core, defineConfig, po, react, run };
|