@let-value/translate-extract 1.0.6-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,721 @@
1
+ const require_chunk = require('../chunk-CUT6urMc.cjs');
2
+ let node_fs = require("node:fs");
3
+ node_fs = require_chunk.__toESM(node_fs);
4
+ let node_path = require("node:path");
5
+ node_path = require_chunk.__toESM(node_path);
6
+ let glob = require("glob");
7
+ glob = require_chunk.__toESM(glob);
8
+ let node_fs_promises = require("node:fs/promises");
9
+ node_fs_promises = require_chunk.__toESM(node_fs_promises);
10
+ let radash = require("radash");
11
+ radash = require_chunk.__toESM(radash);
12
+ let tree_sitter = require("tree-sitter");
13
+ tree_sitter = require_chunk.__toESM(tree_sitter);
14
+ let tree_sitter_javascript = require("tree-sitter-javascript");
15
+ tree_sitter_javascript = require_chunk.__toESM(tree_sitter_javascript);
16
+ let tree_sitter_typescript = require("tree-sitter-typescript");
17
+ tree_sitter_typescript = require_chunk.__toESM(tree_sitter_typescript);
18
+ let oxc_resolver = require("oxc-resolver");
19
+ oxc_resolver = require_chunk.__toESM(oxc_resolver);
20
+ let gettext_parser = require("gettext-parser");
21
+ gettext_parser = require_chunk.__toESM(gettext_parser);
22
+ let plural_forms = require("plural-forms");
23
+ plural_forms = require_chunk.__toESM(plural_forms);
24
+
25
+ //#region src/plugins/core/queries/comment.ts
26
+ function getReference(node, { path: path$1 }) {
27
+ const line = node.startPosition.row + 1;
28
+ const col = node.startPosition.column + 1;
29
+ const rel = (0, node_path.relative)(process.cwd(), path$1);
30
+ return `${rel}:${line}:${col}`;
31
+ }
32
+ function getComment(node) {
33
+ const text = node.text;
34
+ if (text.startsWith("/*")) return text.slice(2, -2).replace(/^\s*\*?\s*/gm, "").trim();
35
+ return text.replace(/^\/\/\s?/, "").trim();
36
+ }
37
+ const withComment = (query) => ({
38
+ pattern: `(
39
+ ((comment) @comment)?
40
+ .
41
+ (_ ${query.pattern})
42
+ )`,
43
+ extract(match) {
44
+ const result = query.extract(match);
45
+ if (!result?.translation) return result;
46
+ const comment = match.captures.find((c) => c.name === "comment")?.node;
47
+ if (!comment) return result;
48
+ if (comment) result.translation.comments = {
49
+ ...result.translation.comments,
50
+ extracted: getComment(comment)
51
+ };
52
+ return result;
53
+ }
54
+ });
55
+
56
+ //#endregion
57
+ //#region src/plugins/core/queries/import.ts
58
+ const importQuery = {
59
+ pattern: `
60
+ [
61
+ (import_statement
62
+ source: (string (string_fragment) @import))
63
+ (export_statement
64
+ source: (string (string_fragment) @import))
65
+ (call_expression
66
+ function: (identifier) @func
67
+ arguments: (arguments (string (string_fragment) @import))
68
+ (#eq? @func "require"))
69
+ (call_expression
70
+ function: (member_expression
71
+ object: (identifier) @obj
72
+ property: (property_identifier) @method)
73
+ arguments: (arguments (string (string_fragment) @import))
74
+ (#eq? @obj "require")
75
+ (#eq? @method "resolve"))
76
+ (call_expression
77
+ function: (import)
78
+ arguments: (arguments (string (string_fragment) @import)))
79
+ ]
80
+ `,
81
+ extract(match) {
82
+ const node = match.captures.find((c) => c.name === "import")?.node;
83
+ return node?.text;
84
+ }
85
+ };
86
+
87
+ //#endregion
88
+ //#region src/plugins/core/queries/utils.ts
89
+ const callPattern = (fnName, args, allowMember = true) => `(
90
+ (call_expression
91
+ function: ${allowMember ? `[
92
+ (identifier) @func
93
+ (member_expression property: (property_identifier) @func)
94
+ ]` : `(identifier) @func`}
95
+ arguments: ${args}
96
+ ) @call
97
+ (#eq? @func "${fnName}")
98
+ )`;
99
+ function isDescendant(node, ancestor) {
100
+ let current = node;
101
+ while (current) {
102
+ if (current.id === ancestor.id) return true;
103
+ current = current.parent;
104
+ }
105
+ return false;
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/plugins/core/queries/message.ts
110
+ const notInPlural = (query) => ({
111
+ pattern: query.pattern,
112
+ extract(match) {
113
+ const result = query.extract(match);
114
+ if (!result?.node) return result;
115
+ let parent = result.node.parent;
116
+ if (parent && parent.type === "arguments") parent = parent.parent;
117
+ if (parent && parent.type === "call_expression") {
118
+ const fn = parent.childForFieldName("function");
119
+ if (fn) {
120
+ if (fn.type === "identifier" && (fn.text === "plural" || fn.text === "ngettext" || fn.text === "pgettext" || fn.text === "npgettext") || fn.type === "member_expression" && [
121
+ "plural",
122
+ "ngettext",
123
+ "pgettext",
124
+ "npgettext"
125
+ ].includes(fn.childForFieldName("property")?.text ?? "")) return void 0;
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+ });
131
+ const messageArg = `[
132
+ (string (string_fragment) @msgid)
133
+ (object
134
+ (_)*
135
+ (pair
136
+ key: (property_identifier) @id_key
137
+ value: (string (string_fragment) @id)
138
+ (#eq? @id_key "id")
139
+ )?
140
+ (_)*
141
+ (pair
142
+ key: (property_identifier) @msg_key
143
+ value: (string (string_fragment) @message)
144
+ (#eq? @msg_key "message")
145
+ )?
146
+ (_)*
147
+ )
148
+ (template_string) @tpl
149
+ ]`;
150
+ const messageArgs = `[ (arguments ${messageArg}) (template_string) @tpl ]`;
151
+ const extractMessage = (name) => (match) => {
152
+ const node = match.captures.find((c) => c.name === "call")?.node;
153
+ if (!node) return void 0;
154
+ const msgid = match.captures.find((c) => c.name === "msgid")?.node.text;
155
+ if (msgid) return {
156
+ node,
157
+ translation: {
158
+ id: msgid,
159
+ message: [msgid]
160
+ }
161
+ };
162
+ const tpl = match.captures.find((c) => c.name === "tpl")?.node;
163
+ if (tpl) {
164
+ for (const child of tpl.children) {
165
+ if (child.type !== "template_substitution") continue;
166
+ const expr = child.namedChildren[0];
167
+ if (!expr || expr.type !== "identifier") return {
168
+ node,
169
+ error: `${name}() template expressions must be simple identifiers`
170
+ };
171
+ }
172
+ const text = tpl.text.slice(1, -1);
173
+ return {
174
+ node,
175
+ translation: {
176
+ id: text,
177
+ message: [text]
178
+ }
179
+ };
180
+ }
181
+ const id = match.captures.find((c) => c.name === "id")?.node.text;
182
+ const message = match.captures.find((c) => c.name === "message")?.node.text;
183
+ const msgId = id ?? message;
184
+ if (!msgId) return void 0;
185
+ const msgstr = message ?? id ?? "";
186
+ return {
187
+ node,
188
+ translation: {
189
+ id: msgId,
190
+ message: [msgstr]
191
+ }
192
+ };
193
+ };
194
+ const messageQuery = notInPlural(withComment({
195
+ pattern: callPattern("message", messageArgs),
196
+ extract: extractMessage("message")
197
+ }));
198
+ const allowed$1 = new Set([
199
+ "string",
200
+ "object",
201
+ "template_string"
202
+ ]);
203
+ const messageInvalidQuery = notInPlural({
204
+ pattern: callPattern("message", "(arguments (_) @arg)"),
205
+ extract(match) {
206
+ const call = match.captures.find((c) => c.name === "call")?.node;
207
+ const node = match.captures.find((c) => c.name === "arg")?.node;
208
+ if (!call || !node) return void 0;
209
+ if (allowed$1.has(node.type)) return void 0;
210
+ return {
211
+ node,
212
+ error: "message() argument must be a string literal, object literal, or template literal"
213
+ };
214
+ }
215
+ });
216
+
217
+ //#endregion
218
+ //#region src/plugins/core/queries/plural-utils.ts
219
+ const extractPluralForms = (name) => (match) => {
220
+ const call = match.captures.find((c) => c.name === "call")?.node;
221
+ const n = match.captures.find((c) => c.name === "n")?.node;
222
+ if (!call || !n || n.nextNamedSibling) return void 0;
223
+ const msgctxt = match.captures.find((c) => c.name === "msgctxt")?.node?.text;
224
+ const msgNodes = match.captures.filter((c) => c.name === "msg").map((c) => c.node);
225
+ const ids = [];
226
+ const strs = [];
227
+ for (const node of msgNodes) {
228
+ const relevant = match.captures.filter((c) => [
229
+ "msgid",
230
+ "id",
231
+ "message",
232
+ "tpl"
233
+ ].includes(c.name) && isDescendant(c.node, node));
234
+ const subMatch = {
235
+ pattern: 0,
236
+ captures: [{
237
+ name: "call",
238
+ node
239
+ }, ...relevant]
240
+ };
241
+ const result = extractMessage(name)(subMatch);
242
+ if (!result) continue;
243
+ if (result.error) return {
244
+ node: call,
245
+ error: result.error
246
+ };
247
+ if (result.translation) {
248
+ ids.push(result.translation.id);
249
+ strs.push(result.translation.message[0] ?? "");
250
+ }
251
+ }
252
+ if (ids.length === 0) return void 0;
253
+ const translation = {
254
+ id: ids[0],
255
+ plural: ids[1],
256
+ message: strs
257
+ };
258
+ if (msgctxt) translation.context = msgctxt;
259
+ return {
260
+ node: call,
261
+ translation
262
+ };
263
+ };
264
+
265
+ //#endregion
266
+ //#region src/plugins/core/queries/context.ts
267
+ const ctxCall = callPattern("context", `(arguments (string (string_fragment) @msgctxt))`).replace(/@call/g, "@ctx").replace(/@func/g, "@ctxfn");
268
+ const contextMsgQuery = withComment({
269
+ pattern: `(
270
+ (call_expression
271
+ function: (member_expression
272
+ object: ${ctxCall}
273
+ property: (property_identifier) @func
274
+ )
275
+ arguments: ${messageArgs}
276
+ ) @call
277
+ (#eq? @func "message")
278
+ )`,
279
+ extract(match) {
280
+ const result = extractMessage("context.message")(match);
281
+ const contextNode = match.captures.find((c) => c.name === "msgctxt")?.node;
282
+ if (!result || !result.translation || !contextNode) return result;
283
+ return {
284
+ node: result.node,
285
+ translation: {
286
+ ...result.translation,
287
+ context: contextNode.text
288
+ }
289
+ };
290
+ }
291
+ });
292
+ const msgCall$3 = callPattern("message", messageArgs, false).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
293
+ const contextPluralQuery = withComment({
294
+ pattern: `(
295
+ (call_expression
296
+ function: (member_expression
297
+ object: ${ctxCall}
298
+ property: (property_identifier) @func
299
+ )
300
+ arguments: (arguments (
301
+ (${msgCall$3} ("," )?)+
302
+ (number) @n
303
+ ))
304
+ ) @call
305
+ (#eq? @func "plural")
306
+ )`,
307
+ extract: extractPluralForms("context.plural")
308
+ });
309
+ const contextInvalidQuery = withComment({
310
+ pattern: ctxCall,
311
+ extract(match) {
312
+ const call = match.captures.find((c) => c.name === "ctx")?.node;
313
+ if (!call) return void 0;
314
+ const parent = call.parent;
315
+ if (parent && parent.type === "member_expression" && parent.childForFieldName("object")?.id === call.id) {
316
+ const property = parent.childForFieldName("property")?.text;
317
+ const grandparent = parent.parent;
318
+ if (grandparent && grandparent.type === "call_expression" && grandparent.childForFieldName("function")?.id === parent.id && (property === "message" || property === "plural")) return void 0;
319
+ }
320
+ return {
321
+ node: call,
322
+ error: "context() must be used with message() or plural() in the same expression"
323
+ };
324
+ }
325
+ });
326
+
327
+ //#endregion
328
+ //#region src/plugins/core/queries/gettext.ts
329
+ const gettextQuery = withComment({
330
+ pattern: callPattern("gettext", messageArgs),
331
+ extract: extractMessage("gettext")
332
+ });
333
+ const allowed = new Set([
334
+ "string",
335
+ "object",
336
+ "template_string",
337
+ "identifier",
338
+ "call_expression"
339
+ ]);
340
+ const gettextInvalidQuery = {
341
+ pattern: callPattern("gettext", "(arguments (_) @arg)"),
342
+ extract(match) {
343
+ const call = match.captures.find((c) => c.name === "call")?.node;
344
+ const node = match.captures.find((c) => c.name === "arg")?.node;
345
+ if (!call || !node) return void 0;
346
+ if (allowed.has(node.type)) return void 0;
347
+ return {
348
+ node,
349
+ error: "gettext() argument must be a string literal, object literal, or template literal"
350
+ };
351
+ }
352
+ };
353
+
354
+ //#endregion
355
+ //#region src/plugins/core/queries/ngettext.ts
356
+ const msgCall$2 = callPattern("message", messageArgs).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
357
+ const plainMsg$1 = `(${messageArg}) @msg`;
358
+ const msgArg$1 = `[${msgCall$2} ${plainMsg$1}]`;
359
+ const ngettextQuery = withComment({
360
+ pattern: callPattern("ngettext", `(arguments ${msgArg$1} "," ${msgArg$1} ("," ${msgArg$1})* "," (_) @n)`),
361
+ extract: extractPluralForms("ngettext")
362
+ });
363
+
364
+ //#endregion
365
+ //#region src/plugins/core/queries/npgettext.ts
366
+ const msgCall$1 = callPattern("message", messageArgs).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
367
+ const plainMsg = `(${messageArg}) @msg`;
368
+ const msgArg = `[${msgCall$1} ${plainMsg}]`;
369
+ const npgettextQuery = withComment({
370
+ pattern: callPattern("npgettext", `(arguments (string (string_fragment) @msgctxt) "," ${msgArg} "," ${msgArg} ("," ${msgArg})* "," (_) @n)`),
371
+ extract: extractPluralForms("npgettext")
372
+ });
373
+
374
+ //#endregion
375
+ //#region src/plugins/core/queries/pgettext.ts
376
+ const pgettextQuery = withComment({
377
+ pattern: callPattern("pgettext", `(arguments (string (string_fragment) @msgctxt) "," ${messageArg})`),
378
+ extract(match) {
379
+ const result = extractMessage("pgettext")(match);
380
+ const contextNode = match.captures.find((c) => c.name === "msgctxt")?.node;
381
+ if (!result || !contextNode || !result.translation) return result;
382
+ return {
383
+ node: result.node,
384
+ translation: {
385
+ ...result.translation,
386
+ context: contextNode.text
387
+ }
388
+ };
389
+ }
390
+ });
391
+
392
+ //#endregion
393
+ //#region src/plugins/core/queries/plural.ts
394
+ const msgCall = callPattern("message", messageArgs, false).replace(/@call/g, "@msg").replace(/@func/g, "@msgfn");
395
+ const pluralQuery = withComment({
396
+ pattern: callPattern("plural", `(arguments (
397
+ (${msgCall} ("," )?)+
398
+ (number) @n
399
+ ))`, false),
400
+ extract: extractPluralForms("plural")
401
+ });
402
+
403
+ //#endregion
404
+ //#region src/plugins/core/queries/index.ts
405
+ const queries = [
406
+ messageQuery,
407
+ messageInvalidQuery,
408
+ gettextQuery,
409
+ gettextInvalidQuery,
410
+ pluralQuery,
411
+ ngettextQuery,
412
+ pgettextQuery,
413
+ npgettextQuery,
414
+ contextMsgQuery,
415
+ contextPluralQuery,
416
+ contextInvalidQuery
417
+ ];
418
+
419
+ //#endregion
420
+ //#region src/plugins/core/parse.ts
421
+ function getLanguage(ext) {
422
+ switch (ext) {
423
+ case ".ts": return tree_sitter_typescript.default.typescript;
424
+ case ".tsx": return tree_sitter_typescript.default.tsx;
425
+ default: return tree_sitter_javascript.default;
426
+ }
427
+ }
428
+ const getCachedParser = (0, radash.memo)(function getCachedParser$1(ext) {
429
+ const parser = new tree_sitter.default();
430
+ const language = getLanguage(ext);
431
+ parser.setLanguage(language);
432
+ return {
433
+ parser,
434
+ language
435
+ };
436
+ });
437
+ function getParser(path$1) {
438
+ const ext = (0, node_path.extname)(path$1);
439
+ return getCachedParser(ext);
440
+ }
441
+ function parseSource(source, path$1) {
442
+ const context = { path: path$1 };
443
+ const { parser, language } = getParser(path$1);
444
+ const tree = parser.parse(source);
445
+ const translations = [];
446
+ const imports = [];
447
+ const seen = /* @__PURE__ */ new Set();
448
+ for (const spec of queries) {
449
+ const query = new tree_sitter.default.Query(language, spec.pattern);
450
+ for (const match of query.matches(tree.rootNode)) {
451
+ const message = spec.extract(match);
452
+ if (!message) continue;
453
+ const { node, translation, error } = message;
454
+ if (seen.has(node.id)) continue;
455
+ seen.add(node.id);
456
+ const reference = getReference(node, context);
457
+ if (translation) translations.push({
458
+ ...translation,
459
+ comments: {
460
+ ...translation.comments,
461
+ reference
462
+ }
463
+ });
464
+ if (error) console.warn(`Parsing error at ${reference}: ${error}`);
465
+ }
466
+ }
467
+ const importTreeQuery = new tree_sitter.default.Query(language, importQuery.pattern);
468
+ for (const match of importTreeQuery.matches(tree.rootNode)) {
469
+ const imp = importQuery.extract(match);
470
+ if (imp) imports.push(imp);
471
+ }
472
+ return {
473
+ translations,
474
+ imports
475
+ };
476
+ }
477
+
478
+ //#endregion
479
+ //#region src/plugins/core/resolve.ts
480
+ function findTsconfig(dir) {
481
+ let current = dir;
482
+ while (true) {
483
+ const config = node_path.default.join(current, "tsconfig.json");
484
+ if (node_fs.default.existsSync(config)) return config;
485
+ const parent = node_path.default.dirname(current);
486
+ if (parent === current) return void 0;
487
+ current = parent;
488
+ }
489
+ }
490
+ const resolverCache = /* @__PURE__ */ new Map();
491
+ function getResolver(dir) {
492
+ const tsconfig = findTsconfig(dir);
493
+ const key = tsconfig ?? "__default__";
494
+ let resolver = resolverCache.get(key);
495
+ if (!resolver) {
496
+ resolver = new oxc_resolver.ResolverFactory({
497
+ extensions: [
498
+ ".ts",
499
+ ".tsx",
500
+ ".js",
501
+ ".jsx",
502
+ ".mjs",
503
+ ".cjs",
504
+ ".json"
505
+ ],
506
+ conditionNames: [
507
+ "import",
508
+ "require",
509
+ "node"
510
+ ],
511
+ ...tsconfig ? { tsconfig: { configFile: tsconfig } } : {}
512
+ });
513
+ resolverCache.set(key, resolver);
514
+ }
515
+ return resolver;
516
+ }
517
+ function resolveImports(file, imports) {
518
+ const dir = node_path.default.dirname(node_path.default.resolve(file));
519
+ const resolver = getResolver(dir);
520
+ const resolved = [];
521
+ for (const spec of imports) {
522
+ const res = resolver.sync(dir, spec);
523
+ if (res.path) resolved.push(res.path);
524
+ }
525
+ return resolved;
526
+ }
527
+
528
+ //#endregion
529
+ //#region src/plugins/core/core.ts
530
+ const filter = /\.([cm]?tsx?|jsx?)$/;
531
+ function core() {
532
+ return {
533
+ name: "core",
534
+ setup(build) {
535
+ build.onResolve({ filter: /.*/ }, ({ entrypoint, path: path$1 }) => {
536
+ return {
537
+ entrypoint,
538
+ path: (0, node_path.resolve)(path$1)
539
+ };
540
+ });
541
+ build.onLoad({ filter }, async ({ entrypoint, path: path$1 }) => {
542
+ const contents = await (0, node_fs_promises.readFile)(path$1, "utf8");
543
+ return {
544
+ entrypoint,
545
+ path: path$1,
546
+ contents
547
+ };
548
+ });
549
+ build.onExtract({ filter }, ({ entrypoint, path: path$1, contents }) => {
550
+ const { translations, imports } = parseSource(contents, path$1);
551
+ if (build.context.config.walk) {
552
+ const paths = resolveImports(path$1, imports);
553
+ for (const path$2 of paths) build.resolvePath({
554
+ entrypoint,
555
+ path: path$2
556
+ });
557
+ }
558
+ return {
559
+ entrypoint,
560
+ path: path$1,
561
+ translations
562
+ };
563
+ });
564
+ }
565
+ };
566
+ }
567
+
568
+ //#endregion
569
+ //#region src/plugins/po/po.ts
570
+ function formatDate(date) {
571
+ const pad = (n) => n.toString().padStart(2, "0");
572
+ const year = date.getFullYear();
573
+ const month = pad(date.getMonth() + 1);
574
+ const day = pad(date.getDate());
575
+ const hours = pad(date.getHours());
576
+ const minutes = pad(date.getMinutes());
577
+ const tzo = -date.getTimezoneOffset();
578
+ const sign = tzo >= 0 ? "+" : "-";
579
+ const offsetHours = pad(Math.floor(Math.abs(tzo) / 60));
580
+ const offsetMinutes = pad(Math.abs(tzo) % 60);
581
+ return `${year}-${month}-${day} ${hours}:${minutes}${sign}${offsetHours}${offsetMinutes}`;
582
+ }
583
+ function collect(source, locale) {
584
+ const translations = { "": {} };
585
+ const nplurals = locale ? (0, plural_forms.getNPlurals)(locale) : void 0;
586
+ for (const { context, id, message, comments, obsolete, plural } of source) {
587
+ const ctx = context || "";
588
+ if (!translations[ctx]) translations[ctx] = {};
589
+ const length = plural ? nplurals ?? message.length : 1;
590
+ translations[ctx][id] = {
591
+ msgctxt: context || void 0,
592
+ msgid: id,
593
+ msgid_plural: plural,
594
+ msgstr: Array.from({ length }, () => ""),
595
+ comments,
596
+ obsolete
597
+ };
598
+ }
599
+ return translations;
600
+ }
601
+ function merge(locale, sources, existing, strategy = "mark", timestamp = /* @__PURE__ */ new Date()) {
602
+ let headers = {};
603
+ let translations = { "": {} };
604
+ const nplurals = (0, plural_forms.getNPlurals)(locale);
605
+ if (existing) {
606
+ const parsed = gettext_parser.po.parse(existing);
607
+ headers = parsed.headers || {};
608
+ translations = parsed.translations || { "": {} };
609
+ for (const ctx of Object.keys(translations)) for (const id of Object.keys(translations[ctx])) {
610
+ if (ctx === "" && id === "") continue;
611
+ translations[ctx][id].obsolete = true;
612
+ }
613
+ }
614
+ const collected = sources.reduce((acc, { translations: translations$1 }) => (0, radash.assign)(acc, translations$1), { "": {} });
615
+ for (const [ctx, msgs] of Object.entries(collected)) {
616
+ if (!translations[ctx]) translations[ctx] = {};
617
+ for (const [id, entry] of Object.entries(msgs)) {
618
+ const existingEntry = translations[ctx][id];
619
+ if (existingEntry) {
620
+ entry.msgstr = existingEntry.msgstr;
621
+ entry.comments = {
622
+ ...entry.comments,
623
+ translator: existingEntry.comments?.translator
624
+ };
625
+ }
626
+ entry.obsolete = false;
627
+ entry.msgstr = entry.msgstr.slice(0, nplurals);
628
+ while (entry.msgstr.length < nplurals) entry.msgstr.push("");
629
+ translations[ctx][id] = entry;
630
+ }
631
+ }
632
+ headers = {
633
+ ...headers,
634
+ "content-type": headers["content-type"] || "text/plain; charset=UTF-8",
635
+ "plural-forms": `nplurals=${nplurals}; plural=${(0, plural_forms.getFormula)(locale)};`,
636
+ language: locale,
637
+ "pot-creation-date": formatDate(timestamp),
638
+ "x-generator": "@let-value/translate-extract"
639
+ };
640
+ if (strategy === "remove") {
641
+ for (const ctx of Object.keys(translations)) for (const id of Object.keys(translations[ctx])) if (translations[ctx][id].obsolete) delete translations[ctx][id];
642
+ }
643
+ const poObj = {
644
+ charset: "utf-8",
645
+ headers,
646
+ translations
647
+ };
648
+ return gettext_parser.po.compile(poObj).toString();
649
+ }
650
+ function po() {
651
+ return {
652
+ name: "po",
653
+ setup(build) {
654
+ build.onCollect({ filter: /.*/ }, ({ entrypoint, translations,...rest }, ctx) => {
655
+ const record = collect(translations, ctx.locale);
656
+ const destination = `${(0, node_path.basename)(entrypoint, (0, node_path.extname)(entrypoint))}.po`;
657
+ return {
658
+ ...rest,
659
+ entrypoint,
660
+ destination,
661
+ translations: record
662
+ };
663
+ });
664
+ build.onGenerate({ filter: /.*\/po$/ }, async ({ path: path$1, locale, collected }, ctx) => {
665
+ const existing = await node_fs_promises.default.readFile(path$1).catch(() => void 0);
666
+ const out = merge(locale, collected, existing, ctx.config.obsolete, ctx.generatedAt);
667
+ await node_fs_promises.default.mkdir((0, node_path.dirname)(path$1), { recursive: true });
668
+ await node_fs_promises.default.writeFile(path$1, out);
669
+ });
670
+ }
671
+ };
672
+ }
673
+
674
+ //#endregion
675
+ //#region src/configuration.ts
676
+ const defaultPlugins = [core(), po()];
677
+ const defaultDestination = (locale, _entrypoint, path$1) => (0, node_path.join)(locale, path$1);
678
+ function defineConfig(config) {
679
+ let plugins;
680
+ const user = config.plugins;
681
+ if (typeof user === "function") plugins = user(defaultPlugins);
682
+ else if (Array.isArray(user)) plugins = [...defaultPlugins, ...user];
683
+ else plugins = defaultPlugins;
684
+ const raw = Array.isArray(config.entrypoints) ? config.entrypoints : [config.entrypoints];
685
+ const entrypoints = [];
686
+ for (const ep of raw) if (typeof ep === "string") {
687
+ const paths = (0, glob.globSync)(ep, { nodir: true });
688
+ if (paths.length === 0) entrypoints.push({ entrypoint: ep });
689
+ else for (const path$1 of paths) entrypoints.push({ entrypoint: path$1 });
690
+ } else {
691
+ const { entrypoint, destination: destination$1, obsolete: obsolete$1 } = ep;
692
+ const paths = (0, glob.globSync)(entrypoint, { nodir: true });
693
+ if (paths.length === 0) entrypoints.push({
694
+ entrypoint,
695
+ destination: destination$1,
696
+ obsolete: obsolete$1
697
+ });
698
+ else for (const path$1 of paths) entrypoints.push({
699
+ entrypoint: path$1,
700
+ destination: destination$1,
701
+ obsolete: obsolete$1
702
+ });
703
+ }
704
+ const defaultLocale = config.defaultLocale ?? "en";
705
+ const locales = config.locales ?? [defaultLocale];
706
+ const destination = config.destination ?? defaultDestination;
707
+ const obsolete = config.obsolete ?? "mark";
708
+ const walk = config.walk ?? true;
709
+ return {
710
+ plugins,
711
+ entrypoints,
712
+ defaultLocale,
713
+ locales,
714
+ destination,
715
+ obsolete,
716
+ walk
717
+ };
718
+ }
719
+
720
+ //#endregion
721
+ exports.defineConfig = defineConfig;