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