@verbatra/sdk 0.5.0-next.2 → 0.5.0-next.3
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/dist/index.cjs +196 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +196 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -777,6 +777,50 @@ function createDefaultClient2() {
|
|
|
777
777
|
};
|
|
778
778
|
return { client, freeAccount };
|
|
779
779
|
}
|
|
780
|
+
var DEEPL_MAX_TEXTS_PER_REQUEST = 50;
|
|
781
|
+
var DEEPL_MAX_PAYLOAD_BYTES = 128 * 1024;
|
|
782
|
+
var DEEPL_PAYLOAD_OVERHEAD_RESERVE_BYTES = 4 * 1024;
|
|
783
|
+
var DEEPL_MAX_TEXT_PAYLOAD_BYTES = DEEPL_MAX_PAYLOAD_BYTES - DEEPL_PAYLOAD_OVERHEAD_RESERVE_BYTES;
|
|
784
|
+
function estimateWireBytes(text) {
|
|
785
|
+
return encodeURIComponent(text).length;
|
|
786
|
+
}
|
|
787
|
+
function chunkTextsForDeepL(texts) {
|
|
788
|
+
const chunks = [];
|
|
789
|
+
let current = [];
|
|
790
|
+
let currentBytes = 0;
|
|
791
|
+
for (const text of texts) {
|
|
792
|
+
const textBytes = estimateWireBytes(text);
|
|
793
|
+
const startsNewChunk = current.length > 0 && (current.length >= DEEPL_MAX_TEXTS_PER_REQUEST || currentBytes + textBytes > DEEPL_MAX_TEXT_PAYLOAD_BYTES);
|
|
794
|
+
if (startsNewChunk) {
|
|
795
|
+
chunks.push(current);
|
|
796
|
+
current = [];
|
|
797
|
+
currentBytes = 0;
|
|
798
|
+
}
|
|
799
|
+
current.push(text);
|
|
800
|
+
currentBytes += textBytes;
|
|
801
|
+
}
|
|
802
|
+
if (current.length > 0) {
|
|
803
|
+
chunks.push(current);
|
|
804
|
+
}
|
|
805
|
+
return chunks;
|
|
806
|
+
}
|
|
807
|
+
var DEPRECATED_BARE_TARGET_CODES = /* @__PURE__ */ new Set(["en", "pt"]);
|
|
808
|
+
function assertValidDeepLSourceLocale(locale) {
|
|
809
|
+
if (locale.includes("-")) {
|
|
810
|
+
throw new ProviderError(
|
|
811
|
+
"INVALID_REQUEST",
|
|
812
|
+
`DeepL does not accept a regional or script source locale code: "${locale}". Only the base language code is valid as a DeepL source (for example, use "en" instead of "en-US").`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function assertValidDeepLTargetLocale(locale) {
|
|
817
|
+
if (DEPRECATED_BARE_TARGET_CODES.has(locale.toLowerCase())) {
|
|
818
|
+
throw new ProviderError(
|
|
819
|
+
"INVALID_REQUEST",
|
|
820
|
+
`DeepL requires a disambiguated target locale code instead of "${locale}" (for example, "en-GB" or "en-US" for English, "pt-PT" or "pt-BR" for Portuguese).`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
780
824
|
var PLACEHOLDER_UNSUPPORTED_MESSAGE = "Some entries contain placeholders or ICU syntax that DeepL cannot preserve; they were left untranslated. Use an LLM provider to translate placeholder-bearing strings.";
|
|
781
825
|
function partitionByPlaceholders(entries) {
|
|
782
826
|
const protectable = [];
|
|
@@ -856,6 +900,8 @@ function resolveClient(deps) {
|
|
|
856
900
|
}
|
|
857
901
|
async function translate(bundle, config, request) {
|
|
858
902
|
const data = validateRequest(request);
|
|
903
|
+
assertValidDeepLSourceLocale(data.sourceLocale);
|
|
904
|
+
assertValidDeepLTargetLocale(data.targetLocale);
|
|
859
905
|
const { protectable, unprotectable } = partitionByPlaceholders(data.entries);
|
|
860
906
|
const genericGlossarySupplied = request.glossary !== void 0 && Object.keys(request.glossary).length > 0;
|
|
861
907
|
const { options, notices } = buildTranslateOptions({
|
|
@@ -883,7 +929,7 @@ async function translateProtectable(client, data, protectable, options, extract,
|
|
|
883
929
|
return { values: /* @__PURE__ */ new Map(), integrity: /* @__PURE__ */ new Map() };
|
|
884
930
|
}
|
|
885
931
|
const texts = protectable.map((entry) => entry.value);
|
|
886
|
-
const results = await
|
|
932
|
+
const results = await callClientChunked(
|
|
887
933
|
client,
|
|
888
934
|
texts,
|
|
889
935
|
data.sourceLocale,
|
|
@@ -901,6 +947,14 @@ function callClient2(client, texts, sourceLang, targetLang, options, signal) {
|
|
|
901
947
|
signal
|
|
902
948
|
);
|
|
903
949
|
}
|
|
950
|
+
async function callClientChunked(client, texts, sourceLang, targetLang, options, signal) {
|
|
951
|
+
const results = [];
|
|
952
|
+
for (const chunk2 of chunkTextsForDeepL(texts)) {
|
|
953
|
+
const chunkResults = await callClient2(client, chunk2, sourceLang, targetLang, options, signal);
|
|
954
|
+
results.push(...chunkResults);
|
|
955
|
+
}
|
|
956
|
+
return results;
|
|
957
|
+
}
|
|
904
958
|
var geminiConfigSchema = z.object({
|
|
905
959
|
model: z.string().min(1),
|
|
906
960
|
maxOutputTokens: z.number().int().positive()
|
|
@@ -1372,6 +1426,17 @@ function buildProvider(config) {
|
|
|
1372
1426
|
|
|
1373
1427
|
// src/config/schema.ts
|
|
1374
1428
|
var DEFAULT_MAX_BATCH_SIZE = 50;
|
|
1429
|
+
function findCaseInsensitiveDuplicate(locales) {
|
|
1430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1431
|
+
for (const locale of locales) {
|
|
1432
|
+
const key = locale.toLowerCase();
|
|
1433
|
+
if (seen.has(key)) {
|
|
1434
|
+
return locale;
|
|
1435
|
+
}
|
|
1436
|
+
seen.add(key);
|
|
1437
|
+
}
|
|
1438
|
+
return void 0;
|
|
1439
|
+
}
|
|
1375
1440
|
var verbatraConfigSchema = z.strictObject({
|
|
1376
1441
|
sourceLocale: z.string().min(1),
|
|
1377
1442
|
targetLocales: z.array(z.string().min(1)).min(1),
|
|
@@ -1413,6 +1478,14 @@ var verbatraConfigSchema = z.strictObject({
|
|
|
1413
1478
|
}).refine((config) => !config.targetLocales.includes(config.sourceLocale), {
|
|
1414
1479
|
message: "targetLocales must not include the source locale",
|
|
1415
1480
|
path: ["targetLocales"]
|
|
1481
|
+
}).refine((config) => findCaseInsensitiveDuplicate(config.targetLocales) === void 0, {
|
|
1482
|
+
error: (issue) => {
|
|
1483
|
+
const duplicate = findCaseInsensitiveDuplicate(
|
|
1484
|
+
issue.input.targetLocales
|
|
1485
|
+
);
|
|
1486
|
+
return `targetLocales must not contain case-insensitively duplicate locales: "${duplicate}"`;
|
|
1487
|
+
},
|
|
1488
|
+
path: ["targetLocales"]
|
|
1416
1489
|
}).refine((config) => config.files.pattern.includes(LOCALE_TOKEN), {
|
|
1417
1490
|
message: `files.pattern must contain the ${LOCALE_TOKEN} token`,
|
|
1418
1491
|
path: ["files", "pattern"]
|
|
@@ -2128,11 +2201,24 @@ function unflattenEntries(entries) {
|
|
|
2128
2201
|
}
|
|
2129
2202
|
return root;
|
|
2130
2203
|
}
|
|
2131
|
-
function
|
|
2204
|
+
function mergeDescriptions(entries, content, deriveDescriptions) {
|
|
2205
|
+
if (!deriveDescriptions) {
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
for (const [key, description] of deriveDescriptions(content)) {
|
|
2209
|
+
const entry = entries.get(key);
|
|
2210
|
+
if (entry !== void 0) {
|
|
2211
|
+
entries.set(key, { ...entry, description });
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
function toEntries(content, namespace, parse3, deriveEntry, keyMode, validateTree, deriveDescriptions) {
|
|
2132
2216
|
try {
|
|
2133
2217
|
const tree = parse3(content);
|
|
2134
2218
|
validateTree?.(tree);
|
|
2135
|
-
|
|
2219
|
+
const entries = flattenTree(tree, namespace, deriveEntry, keyMode);
|
|
2220
|
+
mergeDescriptions(entries, content, deriveDescriptions);
|
|
2221
|
+
return entries;
|
|
2136
2222
|
} catch (error) {
|
|
2137
2223
|
rethrowStructured(error, "The file could not be parsed.");
|
|
2138
2224
|
}
|
|
@@ -2151,6 +2237,7 @@ function createTreeFileAdapter(options) {
|
|
|
2151
2237
|
validateTree,
|
|
2152
2238
|
buildWriteTree,
|
|
2153
2239
|
comparePlaceholders,
|
|
2240
|
+
deriveDescriptions,
|
|
2154
2241
|
keyMode = "literal-leaf"
|
|
2155
2242
|
} = options;
|
|
2156
2243
|
return {
|
|
@@ -2162,7 +2249,15 @@ function createTreeFileAdapter(options) {
|
|
|
2162
2249
|
async read(filePath, locale) {
|
|
2163
2250
|
const content = await readFileContent(filePath);
|
|
2164
2251
|
const namespace = namespaceOf(filePath);
|
|
2165
|
-
const entries = toEntries(
|
|
2252
|
+
const entries = toEntries(
|
|
2253
|
+
content,
|
|
2254
|
+
namespace,
|
|
2255
|
+
parse3,
|
|
2256
|
+
deriveEntry,
|
|
2257
|
+
keyMode,
|
|
2258
|
+
validateTree,
|
|
2259
|
+
deriveDescriptions
|
|
2260
|
+
);
|
|
2166
2261
|
const resource = { locale, namespace, format, entries };
|
|
2167
2262
|
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
2168
2263
|
return { resource, invalidIcuKeys };
|
|
@@ -2203,6 +2298,31 @@ function stripArbMetadata(tree) {
|
|
|
2203
2298
|
function originalKey(encoded) {
|
|
2204
2299
|
return decodeKeyToSegments(encoded).join(".");
|
|
2205
2300
|
}
|
|
2301
|
+
function messageKeyForMetadata(key) {
|
|
2302
|
+
return isMetadataKey(key) && !key.startsWith("@@") ? key.slice(1) : null;
|
|
2303
|
+
}
|
|
2304
|
+
function descriptionOf(value) {
|
|
2305
|
+
if (typeof value !== "object" || value === null) {
|
|
2306
|
+
return void 0;
|
|
2307
|
+
}
|
|
2308
|
+
const description = value.description;
|
|
2309
|
+
return typeof description === "string" ? description : void 0;
|
|
2310
|
+
}
|
|
2311
|
+
function extractArbDescriptions(content) {
|
|
2312
|
+
const tree = parseArbObject(content);
|
|
2313
|
+
const out = /* @__PURE__ */ new Map();
|
|
2314
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
2315
|
+
const messageKey = messageKeyForMetadata(key);
|
|
2316
|
+
if (messageKey === null) {
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
const description = descriptionOf(value);
|
|
2320
|
+
if (description !== void 0) {
|
|
2321
|
+
out.set(encodeSegment(messageKey), description);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
return out;
|
|
2325
|
+
}
|
|
2206
2326
|
function messagesFromEntries(entries) {
|
|
2207
2327
|
const out = /* @__PURE__ */ new Map();
|
|
2208
2328
|
for (const [key, entry] of entries) {
|
|
@@ -2259,7 +2379,8 @@ function createArbAdapter() {
|
|
|
2259
2379
|
computeInvalidIcuKeys: icuInvalidKeys,
|
|
2260
2380
|
validateMessage: icuIsValid,
|
|
2261
2381
|
comparePlaceholders: compareIcuPlaceholders,
|
|
2262
|
-
buildWriteTree: buildArbWriteTree
|
|
2382
|
+
buildWriteTree: buildArbWriteTree,
|
|
2383
|
+
deriveDescriptions: extractArbDescriptions
|
|
2263
2384
|
});
|
|
2264
2385
|
}
|
|
2265
2386
|
function createJsonFileAdapter(options) {
|
|
@@ -2565,16 +2686,33 @@ function parseXml(content) {
|
|
|
2565
2686
|
}
|
|
2566
2687
|
return { doc, root };
|
|
2567
2688
|
}
|
|
2689
|
+
function firstNoteText(parent) {
|
|
2690
|
+
const note = childByName(parent, "note");
|
|
2691
|
+
if (note === null) {
|
|
2692
|
+
return void 0;
|
|
2693
|
+
}
|
|
2694
|
+
const text = (note.textContent ?? "").trim();
|
|
2695
|
+
return text === "" ? void 0 : text;
|
|
2696
|
+
}
|
|
2697
|
+
function transUnitDescription(tu) {
|
|
2698
|
+
return firstNoteText(tu);
|
|
2699
|
+
}
|
|
2700
|
+
function unitDescription(unit) {
|
|
2701
|
+
const notes = childByName(unit, "notes");
|
|
2702
|
+
return notes === null ? void 0 : firstNoteText(notes);
|
|
2703
|
+
}
|
|
2568
2704
|
function walkXliff12(root) {
|
|
2569
2705
|
const units = [];
|
|
2570
2706
|
collectByTag(root, "trans-unit").forEach((tu, index) => {
|
|
2571
2707
|
const source = childByName(tu, "source");
|
|
2572
2708
|
if (source !== null) {
|
|
2709
|
+
const description = transUnitDescription(tu);
|
|
2573
2710
|
units.push({
|
|
2574
2711
|
key: unitKey(tu, index),
|
|
2575
2712
|
source,
|
|
2576
2713
|
target: childByName(tu, "target"),
|
|
2577
|
-
container: tu
|
|
2714
|
+
container: tu,
|
|
2715
|
+
...description !== void 0 ? { description } : {}
|
|
2578
2716
|
});
|
|
2579
2717
|
}
|
|
2580
2718
|
});
|
|
@@ -2584,12 +2722,19 @@ function walkXliff20(root) {
|
|
|
2584
2722
|
const units = [];
|
|
2585
2723
|
collectByTag(root, "unit").forEach((unit, index) => {
|
|
2586
2724
|
const baseKey = unitKey(unit, index);
|
|
2725
|
+
const description = unitDescription(unit);
|
|
2587
2726
|
const segments = elementChildren(unit).filter((el) => el.localName === "segment");
|
|
2588
2727
|
segments.forEach((segment, segIndex) => {
|
|
2589
2728
|
const source = childByName(segment, "source");
|
|
2590
2729
|
if (source !== null) {
|
|
2591
2730
|
const key = segments.length > 1 ? `${baseKey}#${segIndex}` : baseKey;
|
|
2592
|
-
units.push({
|
|
2731
|
+
units.push({
|
|
2732
|
+
key,
|
|
2733
|
+
source,
|
|
2734
|
+
target: childByName(segment, "target"),
|
|
2735
|
+
container: segment,
|
|
2736
|
+
...description !== void 0 ? { description } : {}
|
|
2737
|
+
});
|
|
2593
2738
|
}
|
|
2594
2739
|
});
|
|
2595
2740
|
});
|
|
@@ -2628,7 +2773,8 @@ function parseXliffEntries(content, namespace) {
|
|
|
2628
2773
|
namespace,
|
|
2629
2774
|
value,
|
|
2630
2775
|
placeholders: extractXliffPlaceholders(value),
|
|
2631
|
-
isPlural: false
|
|
2776
|
+
isPlural: false,
|
|
2777
|
+
...unit.description !== void 0 ? { description: unit.description } : {}
|
|
2632
2778
|
});
|
|
2633
2779
|
}
|
|
2634
2780
|
return out;
|
|
@@ -3519,10 +3665,13 @@ var INSTRUCTIONS_LINES = [
|
|
|
3519
3665
|
" structure and the argument names exactly. Translate only the human-readable text.",
|
|
3520
3666
|
"6. Do not edit the 'Key' column or the hidden 'Source hash' column. They map your translation",
|
|
3521
3667
|
" back to the right string. You may sort or filter rows freely; mapping does not depend on row order.",
|
|
3668
|
+
"7. The 'Context' column, when present, is reference text from the source file explaining what the",
|
|
3669
|
+
" string means or where it is used. It is read-only; leave it as-is.",
|
|
3522
3670
|
"",
|
|
3523
3671
|
"Status values:",
|
|
3524
3672
|
" new - this string has no translation yet.",
|
|
3525
3673
|
" changed - the source string changed and the translation needs updating.",
|
|
3674
|
+
" unchanged - already up to date, included for reference. No action needed.",
|
|
3526
3675
|
"",
|
|
3527
3676
|
"When you are done, save the file and send it back. verbatra checks every value on import:",
|
|
3528
3677
|
"a value with a broken placeholder, invalid ICU, or a source that changed since export is",
|
|
@@ -3534,7 +3683,8 @@ var COLUMN = {
|
|
|
3534
3683
|
current: 3,
|
|
3535
3684
|
status: 4,
|
|
3536
3685
|
translation: 5,
|
|
3537
|
-
sourceHash: 6
|
|
3686
|
+
sourceHash: 6,
|
|
3687
|
+
context: 7
|
|
3538
3688
|
};
|
|
3539
3689
|
var HEADERS = [
|
|
3540
3690
|
"Key",
|
|
@@ -3542,7 +3692,8 @@ var HEADERS = [
|
|
|
3542
3692
|
"Current translation",
|
|
3543
3693
|
"Status",
|
|
3544
3694
|
"Translation",
|
|
3545
|
-
"Source hash"
|
|
3695
|
+
"Source hash",
|
|
3696
|
+
"Context"
|
|
3546
3697
|
];
|
|
3547
3698
|
var HEADER_ROW = 1;
|
|
3548
3699
|
var INSTRUCTIONS_SHEET_NAME = "Instructions";
|
|
@@ -3561,7 +3712,8 @@ var COLUMN_WIDTHS = {
|
|
|
3561
3712
|
[COLUMN.source]: 50,
|
|
3562
3713
|
[COLUMN.current]: 50,
|
|
3563
3714
|
[COLUMN.status]: 12,
|
|
3564
|
-
[COLUMN.translation]: 50
|
|
3715
|
+
[COLUMN.translation]: 50,
|
|
3716
|
+
[COLUMN.context]: 50
|
|
3565
3717
|
};
|
|
3566
3718
|
var TEXT_NUMBER_FORMAT = "@";
|
|
3567
3719
|
function styleHeader(sheet) {
|
|
@@ -3592,7 +3744,8 @@ function writeRow(sheet, sheetRow) {
|
|
|
3592
3744
|
translationCell.numFmt = TEXT_NUMBER_FORMAT;
|
|
3593
3745
|
translationCell.value = sheetRow.translation === "" ? null : sheetRow.translation;
|
|
3594
3746
|
row.getCell(COLUMN.sourceHash).value = sheetRow.sourceHash;
|
|
3595
|
-
|
|
3747
|
+
row.getCell(COLUMN.context).value = sheetRow.context;
|
|
3748
|
+
for (let column = COLUMN.key; column <= COLUMN.context; column += 1) {
|
|
3596
3749
|
const cell = row.getCell(column);
|
|
3597
3750
|
cell.protection = { locked: column !== COLUMN.translation };
|
|
3598
3751
|
if (column !== COLUMN.translation) {
|
|
@@ -3617,6 +3770,26 @@ function assertValidWorksheetName(locale) {
|
|
|
3617
3770
|
);
|
|
3618
3771
|
}
|
|
3619
3772
|
}
|
|
3773
|
+
function assertNoWorksheetNameCollisions(sheets) {
|
|
3774
|
+
const reservedKey = INSTRUCTIONS_SHEET_NAME.toLowerCase();
|
|
3775
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3776
|
+
for (const sheet of sheets) {
|
|
3777
|
+
const key = sheet.locale.toLowerCase();
|
|
3778
|
+
if (key === reservedKey) {
|
|
3779
|
+
throw new ExchangeError(
|
|
3780
|
+
"WORKBOOK_INVALID",
|
|
3781
|
+
`The locale "${sheet.locale}" cannot be an Excel worksheet name: it collides with the reserved "${INSTRUCTIONS_SHEET_NAME}" sheet.`
|
|
3782
|
+
);
|
|
3783
|
+
}
|
|
3784
|
+
if (seen.has(key)) {
|
|
3785
|
+
throw new ExchangeError(
|
|
3786
|
+
"WORKBOOK_INVALID",
|
|
3787
|
+
`The locale "${sheet.locale}" cannot be an Excel worksheet name: it collides with another target locale's sheet name.`
|
|
3788
|
+
);
|
|
3789
|
+
}
|
|
3790
|
+
seen.add(key);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3620
3793
|
async function buildDataSheet(workbook, sheet) {
|
|
3621
3794
|
assertValidWorksheetName(sheet.locale);
|
|
3622
3795
|
const worksheet = workbook.addWorksheet(sheet.locale);
|
|
@@ -3643,6 +3816,7 @@ function buildInstructionsSheet(workbook) {
|
|
|
3643
3816
|
sheet.getRow(1).font = { bold: true };
|
|
3644
3817
|
}
|
|
3645
3818
|
async function buildWorkbook(model) {
|
|
3819
|
+
assertNoWorksheetNameCollisions(model.sheets);
|
|
3646
3820
|
const workbook = new ExcelJS.Workbook();
|
|
3647
3821
|
buildInstructionsSheet(workbook);
|
|
3648
3822
|
for (const sheet of model.sheets) {
|
|
@@ -3743,9 +3917,10 @@ var rowSchema = z.object({
|
|
|
3743
3917
|
key: z.string().min(1),
|
|
3744
3918
|
source: z.string(),
|
|
3745
3919
|
currentTarget: z.string(),
|
|
3746
|
-
status: z.enum(["new", "changed"]),
|
|
3920
|
+
status: z.enum(["new", "changed", "unchanged"]),
|
|
3747
3921
|
sourceHash: z.string(),
|
|
3748
|
-
translation: z.string()
|
|
3922
|
+
translation: z.string(),
|
|
3923
|
+
context: z.string()
|
|
3749
3924
|
});
|
|
3750
3925
|
function cellString(cell) {
|
|
3751
3926
|
const value = cell.value;
|
|
@@ -3778,7 +3953,10 @@ function parseRow(sheet, row) {
|
|
|
3778
3953
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
3779
3954
|
status: cellString(row.getCell(COLUMN.status)),
|
|
3780
3955
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
3781
|
-
translation: cellString(row.getCell(COLUMN.translation))
|
|
3956
|
+
translation: cellString(row.getCell(COLUMN.translation)),
|
|
3957
|
+
// A workbook built before the Context column existed has no cell here; getCell auto-vivifies an
|
|
3958
|
+
// empty one, so cellString yields "" and the row still validates, keeping import backward-compatible.
|
|
3959
|
+
context: cellString(row.getCell(COLUMN.context))
|
|
3782
3960
|
};
|
|
3783
3961
|
const result = rowSchema.safeParse(candidate);
|
|
3784
3962
|
if (!result.success) {
|
|
@@ -3867,14 +4045,15 @@ function buildRows(source, target, baseline, includeUnchanged) {
|
|
|
3867
4045
|
currentTarget: target.entries.get(key)?.value ?? "",
|
|
3868
4046
|
status,
|
|
3869
4047
|
sourceHash: contentHash(sourceEntry),
|
|
3870
|
-
translation: ""
|
|
4048
|
+
translation: "",
|
|
4049
|
+
context: sourceEntry.description ?? ""
|
|
3871
4050
|
});
|
|
3872
4051
|
}
|
|
3873
4052
|
};
|
|
3874
4053
|
add(diff2.missing, "new");
|
|
3875
4054
|
add(diff2.changed, "changed");
|
|
3876
4055
|
if (includeUnchanged) {
|
|
3877
|
-
add(diff2.unchanged, "
|
|
4056
|
+
add(diff2.unchanged, "unchanged");
|
|
3878
4057
|
}
|
|
3879
4058
|
return [...rows].sort((a, b) => a.key < b.key ? -1 : 1);
|
|
3880
4059
|
}
|