@menapo/vi18n 0.0.0-stage → 0.1.0

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.
Files changed (100) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +110 -2
  3. package/dist/apply-Bre-lY9J.js +228 -0
  4. package/dist/captured-Bj8bO6vd.js +1034 -0
  5. package/dist/check-D34P-S--.js +1 -0
  6. package/dist/check-DyrAbv73.js +201 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +41 -0
  9. package/dist/codec-DuvU2VQy.js +207 -0
  10. package/dist/collect-Cx4_SKOe.js +13 -0
  11. package/dist/collisions-CfpZU8e4.js +217 -0
  12. package/dist/compile-B5Mz6WJm.js +245 -0
  13. package/dist/compile-Cc97JaW2.js +2502 -0
  14. package/dist/concurrency-C9UUWuDC.js +96 -0
  15. package/dist/concurrency-DONL7t4V.js +1 -0
  16. package/dist/config.d.ts +21 -0
  17. package/dist/config.js +39 -0
  18. package/dist/dir-zw6bGMKQ.js +290 -0
  19. package/dist/duplicates-yuRljQ6h.js +185 -0
  20. package/dist/engine-B7p40M__.js +531 -0
  21. package/dist/engine.d.ts +43 -0
  22. package/dist/engine.js +1241 -0
  23. package/dist/exclude-Du-Zd_qI.js +26 -0
  24. package/dist/export-DGsMbcTZ.js +103 -0
  25. package/dist/extract-Dujlsgnk.js +298 -0
  26. package/dist/formats-KPIg-08V.js +781 -0
  27. package/dist/i18next-CrUnKFfh.js +1596 -0
  28. package/dist/i18next-Rgo-v9l0.js +1 -0
  29. package/dist/import-Cwdhcywt.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-BecCTvKi.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui--PcOOYry.js +833 -0
  35. package/dist/lint.d.ts +105 -0
  36. package/dist/lint.js +971 -0
  37. package/dist/load-CQRz4PpM.js +2950 -0
  38. package/dist/load-gW-oJt1Q.js +747 -0
  39. package/dist/locales-DX4MOBFE.js +827 -0
  40. package/dist/lsp-Cq1Nuu-Q.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-Cf0T7qPW.js +28 -0
  43. package/dist/merge-driver-CpiiSVIc.js +422 -0
  44. package/dist/messages-Cxa-5tR0.js +166 -0
  45. package/dist/migrate-CntqvrW4.js +1 -0
  46. package/dist/migrate-dPgq9uzy.js +1484 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DsaHKHEt.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C7g11THL.js +2031 -0
  52. package/dist/pipeline-wxS0SApG.js +1 -0
  53. package/dist/project-Dwx7iPm6.js +381 -0
  54. package/dist/project-pXipT8iG.js +1046 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-CaBVwJ9u.js +519 -0
  58. package/dist/restore-Btq66Tid.js +175 -0
  59. package/dist/rewrite-CiTRAICl.js +803 -0
  60. package/dist/rich-DCUKl5HM.js +97 -0
  61. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  62. package/dist/run-DYD_VXKy.js +694 -0
  63. package/dist/runtime-DTMmliT1.js +64 -0
  64. package/dist/scan-worker.d.ts +62 -0
  65. package/dist/scan-worker.js +1 -0
  66. package/dist/scoped-BMYpSthj.js +44 -0
  67. package/dist/server-BDkykMjb.js +11823 -0
  68. package/dist/server-qv3mz70d.js +10485 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DV3o8QD7.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-DZArW5aE.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-CcVzger_.js +658 -0
  77. package/dist/t-DC-c9zE5.js +454 -0
  78. package/dist/test.d.ts +116 -0
  79. package/dist/test.js +319 -0
  80. package/dist/translate-DZR3MkSA.js +299 -0
  81. package/dist/ts-BaU5yOf0.js +4863 -0
  82. package/dist/types-CgUCkFU1.d.ts +707 -0
  83. package/dist/types-D3QGM8XO.js +146 -0
  84. package/dist/types-DsdWBG0b.d.ts +558 -0
  85. package/dist/types-DtvQ7UdD.js +1948 -0
  86. package/dist/types-wSh9fCBY.js +11 -0
  87. package/dist/units-CQ5Q99RC.js +150 -0
  88. package/dist/url-CRfCOhUI.js +172 -0
  89. package/dist/url-CsBkRaJy.js +225 -0
  90. package/dist/validate-BxpupdAf.js +2615 -0
  91. package/dist/validate-D6DJepZv.js +365 -0
  92. package/dist/version-BlyLGlOa.js +28 -0
  93. package/dist/vite-scan-B_YW4X_a.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-CEMnpv7k.js +651 -0
  97. package/dist/which-C6PFI1Dm.js +116 -0
  98. package/dist/worker-84U6uYB3.js +1656 -0
  99. package/package.json +97 -3
  100. package/schemas/catalog.json +57 -0
