@verbaly/compiler 0.27.0 → 0.28.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/README.md +4 -3
- package/dist/cli.js +330 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +325 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -204,7 +204,7 @@ declare function clearDrafts(drafts: Drafts, locale: string, keys?: string[]): v
|
|
|
204
204
|
declare function effectiveDrafts(drafts: Drafts, catalogs: Catalogs): Drafts;
|
|
205
205
|
//#endregion
|
|
206
206
|
//#region src/exchange.d.ts
|
|
207
|
-
type ExchangeFormat = 'xliff' | 'csv';
|
|
207
|
+
type ExchangeFormat = 'xliff' | 'csv' | 'po';
|
|
208
208
|
type MobileFormat = 'android-xml' | 'ios-strings';
|
|
209
209
|
type ExportFormat = ExchangeFormat | MobileFormat;
|
|
210
210
|
declare function isMobileFormat(format: ExportFormat): format is MobileFormat;
|
package/dist/index.js
CHANGED
|
@@ -768,7 +768,7 @@ function pseudoLocalize(message) {
|
|
|
768
768
|
}
|
|
769
769
|
const ch = message[i];
|
|
770
770
|
if (ch === "{") {
|
|
771
|
-
const end = matchBrace(message, i);
|
|
771
|
+
const end = matchBrace$1(message, i);
|
|
772
772
|
out += message.slice(i, end);
|
|
773
773
|
i = end;
|
|
774
774
|
continue;
|
|
@@ -792,7 +792,7 @@ function pseudoLocalize(message) {
|
|
|
792
792
|
const pad = "~".repeat(Math.ceil(letters / 3));
|
|
793
793
|
return `⟦${out}${pad ? " " + pad : ""}⟧`;
|
|
794
794
|
}
|
|
795
|
-
function matchBrace(message, start) {
|
|
795
|
+
function matchBrace$1(message, start) {
|
|
796
796
|
let depth = 0;
|
|
797
797
|
for (let i = start; i < message.length; i++) {
|
|
798
798
|
const two = message.slice(i, i + 2);
|
|
@@ -1271,6 +1271,222 @@ function effectiveDrafts(drafts, catalogs) {
|
|
|
1271
1271
|
return out;
|
|
1272
1272
|
}
|
|
1273
1273
|
//#endregion
|
|
1274
|
+
//#region src/inline.ts
|
|
1275
|
+
const OPEN_TAG = /^<([a-zA-Z][a-zA-Z0-9-]*)\s*(\/?)>/;
|
|
1276
|
+
function messageToInline(text) {
|
|
1277
|
+
return renderParts(parseParts(text, 0).parts, /* @__PURE__ */ new Map());
|
|
1278
|
+
}
|
|
1279
|
+
function inlineToMessage(xml) {
|
|
1280
|
+
let out = "";
|
|
1281
|
+
let text = "";
|
|
1282
|
+
let i = 0;
|
|
1283
|
+
const flush = () => {
|
|
1284
|
+
out += unescapeXml(text);
|
|
1285
|
+
text = "";
|
|
1286
|
+
};
|
|
1287
|
+
while (i < xml.length) {
|
|
1288
|
+
if (xml[i] === "<") {
|
|
1289
|
+
const slice = xml.slice(i);
|
|
1290
|
+
const ph = /^<ph\b([^>]*?)\/\s*>/.exec(slice) ?? /^<ph\b([^>]*)>\s*<\/ph\s*>/.exec(slice);
|
|
1291
|
+
if (ph) {
|
|
1292
|
+
flush();
|
|
1293
|
+
out += attr(ph[1], "disp") ?? `{${attr(ph[1], "id") ?? "ph"}}`;
|
|
1294
|
+
i += ph[0].length;
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const pc = /^<pc\b([^>]*)>/.exec(slice);
|
|
1298
|
+
if (pc) {
|
|
1299
|
+
const close = findPcClose(xml, i + pc[0].length);
|
|
1300
|
+
if (close) {
|
|
1301
|
+
flush();
|
|
1302
|
+
const id = attr(pc[1], "id") ?? "pc";
|
|
1303
|
+
out += attr(pc[1], "dispStart") ?? `<${id}>`;
|
|
1304
|
+
out += inlineToMessage(xml.slice(i + pc[0].length, close.innerEnd));
|
|
1305
|
+
out += attr(pc[1], "dispEnd") ?? `</${id}>`;
|
|
1306
|
+
i = close.end;
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
text += xml[i];
|
|
1312
|
+
i += 1;
|
|
1313
|
+
}
|
|
1314
|
+
flush();
|
|
1315
|
+
return out;
|
|
1316
|
+
}
|
|
1317
|
+
function escapeXml(text) {
|
|
1318
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1319
|
+
}
|
|
1320
|
+
function unescapeXml(text) {
|
|
1321
|
+
return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (_, entity) => {
|
|
1322
|
+
if (entity === "lt") return "<";
|
|
1323
|
+
if (entity === "gt") return ">";
|
|
1324
|
+
if (entity === "amp") return "&";
|
|
1325
|
+
if (entity === "quot") return "\"";
|
|
1326
|
+
if (entity === "apos") return "'";
|
|
1327
|
+
const code = entity.startsWith("#x") ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
1328
|
+
return String.fromCodePoint(code);
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
function parseParts(text, pos, closeTag) {
|
|
1332
|
+
const parts = [];
|
|
1333
|
+
let plain = "";
|
|
1334
|
+
const flush = () => {
|
|
1335
|
+
if (plain) parts.push({
|
|
1336
|
+
kind: "text",
|
|
1337
|
+
text: plain
|
|
1338
|
+
});
|
|
1339
|
+
plain = "";
|
|
1340
|
+
};
|
|
1341
|
+
let i = pos;
|
|
1342
|
+
while (i < text.length) {
|
|
1343
|
+
const pair = text[i] + (text[i + 1] ?? "");
|
|
1344
|
+
if (pair === "{{" || pair === "}}") {
|
|
1345
|
+
plain += pair;
|
|
1346
|
+
i += 2;
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
if (text[i] === "{") {
|
|
1350
|
+
const end = matchBrace(text, i);
|
|
1351
|
+
if (end === -1) {
|
|
1352
|
+
plain += "{";
|
|
1353
|
+
i += 1;
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
const src = text.slice(i, end + 1);
|
|
1357
|
+
if (hasTopLevelPipe(src)) plain += src;
|
|
1358
|
+
else {
|
|
1359
|
+
flush();
|
|
1360
|
+
parts.push({
|
|
1361
|
+
kind: "ph",
|
|
1362
|
+
name: paramName(src),
|
|
1363
|
+
src
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
i = end + 1;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (text[i] === "<") {
|
|
1370
|
+
if (closeTag && text.startsWith(`</${closeTag}>`, i)) {
|
|
1371
|
+
flush();
|
|
1372
|
+
return {
|
|
1373
|
+
parts,
|
|
1374
|
+
end: i + closeTag.length + 3,
|
|
1375
|
+
closed: true
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
const open = OPEN_TAG.exec(text.slice(i));
|
|
1379
|
+
if (open) {
|
|
1380
|
+
const [openSrc, name, selfClose] = open;
|
|
1381
|
+
if (selfClose) {
|
|
1382
|
+
flush();
|
|
1383
|
+
parts.push({
|
|
1384
|
+
kind: "ph",
|
|
1385
|
+
name: idSafe(name),
|
|
1386
|
+
src: openSrc
|
|
1387
|
+
});
|
|
1388
|
+
i += openSrc.length;
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
const inner = parseParts(text, i + openSrc.length, name);
|
|
1392
|
+
if (inner.closed) {
|
|
1393
|
+
flush();
|
|
1394
|
+
parts.push({
|
|
1395
|
+
kind: "pc",
|
|
1396
|
+
name: idSafe(name),
|
|
1397
|
+
openSrc,
|
|
1398
|
+
closeSrc: `</${name}>`,
|
|
1399
|
+
children: inner.parts
|
|
1400
|
+
});
|
|
1401
|
+
i = inner.end;
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
plain += "<";
|
|
1406
|
+
i += 1;
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
plain += text[i];
|
|
1410
|
+
i += 1;
|
|
1411
|
+
}
|
|
1412
|
+
flush();
|
|
1413
|
+
return {
|
|
1414
|
+
parts,
|
|
1415
|
+
end: i,
|
|
1416
|
+
closed: false
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function matchBrace(text, start) {
|
|
1420
|
+
let depth = 0;
|
|
1421
|
+
for (let i = start; i < text.length; i++) {
|
|
1422
|
+
const pair = text[i] + (text[i + 1] ?? "");
|
|
1423
|
+
if (pair === "{{" || pair === "}}") {
|
|
1424
|
+
i += 1;
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
if (text[i] === "{") depth += 1;
|
|
1428
|
+
else if (text[i] === "}") {
|
|
1429
|
+
depth -= 1;
|
|
1430
|
+
if (depth === 0) return i;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return -1;
|
|
1434
|
+
}
|
|
1435
|
+
function hasTopLevelPipe(src) {
|
|
1436
|
+
let depth = 0;
|
|
1437
|
+
for (let i = 0; i < src.length; i++) {
|
|
1438
|
+
const pair = src[i] + (src[i + 1] ?? "");
|
|
1439
|
+
if (pair === "{{" || pair === "}}" || pair === "||") {
|
|
1440
|
+
i += 1;
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (src[i] === "{") depth += 1;
|
|
1444
|
+
else if (src[i] === "}") depth -= 1;
|
|
1445
|
+
else if (src[i] === "|" && depth === 1) return true;
|
|
1446
|
+
}
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
function paramName(src) {
|
|
1450
|
+
const name = /^\{\s*([^{}|:\s]+)/.exec(src)?.[1];
|
|
1451
|
+
return name ? idSafe(name) : "ph";
|
|
1452
|
+
}
|
|
1453
|
+
function idSafe(name) {
|
|
1454
|
+
return name.replace(/[^a-zA-Z0-9._-]/g, "_") || "ph";
|
|
1455
|
+
}
|
|
1456
|
+
function renderParts(parts, taken) {
|
|
1457
|
+
let out = "";
|
|
1458
|
+
for (const part of parts) if (part.kind === "text") out += escapeXml(part.text);
|
|
1459
|
+
else if (part.kind === "ph") out += `<ph id="${uniqueId(part.name, taken)}" disp="${escapeXml(part.src)}"/>`;
|
|
1460
|
+
else {
|
|
1461
|
+
const id = uniqueId(part.name, taken);
|
|
1462
|
+
out += `<pc id="${id}" dispStart="${escapeXml(part.openSrc)}" dispEnd="${escapeXml(part.closeSrc)}">`;
|
|
1463
|
+
out += renderParts(part.children, taken);
|
|
1464
|
+
out += "</pc>";
|
|
1465
|
+
}
|
|
1466
|
+
return out;
|
|
1467
|
+
}
|
|
1468
|
+
function uniqueId(name, taken) {
|
|
1469
|
+
const count = taken.get(name) ?? 0;
|
|
1470
|
+
taken.set(name, count + 1);
|
|
1471
|
+
return count === 0 ? name : `${name}${count + 1}`;
|
|
1472
|
+
}
|
|
1473
|
+
function findPcClose(xml, from) {
|
|
1474
|
+
const tags = /<pc\b[^>]*>|<\/pc\s*>/g;
|
|
1475
|
+
tags.lastIndex = from;
|
|
1476
|
+
let depth = 1;
|
|
1477
|
+
for (let match = tags.exec(xml); match; match = tags.exec(xml)) {
|
|
1478
|
+
depth += match[0].startsWith("</") ? -1 : 1;
|
|
1479
|
+
if (depth === 0) return {
|
|
1480
|
+
innerEnd: match.index,
|
|
1481
|
+
end: tags.lastIndex
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function attr(attrs, name) {
|
|
1486
|
+
const value = new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`).exec(attrs)?.[1];
|
|
1487
|
+
return value === void 0 ? void 0 : unescapeXml(value);
|
|
1488
|
+
}
|
|
1489
|
+
//#endregion
|
|
1274
1490
|
//#region src/mobile.ts
|
|
1275
1491
|
function androidValuesDir(locale, sourceLocale) {
|
|
1276
1492
|
if (locale === sourceLocale) return "values";
|
|
@@ -1310,6 +1526,97 @@ function iosText(text) {
|
|
|
1310
1526
|
return text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
1311
1527
|
}
|
|
1312
1528
|
//#endregion
|
|
1529
|
+
//#region src/po.ts
|
|
1530
|
+
function toPo(sourceLocale, locale, entries) {
|
|
1531
|
+
return [
|
|
1532
|
+
[
|
|
1533
|
+
"msgid \"\"",
|
|
1534
|
+
"msgstr \"\"",
|
|
1535
|
+
`${poString("Project-Id-Version: verbaly\n")}`,
|
|
1536
|
+
`${poString("MIME-Version: 1.0\n")}`,
|
|
1537
|
+
`${poString("Content-Type: text/plain; charset=UTF-8\n")}`,
|
|
1538
|
+
`${poString("Content-Transfer-Encoding: 8bit\n")}`,
|
|
1539
|
+
`${poString(`Language: ${locale}\n`)}`,
|
|
1540
|
+
`${poString(`X-Source-Language: ${sourceLocale}\n`)}`
|
|
1541
|
+
].join("\n"),
|
|
1542
|
+
...entries.map(({ key, source, target, location }) => [
|
|
1543
|
+
...location?.length ? [`#: ${location.join(" ")}`] : [],
|
|
1544
|
+
`msgctxt ${poString(key)}`,
|
|
1545
|
+
`msgid ${poString(source)}`,
|
|
1546
|
+
`msgstr ${poString(target)}`
|
|
1547
|
+
].join("\n")),
|
|
1548
|
+
""
|
|
1549
|
+
].join("\n\n");
|
|
1550
|
+
}
|
|
1551
|
+
function parsePo(content) {
|
|
1552
|
+
const entries = {};
|
|
1553
|
+
let locale;
|
|
1554
|
+
let msgctxt;
|
|
1555
|
+
let msgid;
|
|
1556
|
+
let msgstr;
|
|
1557
|
+
let fuzzy = false;
|
|
1558
|
+
let field;
|
|
1559
|
+
const finish = () => {
|
|
1560
|
+
if (msgid === "" && msgctxt === void 0) {
|
|
1561
|
+
const lang = /^Language:[ \t]*(.+?)[ \t]*$/m.exec(msgstr ?? "")?.[1];
|
|
1562
|
+
if (lang) locale = lang;
|
|
1563
|
+
} else if (msgctxt !== void 0 && msgid !== void 0) entries[msgctxt] = fuzzy ? "" : msgstr ?? "";
|
|
1564
|
+
msgctxt = msgid = msgstr = field = void 0;
|
|
1565
|
+
fuzzy = false;
|
|
1566
|
+
};
|
|
1567
|
+
for (const raw of content.replace(/^\uFEFF/, "").split(/\r\n|[\r\n]/)) {
|
|
1568
|
+
const line = raw.trim();
|
|
1569
|
+
if (line === "") {
|
|
1570
|
+
finish();
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
if (line.startsWith("#")) {
|
|
1574
|
+
if (/^#,.*\bfuzzy\b/.test(line)) fuzzy = true;
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const keyword = /^(msgctxt|msgid_plural|msgid|msgstr(?:\[\d+\])?)\s+(.*)$/.exec(line);
|
|
1578
|
+
if (keyword) {
|
|
1579
|
+
const [, name, rest] = keyword;
|
|
1580
|
+
const value = poUnquote(rest);
|
|
1581
|
+
if (value === void 0) continue;
|
|
1582
|
+
if (name === "msgctxt") {
|
|
1583
|
+
msgctxt = value;
|
|
1584
|
+
field = "msgctxt";
|
|
1585
|
+
} else if (name === "msgid") {
|
|
1586
|
+
msgid = value;
|
|
1587
|
+
field = "msgid";
|
|
1588
|
+
} else if (name === "msgstr" || name === "msgstr[0]") {
|
|
1589
|
+
msgstr = value;
|
|
1590
|
+
field = "msgstr";
|
|
1591
|
+
} else field = "other";
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
const continuation = poUnquote(line);
|
|
1595
|
+
if (continuation === void 0 || field === void 0 || field === "other") continue;
|
|
1596
|
+
if (field === "msgctxt") msgctxt = (msgctxt ?? "") + continuation;
|
|
1597
|
+
else if (field === "msgid") msgid = (msgid ?? "") + continuation;
|
|
1598
|
+
else msgstr = (msgstr ?? "") + continuation;
|
|
1599
|
+
}
|
|
1600
|
+
finish();
|
|
1601
|
+
return {
|
|
1602
|
+
locale,
|
|
1603
|
+
entries
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function poString(text) {
|
|
1607
|
+
return `"${text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r")}"`;
|
|
1608
|
+
}
|
|
1609
|
+
function poUnquote(text) {
|
|
1610
|
+
const match = /^"((?:[^"\\]|\\.)*)"$/.exec(text);
|
|
1611
|
+
if (!match) return void 0;
|
|
1612
|
+
return match[1].replace(/\\(.)/g, (_, ch) => {
|
|
1613
|
+
if (ch === "n") return "\n";
|
|
1614
|
+
if (ch === "t") return " ";
|
|
1615
|
+
if (ch === "r") return "\r";
|
|
1616
|
+
return ch;
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
//#endregion
|
|
1313
1620
|
//#region src/translate.ts
|
|
1314
1621
|
async function translateCatalogs(cfg, catalogs, provider, options = {}) {
|
|
1315
1622
|
const batchSize = options.batchSize ?? 20;
|
|
@@ -1391,8 +1698,8 @@ function exportCatalogs(cfg, catalogs, options = {}) {
|
|
|
1391
1698
|
}));
|
|
1392
1699
|
const untranslated = all.filter((entry) => !entry.target);
|
|
1393
1700
|
const entries = options.missing ? untranslated : all;
|
|
1394
|
-
const path = join(dir, `${locale}.${format === "
|
|
1395
|
-
writeFileSync(path, format === "csv" ? toCsv(entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
1701
|
+
const path = join(dir, `${locale}.${format === "xliff" ? "xlf" : format}`);
|
|
1702
|
+
writeFileSync(path, format === "csv" ? toCsv(entries) : format === "po" ? toPo(cfg.sourceLocale, locale, entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
1396
1703
|
files.push({
|
|
1397
1704
|
locale,
|
|
1398
1705
|
path,
|
|
@@ -1480,7 +1787,14 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1480
1787
|
locale: localeOverride ?? basename(file).replace(/\.csv$/i, ""),
|
|
1481
1788
|
entries: parseCsv(content)
|
|
1482
1789
|
};
|
|
1483
|
-
|
|
1790
|
+
if (/\.po$/i.test(file)) {
|
|
1791
|
+
const parsed = parsePo(content);
|
|
1792
|
+
return {
|
|
1793
|
+
locale: localeOverride ?? parsed.locale ?? basename(file).replace(/\.po$/i, ""),
|
|
1794
|
+
entries: parsed.entries
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
throw new Error(`[verbaly] ${file}: unsupported format, expected .xlf, .xliff, .csv or .po.`);
|
|
1484
1798
|
}
|
|
1485
1799
|
function toXliff(sourceLocale, locale, entries) {
|
|
1486
1800
|
const units = entries.map(({ key, source, target, location }) => [
|
|
@@ -1491,8 +1805,8 @@ function toXliff(sourceLocale, locale, entries) {
|
|
|
1491
1805
|
" </notes>"
|
|
1492
1806
|
] : [],
|
|
1493
1807
|
` <segment state="${target ? "translated" : "initial"}">`,
|
|
1494
|
-
` <source>${
|
|
1495
|
-
` <target>${
|
|
1808
|
+
` <source>${messageToInline(source)}</source>`,
|
|
1809
|
+
` <target>${messageToInline(target)}</target>`,
|
|
1496
1810
|
" </segment>",
|
|
1497
1811
|
" </unit>"
|
|
1498
1812
|
].join("\n")).join("\n");
|
|
@@ -1513,30 +1827,16 @@ function parseXliff(content) {
|
|
|
1513
1827
|
const id = /\bid\s*=\s*"([^"]*)"/.exec(match[1])?.[1];
|
|
1514
1828
|
if (!id) continue;
|
|
1515
1829
|
const target = /<target\b[^>]*>([\s\S]*?)<\/target>/.exec(match[2])?.[1];
|
|
1516
|
-
entries[unescapeXml(id)] = target === void 0 ? "" :
|
|
1830
|
+
entries[unescapeXml(id)] = target === void 0 ? "" : inlineToMessage(stripCdata(target));
|
|
1517
1831
|
}
|
|
1518
1832
|
return {
|
|
1519
1833
|
locale,
|
|
1520
1834
|
entries
|
|
1521
1835
|
};
|
|
1522
1836
|
}
|
|
1523
|
-
function escapeXml(text) {
|
|
1524
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1525
|
-
}
|
|
1526
1837
|
function stripCdata(text) {
|
|
1527
1838
|
return text.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1");
|
|
1528
1839
|
}
|
|
1529
|
-
function unescapeXml(text) {
|
|
1530
|
-
return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (_, entity) => {
|
|
1531
|
-
if (entity === "lt") return "<";
|
|
1532
|
-
if (entity === "gt") return ">";
|
|
1533
|
-
if (entity === "amp") return "&";
|
|
1534
|
-
if (entity === "quot") return "\"";
|
|
1535
|
-
if (entity === "apos") return "'";
|
|
1536
|
-
const code = entity.startsWith("#x") ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
|
|
1537
|
-
return String.fromCodePoint(code);
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
1840
|
function toCsv(entries) {
|
|
1541
1841
|
return [
|
|
1542
1842
|
"key,source,target,location",
|
|
@@ -1758,7 +2058,8 @@ async function renderSite(cfg, options = {}) {
|
|
|
1758
2058
|
const attribute = options.attribute ?? cfg.render.attribute;
|
|
1759
2059
|
const baseUrl = (options.baseUrl ?? cfg.render.baseUrl)?.replace(/\/+$/, "");
|
|
1760
2060
|
const wantHreflang = (options.hreflang ?? cfg.render.hreflang ?? true) && baseUrl !== void 0;
|
|
1761
|
-
const
|
|
2061
|
+
const sitemap = options.sitemap ?? cfg.render.sitemap ?? false;
|
|
2062
|
+
const wantSitemap = sitemap !== false && baseUrl !== void 0;
|
|
1762
2063
|
const clean = options.clean ?? cfg.render.clean ?? false;
|
|
1763
2064
|
const catalogs = loadCatalogs(cfg);
|
|
1764
2065
|
if (clean) {
|
|
@@ -1801,7 +2102,7 @@ async function renderSite(cfg, options = {}) {
|
|
|
1801
2102
|
writeFileSync(out, result.html);
|
|
1802
2103
|
}
|
|
1803
2104
|
}
|
|
1804
|
-
if (wantSitemap && urls.length) writeFileSync(join(site, typeof
|
|
2105
|
+
if (wantSitemap && urls.length) writeFileSync(join(site, typeof sitemap === "string" ? sitemap : "sitemap-i18n.xml"), buildSitemap(urls));
|
|
1805
2106
|
return {
|
|
1806
2107
|
files: files.length,
|
|
1807
2108
|
locales: [...locales],
|