@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.
- package/LICENSE +49 -0
- package/README.md +110 -2
- package/dist/apply-Bre-lY9J.js +228 -0
- package/dist/captured-Bj8bO6vd.js +1034 -0
- package/dist/check-D34P-S--.js +1 -0
- package/dist/check-DyrAbv73.js +201 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +41 -0
- package/dist/codec-DuvU2VQy.js +207 -0
- package/dist/collect-Cx4_SKOe.js +13 -0
- package/dist/collisions-CfpZU8e4.js +217 -0
- package/dist/compile-B5Mz6WJm.js +245 -0
- package/dist/compile-Cc97JaW2.js +2502 -0
- package/dist/concurrency-C9UUWuDC.js +96 -0
- package/dist/concurrency-DONL7t4V.js +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +39 -0
- package/dist/dir-zw6bGMKQ.js +290 -0
- package/dist/duplicates-yuRljQ6h.js +185 -0
- package/dist/engine-B7p40M__.js +531 -0
- package/dist/engine.d.ts +43 -0
- package/dist/engine.js +1241 -0
- package/dist/exclude-Du-Zd_qI.js +26 -0
- package/dist/export-DGsMbcTZ.js +103 -0
- package/dist/extract-Dujlsgnk.js +298 -0
- package/dist/formats-KPIg-08V.js +781 -0
- package/dist/i18next-CrUnKFfh.js +1596 -0
- package/dist/i18next-Rgo-v9l0.js +1 -0
- package/dist/import-Cwdhcywt.js +225 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +239 -0
- package/dist/init-BecCTvKi.js +792 -0
- package/dist/library-D5CWvTQs.js +1882 -0
- package/dist/lingui--PcOOYry.js +833 -0
- package/dist/lint.d.ts +105 -0
- package/dist/lint.js +971 -0
- package/dist/load-CQRz4PpM.js +2950 -0
- package/dist/load-gW-oJt1Q.js +747 -0
- package/dist/locales-DX4MOBFE.js +827 -0
- package/dist/lsp-Cq1Nuu-Q.js +31 -0
- package/dist/match-BGr8EXB3.js +89 -0
- package/dist/mcp-Cf0T7qPW.js +28 -0
- package/dist/merge-driver-CpiiSVIc.js +422 -0
- package/dist/messages-Cxa-5tR0.js +166 -0
- package/dist/migrate-CntqvrW4.js +1 -0
- package/dist/migrate-dPgq9uzy.js +1484 -0
- package/dist/openai-CTaMDcl-.js +346 -0
- package/dist/output-DsaHKHEt.js +164 -0
- package/dist/parse.d.ts +62 -0
- package/dist/parse.js +156 -0
- package/dist/pipeline-C7g11THL.js +2031 -0
- package/dist/pipeline-wxS0SApG.js +1 -0
- package/dist/project-Dwx7iPm6.js +381 -0
- package/dist/project-pXipT8iG.js +1046 -0
- package/dist/pseudo-DRv30iN5.js +145 -0
- package/dist/raw-C4Y6vyGY.js +4708 -0
- package/dist/react-intl-CaBVwJ9u.js +519 -0
- package/dist/restore-Btq66Tid.js +175 -0
- package/dist/rewrite-CiTRAICl.js +803 -0
- package/dist/rich-DCUKl5HM.js +97 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/run-DYD_VXKy.js +694 -0
- package/dist/runtime-DTMmliT1.js +64 -0
- package/dist/scan-worker.d.ts +62 -0
- package/dist/scan-worker.js +1 -0
- package/dist/scoped-BMYpSthj.js +44 -0
- package/dist/server-BDkykMjb.js +11823 -0
- package/dist/server-qv3mz70d.js +10485 -0
- package/dist/server.d.ts +169 -0
- package/dist/server.js +1234 -0
- package/dist/sitemap-DV3o8QD7.js +159 -0
- package/dist/sources-D19czDSA.js +2242 -0
- package/dist/status-DZArW5aE.js +149 -0
- package/dist/stdio-CVaJfxA7.js +29 -0
- package/dist/stdio-FPOKQAgu.js +95 -0
- package/dist/sync-CcVzger_.js +658 -0
- package/dist/t-DC-c9zE5.js +454 -0
- package/dist/test.d.ts +116 -0
- package/dist/test.js +319 -0
- package/dist/translate-DZR3MkSA.js +299 -0
- package/dist/ts-BaU5yOf0.js +4863 -0
- package/dist/types-CgUCkFU1.d.ts +707 -0
- package/dist/types-D3QGM8XO.js +146 -0
- package/dist/types-DsdWBG0b.d.ts +558 -0
- package/dist/types-DtvQ7UdD.js +1948 -0
- package/dist/types-wSh9fCBY.js +11 -0
- package/dist/units-CQ5Q99RC.js +150 -0
- package/dist/url-CRfCOhUI.js +172 -0
- package/dist/url-CsBkRaJy.js +225 -0
- package/dist/validate-BxpupdAf.js +2615 -0
- package/dist/validate-D6DJepZv.js +365 -0
- package/dist/version-BlyLGlOa.js +28 -0
- package/dist/vite-scan-B_YW4X_a.js +242 -0
- package/dist/vite.d.ts +34 -0
- package/dist/vite.js +7558 -0
- package/dist/vue-i18n-CEMnpv7k.js +651 -0
- package/dist/which-C6PFI1Dm.js +116 -0
- package/dist/worker-84U6uYB3.js +1656 -0
- package/package.json +97 -3
- 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 === "&" ? "&" : ch === "<" ? "<" : ">");
|
|
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 "&";
|
|
128
|
+
case "<": return "<";
|
|
129
|
+
case ">": return ">";
|
|
130
|
+
case "\"": return """;
|
|
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 += " ";
|
|
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 ? " " : 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 };
|