@@ -0,0 +1,781 @@
1
+ import { _ as parseJsonCatalog, g as serializePo, h as parsePo, v as serializeJsonCatalog, y as createEntry } from "./library-D5CWvTQs.js";
2
+ import { t as VERSION } from "./version-BlyLGlOa.js";
3
+ import { a as exchangeKey, s as parseExchangeKey } from "./units-CQ5Q99RC.js";
4
+ //#region src/core/exchange/csv.ts
5
+ /**
6
+ * RFC 4180 CSV for `vi18n export --format csv` (PLAN §4.4 P2). The writer
7
+ * starts with a UTF-8 byte order mark and ends rows with CRLF, which is what
8
+ * spreadsheet apps need to open non-ASCII text correctly; the reader takes
9
+ * either line ending, with or without the mark.
10
+ */
11
+ const NEEDS_QUOTES = /[",\r\n]|^\s|\s$/;
12
+ const FORMULA = /^'*[=+\-@\t\r]/;
13
+ const GUARDED = /^'+[=+\-@\t\r]/;
14
+ function field(raw) {
15
+ const value = FORMULA.test(raw) ? `'${raw}` : raw;
16
+ return NEEDS_QUOTES.test(value) ? `"${value.replaceAll("\"", "\"\"")}"` : value;
17
+ }
18
+ function unguard(cell) {
19
+ return GUARDED.test(cell) ? cell.slice(1) : cell;
20
+ }
21
+ function serializeCsv(rows) {
22
+ return `\uFEFF${rows.map((row) => row.map(field).join(",")).join("\r\n")}\r\n`;
23
+ }
24
+ var CsvError = class extends Error {
25
+ line;
26
+ constructor(message, line) {
27
+ super(message);
28
+ this.line = line;
29
+ }
30
+ };
31
+ /** Rows with the 1-based line each starts on; a quoted field may span lines. */
32
+ function parseCsv(input) {
33
+ const text = input.charCodeAt(0) === 65279 ? input.slice(1) : input;
34
+ const rows = [];
35
+ let cells = [];
36
+ let cell = "";
37
+ let line = 1;
38
+ let rowLine = 1;
39
+ let i = 0;
40
+ let quoted = false;
41
+ let started = false;
42
+ const endRow = () => {
43
+ cells.push(unguard(cell));
44
+ if (!(cells.length === 1 && cells[0] === "")) rows.push({
45
+ cells,
46
+ line: rowLine
47
+ });
48
+ cells = [];
49
+ cell = "";
50
+ started = false;
51
+ };
52
+ while (i < text.length) {
53
+ const ch = text[i];
54
+ if (quoted) {
55
+ if (ch === "\"") {
56
+ if (text[i + 1] === "\"") {
57
+ cell += "\"";
58
+ i += 2;
59
+ continue;
60
+ }
61
+ quoted = false;
62
+ i++;
63
+ const next = text[i];
64
+ if (next !== void 0 && next !== "," && next !== "\n" && next !== "\r") throw new CsvError("text after a closing quote; double a quote inside a quoted field", line);
65
+ continue;
66
+ }
67
+ if (ch === "\n") line++;
68
+ cell += ch;
69
+ i++;
70
+ continue;
71
+ }
72
+ if (!started) {
73
+ rowLine = line;
74
+ started = true;
75
+ }
76
+ if (ch === "\"" && cell === "") {
77
+ quoted = true;
78
+ i++;
79
+ } else if (ch === ",") {
80
+ cells.push(unguard(cell));
81
+ cell = "";
82
+ i++;
83
+ } else if (ch === "\r" || ch === "\n") {
84
+ endRow();
85
+ i += ch === "\r" && text[i + 1] === "\n" ? 2 : 1;
86
+ line++;
87
+ } else {
88
+ cell += ch;
89
+ i++;
90
+ }
91
+ }
92
+ if (quoted) throw new CsvError("a quoted field is never closed", line);
93
+ if (started || cell !== "" || cells.length > 0) endRow();
94
+ return rows;
95
+ }
96
+ //#endregion
97
+ //#region src/core/exchange/xml.ts
98
+ var XmlError = class extends Error {
99
+ line;
100
+ constructor(message, line) {
101
+ super(message);
102
+ this.line = line;
103
+ }
104
+ };
105
+ const XML_NS = "http://www.w3.org/XML/1998/namespace";
106
+ const NAME = /[A-Za-z_:\u00C0-\uFFFF][\w.:\u00B7\u00C0-\uFFFF-]*/y;
107
+ const MAX_DEPTH = 256;
108
+ const PREDEFINED = {
109
+ lt: "<",
110
+ gt: ">",
111
+ amp: "&",
112
+ quot: "\"",
113
+ apos: "'"
114
+ };
115
+ /** Characters XML 1.0 allows in a document (§2.2), surrogate pairs checked as pairs. */
116
+ function isXmlChar(code) {
117
+ return code === 9 || code === 10 || code === 13 || code >= 32 && code <= 55295 || code >= 57344 && code <= 65533 || code >= 65536 && code <= 1114111;
118
+ }
119
+ /** Text content: `&`, `<` and `>` (the last so `]]>` can never appear). */
120
+ function escapeText(value) {
121
+ return value.replace(/[&<>]/g, (ch) => ch === "&" ? "&amp;" : ch === "<" ? "&lt;" : "&gt;");
122
+ }
123
+ /** Attribute values in double quotes; whitespace as references, since readers normalise it to spaces. */
124
+ function escapeAttribute(value) {
125
+ return value.replace(/[&<>"\t\n\r]/g, (ch) => {
126
+ switch (ch) {
127
+ case "&": return "&amp;";
128
+ case "<": return "&lt;";
129
+ case ">": return "&gt;";
130
+ case "\"": return "&quot;";
131
+ default: return `&#${ch.charCodeAt(0)};`;
132
+ }
133
+ });
134
+ }
135
+ function parseXml(input) {
136
+ const text = (input.charCodeAt(0) === 65279 ? input.slice(1) : input).replace(/\r\n?/g, "\n");
137
+ let i = 0;
138
+ let line = 1;
139
+ const fail = (message) => {
140
+ throw new XmlError(message, line);
141
+ };
142
+ const advance = (to) => {
143
+ for (let k = i; k < to; k++) if (text.charCodeAt(k) === 10) line++;
144
+ i = to;
145
+ };
146
+ const expect = (literal) => {
147
+ if (!text.startsWith(literal, i)) fail(`expected ${JSON.stringify(literal)}`);
148
+ advance(i + literal.length);
149
+ };
150
+ const skipSpace = () => {
151
+ let k = i;
152
+ while (k < text.length && /[ \t\n]/.test(text[k])) k++;
153
+ advance(k);
154
+ };
155
+ const readName = () => {
156
+ NAME.lastIndex = i;
157
+ const match = NAME.exec(text);
158
+ if (match === null) return fail("expected a name");
159
+ advance(i + match[0].length);
160
+ return match[0];
161
+ };
162
+ const checkChars = (value) => {
163
+ for (const ch of value) if (!isXmlChar(ch.codePointAt(0))) fail("a character XML does not allow");
164
+ };
165
+ const decode = (raw) => {
166
+ checkChars(raw);
167
+ if (!raw.includes("&")) return raw;
168
+ return raw.replace(/&([^;&\s]*);?/g, (match, name) => {
169
+ if (!match.endsWith(";")) return fail("an unterminated entity reference");
170
+ if (name.startsWith("#")) {
171
+ const code = /^#x[0-9a-fA-F]+$/.test(name) ? Number.parseInt(name.slice(2), 16) : /^#\d+$/.test(name) ? Number(name.slice(1)) : NaN;
172
+ if (!isXmlChar(code)) return fail(`an invalid character reference &${name};`);
173
+ return String.fromCodePoint(code);
174
+ }
175
+ const value = Object.hasOwn(PREDEFINED, name) ? PREDEFINED[name] : void 0;
176
+ if (value === void 0) return fail(`an undeclared entity &${name};`);
177
+ return value;
178
+ });
179
+ };
180
+ /** Comments, processing instructions and white space between markup. */
181
+ const skipMisc = () => {
182
+ for (;;) {
183
+ skipSpace();
184
+ if (text.startsWith("<!--", i)) {
185
+ const end = text.indexOf("-->", i + 4);
186
+ if (end === -1) fail("an unterminated comment");
187
+ advance(end + 3);
188
+ } else if (text.startsWith("<?", i)) {
189
+ const end = text.indexOf("?>", i + 2);
190
+ if (end === -1) fail("an unterminated processing instruction");
191
+ advance(end + 2);
192
+ } else if (text.startsWith("<!DOCTYPE", i)) fail("a DOCTYPE; XLIFF needs none, and vi18n does not read entity declarations");
193
+ else return;
194
+ }
195
+ };
196
+ const resolve = (name, scopes, attribute) => {
197
+ const colon = name.indexOf(":");
198
+ const prefix = colon === -1 ? "" : name.slice(0, colon);
199
+ const local = colon === -1 ? name : name.slice(colon + 1);
200
+ if (prefix === "" && attribute) return {
201
+ ns: null,
202
+ local
203
+ };
204
+ if (prefix === "xml") return {
205
+ ns: XML_NS,
206
+ local
207
+ };
208
+ for (let s = scopes.length - 1; s >= 0; s--) {
209
+ const uri = scopes[s].get(prefix);
210
+ if (uri !== void 0) return {
211
+ ns: uri === "" ? null : uri,
212
+ local
213
+ };
214
+ }
215
+ if (prefix !== "") fail(`an undeclared namespace prefix ${prefix}:`);
216
+ return {
217
+ ns: null,
218
+ local
219
+ };
220
+ };
221
+ const element = (scopes, depth) => {
222
+ if (depth > MAX_DEPTH) fail(`elements nested more than ${MAX_DEPTH} deep`);
223
+ const start = line;
224
+ expect("<");
225
+ const name = readName();
226
+ const attrs = /* @__PURE__ */ new Map();
227
+ for (;;) {
228
+ const before = i;
229
+ skipSpace();
230
+ if (text.startsWith("/>", i) || text.startsWith(">", i)) break;
231
+ if (i === before) fail("expected white space between attributes");
232
+ const attr = readName();
233
+ skipSpace();
234
+ expect("=");
235
+ skipSpace();
236
+ const quote = text[i];
237
+ if (quote !== "\"" && quote !== "'") fail("expected a quoted attribute value");
238
+ const end = text.indexOf(quote, i + 1);
239
+ if (end === -1) fail("an unterminated attribute value");
240
+ const raw = text.slice(i + 1, end);
241
+ if (raw.includes("<")) fail("< in an attribute value");
242
+ advance(end + 1);
243
+ if (attrs.has(attr)) fail(`attribute ${attr} given twice`);
244
+ attrs.set(attr, decode(raw.replace(/[\t\n]/g, " ")));
245
+ }
246
+ const declared = /* @__PURE__ */ new Map();
247
+ for (const [attr, value] of attrs) if (attr === "xmlns") declared.set("", value);
248
+ else if (attr.startsWith("xmlns:")) declared.set(attr.slice(6), value);
249
+ const inner = declared.size > 0 ? [...scopes, declared] : scopes;
250
+ const { ns, local } = resolve(name, inner, false);
251
+ for (const attr of attrs.keys()) if (attr !== "xmlns" && !attr.startsWith("xmlns:")) resolve(attr, inner, true);
252
+ const node = {
253
+ kind: "element",
254
+ name,
255
+ ns,
256
+ local,
257
+ attrs,
258
+ children: [],
259
+ line: start
260
+ };
261
+ if (text.startsWith("/>", i)) {
262
+ advance(i + 2);
263
+ return node;
264
+ }
265
+ advance(i + 1);
266
+ let buffer = "";
267
+ const flush = () => {
268
+ if (buffer !== "") node.children.push({
269
+ kind: "text",
270
+ text: buffer
271
+ });
272
+ buffer = "";
273
+ };
274
+ for (;;) {
275
+ if (i >= text.length) fail(`<${name}> is never closed`);
276
+ if (text.startsWith("</", i)) {
277
+ flush();
278
+ advance(i + 2);
279
+ const end = readName();
280
+ if (end !== name) fail(`</${end}> closes <${name}>`);
281
+ skipSpace();
282
+ expect(">");
283
+ return node;
284
+ }
285
+ if (text.startsWith("<!--", i)) {
286
+ const end = text.indexOf("-->", i + 4);
287
+ if (end === -1) fail("an unterminated comment");
288
+ advance(end + 3);
289
+ } else if (text.startsWith("<![CDATA[", i)) {
290
+ const end = text.indexOf("]]>", i + 9);
291
+ if (end === -1) fail("an unterminated CDATA section");
292
+ const raw = text.slice(i + 9, end);
293
+ checkChars(raw);
294
+ buffer += raw;
295
+ advance(end + 3);
296
+ } else if (text.startsWith("<?", i)) {
297
+ const end = text.indexOf("?>", i + 2);
298
+ if (end === -1) fail("an unterminated processing instruction");
299
+ advance(end + 2);
300
+ } else if (text[i] === "<") {
301
+ flush();
302
+ node.children.push(element(inner, depth + 1));
303
+ } else {
304
+ let end = text.indexOf("<", i);
305
+ if (end === -1) end = text.length;
306
+ const raw = text.slice(i, end);
307
+ if (raw.includes("]]>")) fail("]]> in text");
308
+ buffer += decode(raw);
309
+ advance(end);
310
+ }
311
+ }
312
+ };
313
+ skipMisc();
314
+ if (text[i] !== "<") fail("expected the root element");
315
+ const root = element([/* @__PURE__ */ new Map([["", ""]])], 0);
316
+ skipMisc();
317
+ if (i < text.length) fail("content after the root element");
318
+ return root;
319
+ }
320
+ /** Child elements of one namespace, in order. */
321
+ function childElements(node, ns, local) {
322
+ const out = [];
323
+ for (const child of node.children) if (child.kind === "element" && child.ns === ns && (local === void 0 || child.local === local)) out.push(child);
324
+ return out;
325
+ }
326
+ //#endregion
327
+ //#region src/core/exchange/xliff.ts
328
+ const XLIFF_NS = "urn:oasis:names:tc:xliff:document:2.0";
329
+ const AI_SUBSTATE = "vi18n:ai";
330
+ const STATES = [
331
+ "initial",
332
+ "translated",
333
+ "reviewed",
334
+ "final"
335
+ ];
336
+ /**
337
+ * Segment content: characters XML cannot hold become `<cp hex>` (XLIFF 2
338
+ * §4.3.1.8), and a carriage return a character reference, since readers
339
+ * turn a literal one into a line feed.
340
+ */
341
+ function content(value) {
342
+ let out = "";
343
+ for (const ch of escapeText(value)) {
344
+ const code = ch.codePointAt(0);
345
+ if (code === 13) out += "&#13;";
346
+ else if (isXmlChar(code)) out += ch;
347
+ else out += `<cp hex="${code.toString(16).toUpperCase().padStart(4, "0")}"/>`;
348
+ }
349
+ return out;
350
+ }
351
+ /** Notes hold text only; a character XML cannot hold is replaced. */
352
+ function noteText(value) {
353
+ let out = "";
354
+ for (const ch of escapeText(value)) {
355
+ const code = ch.codePointAt(0);
356
+ out += code === 13 ? "&#13;" : isXmlChar(code) ? ch : "�";
357
+ }
358
+ return out;
359
+ }
360
+ /** White space a reader may normalise unless told to preserve it. */
361
+ function space(value) {
362
+ return /[\t\n\r]|^ | $| {2}/.test(value) ? " xml:space=\"preserve\"" : "";
363
+ }
364
+ function serializeXliff(units, meta) {
365
+ const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>"];
366
+ const target = meta.isSourceLocale ? "" : ` trgLang="${escapeAttribute(meta.locale)}"`;
367
+ lines.push(`<xliff xmlns="${XLIFF_NS}" version="2.0" srcLang="${escapeAttribute(meta.sourceLocale)}"${target}>`, " <file id=\"vi18n\" canResegment=\"no\">");
368
+ for (const unit of units) {
369
+ lines.push(` <unit id="${escapeAttribute(unit.id)}">`);
370
+ const notes = [];
371
+ for (const context of unit.entry.contexts) notes.push(`<note category="description">${noteText(context)}</note>`);
372
+ for (const comment of unit.entry.comments) notes.push(`<note category="comment">${noteText(comment)}</note>`);
373
+ for (const ref of unit.entry.refs) notes.push(`<note category="location">${noteText(ref)}</note>`);
374
+ if (unit.entry.maxLength !== null) notes.push(`<note category="max-length">${unit.entry.maxLength}</note>`);
375
+ if (notes.length > 0) {
376
+ lines.push(" <notes>");
377
+ for (const note of notes) lines.push(` ${note}`);
378
+ lines.push(" </notes>");
379
+ }
380
+ const hasTarget = !meta.isSourceLocale && unit.translation !== "";
381
+ let state = "";
382
+ if (hasTarget) state = unit.fuzzy ? " state=\"initial\"" : unit.state === "ai" ? ` state="translated" subState="${AI_SUBSTATE}"` : " state=\"translated\"";
383
+ lines.push(` <segment${state}>`);
384
+ lines.push(` <source${space(unit.source)}>${content(unit.source)}</source>`);
385
+ if (hasTarget) lines.push(` <target${space(unit.translation)}>${content(unit.translation)}</target>`);
386
+ lines.push(" </segment>", " </unit>");
387
+ }
388
+ lines.push(" </file>", "</xliff>");
389
+ return `${lines.join("\n")}\n`;
390
+ }
391
+ /** Inline content as text; markup vi18n never writes (placeholders, codes) cannot be read back. */
392
+ function inlineText(node) {
393
+ let text = "";
394
+ for (const child of node.children) {
395
+ if (child.kind === "text") {
396
+ text += child.text;
397
+ continue;
398
+ }
399
+ if (child.ns === "urn:oasis:names:tc:xliff:document:2.0" && child.local === "cp") {
400
+ const hex = child.attrs.get("hex") ?? "";
401
+ const code = /^[0-9a-fA-F]{1,6}$/.test(hex) ? Number.parseInt(hex, 16) : NaN;
402
+ if (!(code <= 1114111)) return { unsupported: `<cp hex="${hex}"/>` };
403
+ text += String.fromCodePoint(code);
404
+ continue;
405
+ }
406
+ if (child.ns === "urn:oasis:names:tc:xliff:document:2.0" && (child.local === "mrk" || child.local === "pc")) {
407
+ const inner = inlineText(child);
408
+ if ("unsupported" in inner) return inner;
409
+ text += inner.text;
410
+ continue;
411
+ }
412
+ if (child.ns === "urn:oasis:names:tc:xliff:document:2.0" && (child.local === "sm" || child.local === "em")) continue;
413
+ return { unsupported: `<${child.name}>` };
414
+ }
415
+ return { text };
416
+ }
417
+ function rank(state) {
418
+ return STATES.indexOf(state);
419
+ }
420
+ /**
421
+ * Units of an XLIFF 2.x document, from every `<file>` and `<group>`. A unit's
422
+ * segments and ignorables are joined back into one message; its state is
423
+ * the least advanced of its segments'. A segment without `state` but with a
424
+ * `<target>` counts as translated, since some tools never set one.
425
+ */
426
+ function parseXliff(text) {
427
+ let root;
428
+ try {
429
+ root = parseXml(text);
430
+ } catch (error) {
431
+ if (!(error instanceof XmlError)) throw error;
432
+ return {
433
+ locale: null,
434
+ srcLang: null,
435
+ records: [],
436
+ error: {
437
+ message: `not well-formed XML: ${error.message}`,
438
+ line: error.line
439
+ }
440
+ };
441
+ }
442
+ const version = root.attrs.get("version") ?? "";
443
+ if (root.ns !== "urn:oasis:names:tc:xliff:document:2.0" || root.local !== "xliff" || !version.startsWith("2.")) return {
444
+ locale: null,
445
+ srcLang: null,
446
+ records: [],
447
+ error: {
448
+ message: root.local === "xliff" ? `XLIFF ${version || "without a version"} is not supported; export and import XLIFF 2.0` : `the root element is <${root.name}>, not an XLIFF 2 <xliff>`,
449
+ line: root.line
450
+ }
451
+ };
452
+ const records = [];
453
+ const walk = (node) => {
454
+ for (const child of node.children) {
455
+ if (child.kind !== "element" || child.ns !== "urn:oasis:names:tc:xliff:document:2.0") continue;
456
+ if (child.local === "group") walk(child);
457
+ else if (child.local === "unit") records.push(unitRecord(child));
458
+ }
459
+ };
460
+ for (const file of childElements(root, XLIFF_NS, "file")) walk(file);
461
+ return {
462
+ locale: root.attrs.get("trgLang") ?? null,
463
+ srcLang: root.attrs.get("srcLang") ?? null,
464
+ records,
465
+ error: null
466
+ };
467
+ }
468
+ function unitRecord(unit) {
469
+ const raw = unit.attrs.get("id") ?? "";
470
+ const { key, label } = parseExchangeKey(raw);
471
+ const record = {
472
+ line: unit.line,
473
+ ref: raw,
474
+ key: key === "" ? null : key,
475
+ label,
476
+ source: "",
477
+ translation: "",
478
+ state: "translated",
479
+ draft: false
480
+ };
481
+ let source = "";
482
+ let target = "";
483
+ let complete = true;
484
+ let least = "final";
485
+ let ai = false;
486
+ for (const part of unit.children) {
487
+ if (part.kind !== "element" || part.ns !== "urn:oasis:names:tc:xliff:document:2.0") continue;
488
+ if (part.local !== "segment" && part.local !== "ignorable") continue;
489
+ const [from] = childElements(part, XLIFF_NS, "source");
490
+ const [to] = childElements(part, XLIFF_NS, "target");
491
+ const sourceText = from === void 0 ? { text: "" } : inlineText(from);
492
+ const targetText = to === void 0 ? null : inlineText(to);
493
+ if ("unsupported" in sourceText) return {
494
+ ...record,
495
+ unsupported: sourceText.unsupported
496
+ };
497
+ if (targetText !== null && "unsupported" in targetText) return {
498
+ ...record,
499
+ unsupported: targetText.unsupported
500
+ };
501
+ source += sourceText.text;
502
+ if (part.local === "ignorable") {
503
+ target += targetText === null ? sourceText.text : targetText.text;
504
+ continue;
505
+ }
506
+ if (targetText === null) complete = false;
507
+ else target += targetText.text;
508
+ const written = part.attrs.get("state");
509
+ const state = STATES.includes(written ?? "") ? written : targetText === null ? "initial" : "translated";
510
+ if (rank(state) < rank(least)) least = state;
511
+ if (state === "translated" && part.attrs.get("subState") === "vi18n:ai") ai = true;
512
+ }
513
+ record.source = source;
514
+ if (!complete) return record;
515
+ record.translation = target;
516
+ record.draft = least === "initial";
517
+ record.state = least === "translated" && ai ? "ai" : "translated";
518
+ return record;
519
+ }
520
+ //#endregion
521
+ //#region src/core/exchange/formats.ts
522
+ /**
523
+ * Export writers and import readers per format (PLAN §4.4, §5.19). Bilingual
524
+ * formats (PO, vi18n JSON, XLIFF 2, CSV) carry each unit's source; key-only
525
+ * formats (flat JSON, ARB) carry a value per key, which is the source text in
526
+ * the source locale's file and the translation in a target's, the usual pair
527
+ * a key-value TMS uploads. A key-only target file holds only translations
528
+ * that would ship and `""` for the rest, so re-importing it can never turn a
529
+ * fuzzy or invalid draft into a translation.
530
+ */
531
+ /** CSV columns, in the order written; import finds them by name. */
532
+ const CSV_COLUMNS = [
533
+ "key",
534
+ "source",
535
+ "translation",
536
+ "state",
537
+ "context"
538
+ ];
539
+ function serializeExport(format, units, meta) {
540
+ switch (format) {
541
+ case "po":
542
+ case "json": return catalogText(format, units, meta);
543
+ case "xliff2": return serializeXliff(units, meta);
544
+ case "flat": {
545
+ const members = units.map((unit) => ` ${JSON.stringify(exchangeKey(unit))}: ${JSON.stringify(keyOnlyValue(unit, meta))}`);
546
+ return members.length === 0 ? "{}\n" : `{\n${members.join(",\n")}\n}\n`;
547
+ }
548
+ case "arb": {
549
+ const members = [` "@@locale": ${JSON.stringify(meta.locale)}`];
550
+ for (const unit of units) {
551
+ const key = exchangeKey(unit);
552
+ members.push(` ${JSON.stringify(key)}: ${JSON.stringify(keyOnlyValue(unit, meta))}`);
553
+ if (unit.entry.contexts.length > 0) {
554
+ const description = JSON.stringify(unit.entry.contexts.join("\n"));
555
+ members.push(` ${JSON.stringify(`@${key}`)}: { "description": ${description} }`);
556
+ }
557
+ }
558
+ return `{\n${members.join(",\n")}\n}\n`;
559
+ }
560
+ case "csv": {
561
+ const rows = [[...CSV_COLUMNS]];
562
+ for (const unit of units) {
563
+ const state = meta.isSourceLocale ? "" : unit.fuzzy ? "fuzzy" : unit.state;
564
+ rows.push([
565
+ exchangeKey(unit),
566
+ unit.source,
567
+ meta.isSourceLocale ? "" : unit.translation,
568
+ state,
569
+ unit.entry.contexts.join("\n")
570
+ ]);
571
+ }
572
+ return serializeCsv(rows);
573
+ }
574
+ }
575
+ }
576
+ function keyOnlyValue(unit, meta) {
577
+ if (meta.isSourceLocale) return unit.source;
578
+ return unit.usable ? unit.translation : "";
579
+ }
580
+ /** A PO or vi18n JSON catalog holding just the exported units, written by the catalog writers. */
581
+ function catalogText(format, units, meta) {
582
+ const layout = format === "json" ? "json" : meta.msgid;
583
+ const sameFormat = format === "json" === (meta.layout === "json");
584
+ const catalog = {
585
+ locale: meta.locale,
586
+ layout,
587
+ header: {
588
+ language: meta.locale,
589
+ idScheme: meta.idScheme,
590
+ sourceLocale: meta.sourceLocale,
591
+ aiModel: meta.header?.aiModel ?? null,
592
+ extra: sameFormat ? meta.header?.extra ?? [] : []
593
+ },
594
+ entries: units.map((unit) => createEntry({
595
+ ...unit.entry,
596
+ manualId: unit.manual ? unit.id : null,
597
+ foreignContext: null,
598
+ label: layout === "id" ? unit.label : layout === "json" && !unit.manual ? unit.id : null,
599
+ source: unit.source,
600
+ translation: unit.translation,
601
+ state: unit.state,
602
+ fuzzy: unit.fuzzy,
603
+ line: 0,
604
+ wrapped: false
605
+ })),
606
+ obsolete: [],
607
+ droppedObsolete: 0
608
+ };
609
+ const options = {
610
+ isSourceLocale: meta.isSourceLocale,
611
+ version: VERSION
612
+ };
613
+ return format === "json" ? serializeJsonCatalog(catalog, options) : serializePo(catalog, options);
614
+ }
615
+ /** Reads an import file; `msgid` is the PO layout to read it in (the project's `catalogs.msgid`). */
616
+ function readImport(format, text, options) {
617
+ switch (format) {
618
+ case "po": return readPo(text, options.msgid);
619
+ case "json": return readJson(text);
620
+ case "xliff2": return parseXliff(text);
621
+ case "flat": return readKeyValue(text, false);
622
+ case "arb": return readKeyValue(text, true);
623
+ case "csv": return readCsv(text);
624
+ }
625
+ }
626
+ function failed(message, line = 0) {
627
+ return {
628
+ locale: null,
629
+ records: [],
630
+ error: {
631
+ message,
632
+ line
633
+ }
634
+ };
635
+ }
636
+ function readPo(text, layout) {
637
+ const { catalog, diagnostics } = parsePo(text, {
638
+ locale: "",
639
+ layout,
640
+ isSourceLocale: false
641
+ });
642
+ const error = diagnostics.find((diagnostic) => diagnostic.severity === "error");
643
+ if (error !== void 0) return failed(error.message, error.line ?? 0);
644
+ const records = catalog.entries.map((entry) => ({
645
+ line: entry.line,
646
+ ref: entry.manualId ?? entry.foreignContext ?? (layout === "id" ? entry.label : entry.source) ?? "",
647
+ key: entry.foreignContext !== null ? `\u0004${entry.foreignContext}` : entry.manualId ?? (layout === "id" ? entry.label : null),
648
+ label: layout === "id" ? entry.label : null,
649
+ source: layout === "id" ? null : entry.source,
650
+ translation: entry.translation,
651
+ state: entry.state === "ai" ? "ai" : "translated",
652
+ draft: entry.fuzzy
653
+ }));
654
+ return {
655
+ locale: catalog.header.language === "" ? null : catalog.header.language,
656
+ records,
657
+ error: null
658
+ };
659
+ }
660
+ function readJson(text) {
661
+ const { catalog, diagnostics } = parseJsonCatalog(text, {
662
+ locale: "",
663
+ isSourceLocale: false
664
+ });
665
+ const error = diagnostics.find((diagnostic) => diagnostic.severity === "error");
666
+ if (error !== void 0) return failed(error.message, error.line ?? 0);
667
+ const records = catalog.entries.map((entry) => ({
668
+ line: entry.line,
669
+ ref: entry.manualId ?? entry.label ?? "",
670
+ key: entry.manualId ?? entry.label,
671
+ label: null,
672
+ source: entry.source,
673
+ translation: entry.translation,
674
+ state: entry.state === "ai" ? "ai" : "translated",
675
+ draft: entry.fuzzy
676
+ }));
677
+ return {
678
+ locale: catalog.header.language === "" ? null : catalog.header.language,
679
+ records,
680
+ error: null
681
+ };
682
+ }
683
+ /** 1-based line of each top-level member, found by a light scan (JSON.parse gives none). */
684
+ function memberLines(text) {
685
+ const lines = /* @__PURE__ */ new Map();
686
+ for (const match of text.matchAll(/^[ \t]*("(?:[^"\\\n]|\\.)*")\s*:/gm)) try {
687
+ const key = JSON.parse(match[1]);
688
+ if (!lines.has(key)) lines.set(key, text.slice(0, match.index).split("\n").length);
689
+ } catch {}
690
+ return lines;
691
+ }
692
+ /**
693
+ * Flat JSON and ARB: string values by key. Nested objects (a TMS's "nested
694
+ * JSON" splits keys at dots) are joined back with `.`; ARB's `@@…` and
695
+ * `@key` metadata members are not messages.
696
+ */
697
+ function readKeyValue(text, arb) {
698
+ let root;
699
+ try {
700
+ root = JSON.parse(text.charCodeAt(0) === 65279 ? text.slice(1) : text);
701
+ } catch (error) {
702
+ return failed(error instanceof Error ? error.message : String(error));
703
+ }
704
+ if (typeof root !== "object" || root === null || Array.isArray(root)) return failed("expected a JSON object of keys and strings");
705
+ const lines = memberLines(text);
706
+ const records = [];
707
+ let locale = null;
708
+ let bad = null;
709
+ const walk = (value, prefix, top) => {
710
+ for (const [name, inner] of Object.entries(value)) {
711
+ if (arb && top && name.startsWith("@")) {
712
+ if (name === "@@locale" && typeof inner === "string") locale = inner;
713
+ continue;
714
+ }
715
+ const path = prefix === "" ? name : `${prefix}.${name}`;
716
+ if (typeof inner === "string") {
717
+ const { key, label } = parseExchangeKey(path);
718
+ records.push({
719
+ line: lines.get(name) ?? 0,
720
+ ref: path,
721
+ key,
722
+ label,
723
+ source: null,
724
+ translation: inner,
725
+ state: null,
726
+ draft: false
727
+ });
728
+ } else if (!arb && typeof inner === "object" && inner !== null && !Array.isArray(inner)) walk(inner, path, false);
729
+ else bad ??= path;
730
+ }
731
+ };
732
+ walk(root, "", true);
733
+ if (bad !== null) return failed(`${JSON.stringify(bad)} is not a string`, lines.get(bad) ?? 0);
734
+ return {
735
+ locale,
736
+ records,
737
+ error: null
738
+ };
739
+ }
740
+ function readCsv(text) {
741
+ let rows;
742
+ try {
743
+ rows = parseCsv(text);
744
+ } catch (error) {
745
+ if (error instanceof CsvError) return failed(error.message, error.line);
746
+ throw error;
747
+ }
748
+ const header = rows[0];
749
+ if (header === void 0) return failed("the file is empty; expected a header row");
750
+ const column = (name) => header.cells.findIndex((cell) => cell.trim() === name);
751
+ const keyAt = column("key");
752
+ const translationAt = column("translation");
753
+ if (keyAt === -1 || translationAt === -1) return failed("the header row needs \"key\" and \"translation\" columns", header.line);
754
+ const sourceAt = column("source");
755
+ const stateAt = column("state");
756
+ const records = [];
757
+ for (const row of rows.slice(1)) {
758
+ const raw = row.cells[keyAt] ?? "";
759
+ if (raw === "") continue;
760
+ const { key, label } = parseExchangeKey(raw);
761
+ const source = sourceAt === -1 ? "" : row.cells[sourceAt] ?? "";
762
+ const state = stateAt === -1 ? "" : (row.cells[stateAt] ?? "").trim();
763
+ records.push({
764
+ line: row.line,
765
+ ref: raw,
766
+ key,
767
+ label,
768
+ source: source === "" ? null : source,
769
+ translation: row.cells[translationAt] ?? "",
770
+ state: state === "ai" ? "ai" : stateAt === -1 ? null : "translated",
771
+ draft: state === "fuzzy"
772
+ });
773
+ }
774
+ return {
775
+ locale: null,
776
+ records,
777
+ error: null
778
+ };
779
+ }
780
+ //#endregion
781
+ export { serializeExport as n, readImport as t };