@verbatra/sdk 0.4.3 → 0.4.4

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 CHANGED
@@ -86,7 +86,7 @@ Discovers and validates the configuration. With no arguments it searches upward
86
86
 
87
87
  ### `translate(input): Promise<RunSummary>`
88
88
 
89
- Runs the one-shot read, diff, translate, write flow over every target locale. `input` is `{ config, cwd?, dryRun?, prune?, generatePlurals? }`. With `dryRun: true` it reads, diffs, and reports without calling the provider or writing anything. `prune` and `generatePlurals` each override the matching config option for this run. Resolves to a `RunSummary` (`dryRun`, `locales`, `succeeded`, and `failed`); each locale summary lists `translated`, `generated`, `pruned`, `integrityMismatches`, and `notices`.
89
+ Runs the one-shot read, diff, translate, write flow over every target locale. `input` is `{ config, cwd?, dryRun?, prune?, generatePlurals? }`. With `dryRun: true` it reads, diffs, and reports without calling the provider or writing anything. `prune` and `generatePlurals` each override the matching config option for this run. Resolves to a `RunSummary` (`dryRun`, `locales`, `succeeded`, and `failed`); each locale summary lists `translated`, `generated`, `pruned`, `integrityMismatches`, `providerFailures`, and `notices`. `integrityMismatches` is a translation that came back and failed the placeholder-integrity check; `providerFailures` is a key withheld because the provider call itself failed (nothing was translated), with the secret-free failure code and message reported in `notices`.
90
90
 
91
91
  ```ts
92
92
  const preview = await translate({ config, dryRun: true });
package/dist/index.cjs CHANGED
@@ -1182,19 +1182,53 @@ function childMessages(element) {
1182
1182
  }
1183
1183
  return [];
1184
1184
  }
1185
- function collect(elements, add, state) {
1185
+ function addCount(target, token, count) {
1186
+ target.set(token, (target.get(token) ?? 0) + count);
1187
+ }
1188
+ function combineBranches(branchMultisets) {
1189
+ const combined = /* @__PURE__ */ new Map();
1190
+ const tokens = /* @__PURE__ */ new Set();
1191
+ for (const branch of branchMultisets) {
1192
+ for (const token of branch.keys()) {
1193
+ tokens.add(token);
1194
+ }
1195
+ }
1196
+ for (const token of tokens) {
1197
+ const minCount = Math.min(...branchMultisets.map((branch) => branch.get(token) ?? 0));
1198
+ if (minCount > 0) {
1199
+ combined.set(token, minCount);
1200
+ }
1201
+ }
1202
+ return combined;
1203
+ }
1204
+ function collect(elements, state) {
1205
+ const result = /* @__PURE__ */ new Map();
1186
1206
  for (const element of elements) {
1187
1207
  const token = tokenOf(element);
1188
1208
  if (token !== void 0) {
1189
- add(token);
1209
+ addCount(result, token, 1);
1190
1210
  }
1191
1211
  if (element.type === icuMessageformatParser.TYPE.plural) {
1192
1212
  state.isPlural = true;
1193
1213
  }
1194
- for (const child of childMessages(element)) {
1195
- collect(child, add, state);
1214
+ const branches = childMessages(element);
1215
+ if (branches.length > 0) {
1216
+ const branchMultisets = branches.map((branch) => collect(branch, state));
1217
+ for (const [branchToken, branchCount] of combineBranches(branchMultisets)) {
1218
+ addCount(result, branchToken, branchCount);
1219
+ }
1220
+ }
1221
+ }
1222
+ return result;
1223
+ }
1224
+ function flatten(multiset) {
1225
+ const flattened = [];
1226
+ for (const [token, count] of multiset) {
1227
+ for (let i = 0; i < count; i += 1) {
1228
+ flattened.push(token);
1196
1229
  }
1197
1230
  }
1231
+ return flattened;
1198
1232
  }
1199
1233
  function analyzeIcuValue(value) {
1200
1234
  if (!value.includes("{") && !value.includes("<")) {
@@ -1202,16 +1236,9 @@ function analyzeIcuValue(value) {
1202
1236
  }
1203
1237
  try {
1204
1238
  const ast = icuMessageformatParser.parse(value);
1205
- const placeholders = [];
1206
1239
  const state = { isPlural: false };
1207
- collect(
1208
- ast,
1209
- (token) => {
1210
- placeholders.push(token);
1211
- },
1212
- state
1213
- );
1214
- return { placeholders, isPlural: state.isPlural, valid: true };
1240
+ const multiset = collect(ast, state);
1241
+ return { placeholders: flatten(multiset), isPlural: state.isPlural, valid: true };
1215
1242
  } catch {
1216
1243
  return INVALID;
1217
1244
  }
@@ -1473,21 +1500,28 @@ function addEntries(ctx, prefix, node) {
1473
1500
  }
1474
1501
  }
1475
1502
  }
1476
- function addPathEntries(node, prefix, namespace, derive, out) {
1503
+ function addPathEntries(node, prefix, namespace, derive, out, claimedPaths) {
1477
1504
  for (const [key, value] of Object.entries(node)) {
1478
1505
  const path = prefix === "" ? key : `${prefix}.${key}`;
1506
+ if (claimedPaths.has(path)) {
1507
+ throw new AdapterError(
1508
+ "INVALID_STRUCTURE",
1509
+ "A dotted key and a nested key path resolve to the same path."
1510
+ );
1511
+ }
1512
+ claimedPaths.add(path);
1479
1513
  if (typeof value === "string") {
1480
1514
  const { placeholders, isPlural } = derive(key, value);
1481
1515
  out.set(path, { key: path, namespace, value, placeholders, isPlural });
1482
1516
  } else {
1483
- addPathEntries(value, path, namespace, derive, out);
1517
+ addPathEntries(value, path, namespace, derive, out, claimedPaths);
1484
1518
  }
1485
1519
  }
1486
1520
  }
1487
1521
  function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
1488
1522
  const out = /* @__PURE__ */ new Map();
1489
1523
  if (keyMode === "path-notation") {
1490
- addPathEntries(tree, "", namespace, derive, out);
1524
+ addPathEntries(tree, "", namespace, derive, out, /* @__PURE__ */ new Set());
1491
1525
  return out;
1492
1526
  }
1493
1527
  addEntries({ namespace, derive, out, claimed: /* @__PURE__ */ new Map() }, [], tree);
@@ -1609,21 +1643,20 @@ function messagesFromEntries(entries) {
1609
1643
  }
1610
1644
  return out;
1611
1645
  }
1646
+ function isEnoent(error) {
1647
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1648
+ }
1612
1649
  async function readDestinationPairs(filePath) {
1613
- let parsed;
1650
+ let content;
1614
1651
  try {
1615
- const outcome = await readBounded2(filePath);
1616
- if (outcome.kind !== "ok") {
1652
+ content = await readFileContent(filePath);
1653
+ } catch (error) {
1654
+ if (isEnoent(error)) {
1617
1655
  return null;
1618
1656
  }
1619
- parsed = JSON.parse(outcome.content);
1620
- } catch {
1621
- return null;
1622
- }
1623
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1624
- return null;
1657
+ throw error;
1625
1658
  }
1626
- return Object.entries(parsed);
1659
+ return Object.entries(parseArbObject(content));
1627
1660
  }
1628
1661
  async function buildArbWriteTree(entries, filePath) {
1629
1662
  const messages = messagesFromEntries(entries);
@@ -1724,6 +1757,20 @@ function createNextIntlJsonAdapter() {
1724
1757
  validateMessage: icuIsValid
1725
1758
  });
1726
1759
  }
1760
+ function assertNoDottedNestedKey(tree) {
1761
+ for (const [key, value] of Object.entries(tree)) {
1762
+ if (typeof value !== "object") {
1763
+ continue;
1764
+ }
1765
+ if (key.includes(".")) {
1766
+ throw new AdapterError(
1767
+ "MIXED_STRUCTURE",
1768
+ "A nested object key contains a literal dot, which is ambiguous with a dotted path."
1769
+ );
1770
+ }
1771
+ assertNoDottedNestedKey(value);
1772
+ }
1773
+ }
1727
1774
  function assertNotMixed(tree) {
1728
1775
  let hasNested = false;
1729
1776
  let hasFlatDottedKey = false;
@@ -1740,6 +1787,7 @@ function assertNotMixed(tree) {
1740
1787
  "The file mixes flat dotted keys with nested objects."
1741
1788
  );
1742
1789
  }
1790
+ assertNoDottedNestedKey(tree);
1743
1791
  }
1744
1792
  async function detectStyle(filePath) {
1745
1793
  let parsed;
@@ -2237,6 +2285,7 @@ function failureSummary(locale, error) {
2237
2285
  pruned: [],
2238
2286
  invalidIcuSource: [],
2239
2287
  integrityMismatches: [],
2288
+ providerFailures: [],
2240
2289
  generated: [],
2241
2290
  notices: [],
2242
2291
  error: describeError(error)
@@ -2248,6 +2297,29 @@ function partition(locales) {
2248
2297
  return { succeeded, failed };
2249
2298
  }
2250
2299
 
2300
+ // src/flow/batching.ts
2301
+ function chunk(items, size) {
2302
+ const chunks = [];
2303
+ for (let index = 0; index < items.length; index += size) {
2304
+ chunks.push(items.slice(index, index + size));
2305
+ }
2306
+ return chunks;
2307
+ }
2308
+ var GENERIC_PROVIDER_FAILURE_MESSAGE = "The provider call failed.";
2309
+ function classifyProviderFailure(error) {
2310
+ if (error instanceof ProviderError) {
2311
+ return { code: error.code, message: error.message };
2312
+ }
2313
+ return { code: "PROVIDER_CALL_FAILED", message: GENERIC_PROVIDER_FAILURE_MESSAGE };
2314
+ }
2315
+ function subBatchFailedNotice(count, error) {
2316
+ const { code, message } = classifyProviderFailure(error);
2317
+ return {
2318
+ code: "SUB_BATCH_FAILED",
2319
+ message: `A sub-batch of ${count} entries failed (${code}: ${message}) and was withheld; it will be retried next run.`
2320
+ };
2321
+ }
2322
+
2251
2323
  // src/flow/notices.ts
2252
2324
  function isNotice(value) {
2253
2325
  return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
@@ -2430,13 +2502,36 @@ async function generatePluralForms(context) {
2430
2502
  const plan = planPluralGeneration(context.source, context.targetLocale, context.format);
2431
2503
  const stale = staleItems(plan.items, context.baseline);
2432
2504
  if (stale.length === 0) {
2433
- return { accepted: [], withheld: [] };
2505
+ return { accepted: [], withheld: [], providerFailures: [], notices: [] };
2434
2506
  }
2435
- const entries = stale.map(syntheticEntry);
2436
- const result = await context.provider.translateBatch(buildRequest2(context, entries));
2437
2507
  const accepted = [];
2438
2508
  const withheld = [];
2439
- for (const item of stale) {
2509
+ const providerFailures = [];
2510
+ const notices = [];
2511
+ for (const batch of chunk(stale, context.maxBatchSize)) {
2512
+ const batchNotices = await runGenerationSubBatch(
2513
+ context,
2514
+ batch,
2515
+ accepted,
2516
+ withheld,
2517
+ providerFailures
2518
+ );
2519
+ notices.push(...batchNotices);
2520
+ }
2521
+ return { accepted, withheld, providerFailures, notices };
2522
+ }
2523
+ async function runGenerationSubBatch(context, batch, accepted, withheld, providerFailures) {
2524
+ let result;
2525
+ try {
2526
+ const entries = batch.map(syntheticEntry);
2527
+ result = await context.provider.translateBatch(buildRequest2(context, entries));
2528
+ } catch (error) {
2529
+ for (const item of batch) {
2530
+ providerFailures.push(item.targetKey);
2531
+ }
2532
+ return [subBatchFailedNotice(batch.length, error)];
2533
+ }
2534
+ for (const item of batch) {
2440
2535
  const value = result.values.get(item.targetKey);
2441
2536
  const integrity = result.integrity.get(item.targetKey);
2442
2537
  if (value !== void 0 && integrity?.matches === true) {
@@ -2449,7 +2544,7 @@ async function generatePluralForms(context) {
2449
2544
  withheld.push(item.targetKey);
2450
2545
  }
2451
2546
  }
2452
- return { accepted, withheld };
2547
+ return readNotices(result);
2453
2548
  }
2454
2549
 
2455
2550
  // src/flow/locale-run.ts
@@ -2499,6 +2594,7 @@ async function runLocale(params) {
2499
2594
  translated: toTranslate,
2500
2595
  generated: [],
2501
2596
  integrityMismatches: [],
2597
+ providerFailures: [],
2502
2598
  pruned,
2503
2599
  notices: sdkNotices
2504
2600
  }),
@@ -2508,12 +2604,14 @@ async function runLocale(params) {
2508
2604
  const entries = toTranslate.map((key) => params.source.entries.get(key)).filter((entry) => entry !== void 0);
2509
2605
  const accepted = /* @__PURE__ */ new Map();
2510
2606
  const integrityMismatches = [];
2607
+ const providerFailures = [];
2511
2608
  const subBatchNotices = await translateAndCheck(
2512
2609
  provider,
2513
2610
  params,
2514
2611
  entries,
2515
2612
  accepted,
2516
- integrityMismatches
2613
+ integrityMismatches,
2614
+ providerFailures
2517
2615
  );
2518
2616
  const merged = new Map(target.entries);
2519
2617
  for (const key of pruned) {
@@ -2537,8 +2635,18 @@ async function runLocale(params) {
2537
2635
  path
2538
2636
  );
2539
2637
  const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
2540
- const notices = [...pluralNotices, ...subBatchNotices];
2541
- const withheld = /* @__PURE__ */ new Set([...integrityMismatches, ...invalidIcuSource, ...generation.withheld]);
2638
+ const notices = [
2639
+ ...pluralNotices,
2640
+ ...subBatchNotices,
2641
+ ...generation.notices
2642
+ ];
2643
+ const withheld = /* @__PURE__ */ new Set([
2644
+ ...integrityMismatches,
2645
+ ...providerFailures,
2646
+ ...invalidIcuSource,
2647
+ ...generation.withheld,
2648
+ ...generation.providerFailures
2649
+ ]);
2542
2650
  return {
2543
2651
  summary: baseSummary({
2544
2652
  locale: params.targetLocale,
@@ -2549,6 +2657,8 @@ async function runLocale(params) {
2549
2657
  generated: generation.accepted.map((form) => form.targetKey).sort(),
2550
2658
  // Withheld generated forms surface alongside withheld translations: both failed integrity.
2551
2659
  integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
2660
+ // A generation sub-batch whose provider call itself threw is a provider failure, never integrity.
2661
+ providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
2552
2662
  pruned,
2553
2663
  notices
2554
2664
  }),
@@ -2557,7 +2667,7 @@ async function runLocale(params) {
2557
2667
  }
2558
2668
  async function runGeneration(params, provider) {
2559
2669
  if (!params.generatePlurals || provider.kind !== "llm") {
2560
- return { accepted: [], withheld: [] };
2670
+ return { accepted: [], withheld: [], providerFailures: [], notices: [] };
2561
2671
  }
2562
2672
  return generatePluralForms({
2563
2673
  source: params.source,
@@ -2568,7 +2678,8 @@ async function runGeneration(params, provider) {
2568
2678
  provider,
2569
2679
  glossary: params.glossary,
2570
2680
  tone: params.tone,
2571
- baseline: params.baseline
2681
+ baseline: params.baseline,
2682
+ maxBatchSize: params.maxBatchSize
2572
2683
  });
2573
2684
  }
2574
2685
  function pluralNoticeFor(params, merged) {
@@ -2590,27 +2701,35 @@ function baseSummary(parts) {
2590
2701
  pruned: parts.pruned,
2591
2702
  invalidIcuSource: parts.invalidIcuSource,
2592
2703
  integrityMismatches: parts.integrityMismatches,
2704
+ providerFailures: parts.providerFailures,
2593
2705
  generated: parts.generated,
2594
2706
  notices: parts.notices
2595
2707
  };
2596
2708
  }
2597
- async function translateAndCheck(provider, params, entries, accepted, integrityMismatches) {
2709
+ async function translateAndCheck(provider, params, entries, accepted, integrityMismatches, providerFailures) {
2598
2710
  const notices = [];
2599
2711
  for (const batch of chunk(entries, params.maxBatchSize)) {
2600
- const subNotices = await runSubBatch(provider, params, batch, accepted, integrityMismatches);
2712
+ const subNotices = await runSubBatch(
2713
+ provider,
2714
+ params,
2715
+ batch,
2716
+ accepted,
2717
+ integrityMismatches,
2718
+ providerFailures
2719
+ );
2601
2720
  notices.push(...subNotices);
2602
2721
  }
2603
2722
  return notices;
2604
2723
  }
2605
- async function runSubBatch(provider, params, batch, accepted, integrityMismatches) {
2724
+ async function runSubBatch(provider, params, batch, accepted, integrityMismatches, providerFailures) {
2606
2725
  let result;
2607
2726
  try {
2608
2727
  result = await provider.translateBatch(buildRequest3(params, batch));
2609
- } catch {
2728
+ } catch (error) {
2610
2729
  for (const entry of batch) {
2611
- integrityMismatches.push(entry.key);
2730
+ providerFailures.push(entry.key);
2612
2731
  }
2613
- return [subBatchFailedNotice(batch.length)];
2732
+ return [subBatchFailedNotice(batch.length, error)];
2614
2733
  }
2615
2734
  for (const entry of batch) {
2616
2735
  const value = result.values.get(entry.key);
@@ -2623,19 +2742,6 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
2623
2742
  }
2624
2743
  return readNotices(result);
2625
2744
  }
2626
- function subBatchFailedNotice(count) {
2627
- return {
2628
- code: "SUB_BATCH_FAILED",
2629
- message: `A sub-batch of ${count} entries failed and was withheld; it will be retried next run.`
2630
- };
2631
- }
2632
- function chunk(items, size) {
2633
- const chunks = [];
2634
- for (let index = 0; index < items.length; index += size) {
2635
- chunks.push(items.slice(index, index + size));
2636
- }
2637
- return chunks;
2638
- }
2639
2745
  function computeLockEntries(params, merged, withheld, generated) {
2640
2746
  const lockEntries = {};
2641
2747
  const sourceBaseKeys = sourcePluralBaseKeys(params.source);
@@ -2783,6 +2889,7 @@ var COLUMN_WIDTHS = {
2783
2889
  [COLUMN.status]: 12,
2784
2890
  [COLUMN.translation]: 50
2785
2891
  };
2892
+ var TEXT_NUMBER_FORMAT = "@";
2786
2893
  function styleHeader(sheet) {
2787
2894
  const header = sheet.getRow(HEADER_ROW);
2788
2895
  HEADERS.forEach((label, index) => {
@@ -2798,6 +2905,7 @@ function applyColumnGeometry(sheet) {
2798
2905
  sheet.getColumn(Number(column)).width = width;
2799
2906
  }
2800
2907
  sheet.getColumn(COLUMN.sourceHash).hidden = true;
2908
+ sheet.getColumn(COLUMN.translation).numFmt = TEXT_NUMBER_FORMAT;
2801
2909
  sheet.views = [{ state: "frozen", ySplit: HEADER_ROW }];
2802
2910
  }
2803
2911
  function writeRow(sheet, sheetRow) {
@@ -2806,7 +2914,9 @@ function writeRow(sheet, sheetRow) {
2806
2914
  row.getCell(COLUMN.source).value = sheetRow.source;
2807
2915
  row.getCell(COLUMN.current).value = sheetRow.currentTarget;
2808
2916
  row.getCell(COLUMN.status).value = sheetRow.status;
2809
- row.getCell(COLUMN.translation).value = sheetRow.translation === "" ? null : sheetRow.translation;
2917
+ const translationCell = row.getCell(COLUMN.translation);
2918
+ translationCell.numFmt = TEXT_NUMBER_FORMAT;
2919
+ translationCell.value = sheetRow.translation === "" ? null : sheetRow.translation;
2810
2920
  row.getCell(COLUMN.sourceHash).value = sheetRow.sourceHash;
2811
2921
  for (let column = COLUMN.key; column <= COLUMN.sourceHash; column += 1) {
2812
2922
  const cell = row.getCell(column);
@@ -3158,9 +3268,20 @@ function judge(row, sourceEntry, adapter) {
3158
3268
  }
3159
3269
  return void 0;
3160
3270
  }
3271
+ function trackBlankDrift(row, params, buckets) {
3272
+ const sourceEntry = params.source.entries.get(row.key);
3273
+ if (sourceEntry === void 0) {
3274
+ return;
3275
+ }
3276
+ const priorHash = params.baseline.get(row.key);
3277
+ if (priorHash !== void 0 && priorHash !== contentHash(sourceEntry)) {
3278
+ buckets.blankDrifted.add(row.key);
3279
+ }
3280
+ }
3161
3281
  function classifyRows(params, buckets) {
3162
3282
  for (const row of params.sheet.rows) {
3163
3283
  if (row.translation === "") {
3284
+ trackBlankDrift(row, params, buckets);
3164
3285
  continue;
3165
3286
  }
3166
3287
  if (isUnknownKey(row, params.source, params.target)) {
@@ -3179,9 +3300,20 @@ function classifyRows(params, buckets) {
3179
3300
  }
3180
3301
  }
3181
3302
  }
3303
+ function blankRowBaselineNotice(count) {
3304
+ return {
3305
+ code: "BLANK_ROW_BASELINE_RETAINED",
3306
+ message: `${count} row(s) were left blank for a key whose source changed since the row's baseline was recorded; the prior baseline was kept so the drift keeps being reported.`
3307
+ };
3308
+ }
3182
3309
  function importLocale(params) {
3183
3310
  const diff2 = diffResources(params.source, params.target, { baseline: params.baseline });
3184
- const buckets = { accepted: /* @__PURE__ */ new Map(), mismatches: [], withheld: /* @__PURE__ */ new Set() };
3311
+ const buckets = {
3312
+ accepted: /* @__PURE__ */ new Map(),
3313
+ mismatches: [],
3314
+ withheld: /* @__PURE__ */ new Set(),
3315
+ blankDrifted: /* @__PURE__ */ new Set()
3316
+ };
3185
3317
  classifyRows(params, buckets);
3186
3318
  const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
3187
3319
  const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
@@ -3195,9 +3327,11 @@ function importLocale(params) {
3195
3327
  pruned: [],
3196
3328
  invalidIcuSource,
3197
3329
  integrityMismatches: [...buckets.mismatches].sort(),
3330
+ // A workbook import never calls a provider, so a provider-call failure cannot occur here.
3331
+ providerFailures: [],
3198
3332
  // Plural generation is a translate-flow concern; the manual workbook import never generates forms.
3199
3333
  generated: [],
3200
- notices: []
3334
+ notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : []
3201
3335
  };
3202
3336
  return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
3203
3337
  }
@@ -3231,21 +3365,19 @@ function mergeAccepted(target, accepted) {
3231
3365
  }
3232
3366
  return merged;
3233
3367
  }
3234
- function computeLockEntries2(source, merged, baseline, withheld) {
3368
+ function computeLockEntries2(source, merged, baseline, accepted) {
3235
3369
  const entries = {};
3236
3370
  for (const key of merged.keys()) {
3237
3371
  const sourceEntry = source.entries.get(key);
3238
3372
  if (sourceEntry === void 0) {
3239
3373
  continue;
3240
3374
  }
3241
- if (withheld.has(key)) {
3242
- const prior = baseline.get(key);
3243
- if (prior !== void 0) {
3244
- entries[key] = prior;
3245
- }
3375
+ if (accepted.has(key)) {
3376
+ entries[key] = contentHash(sourceEntry);
3246
3377
  continue;
3247
3378
  }
3248
- entries[key] = contentHash(sourceEntry);
3379
+ const prior = baseline.get(key);
3380
+ entries[key] = prior !== void 0 ? prior : contentHash(sourceEntry);
3249
3381
  }
3250
3382
  return entries;
3251
3383
  }
@@ -3258,7 +3390,7 @@ async function runSheet(ctx, sheet, lock) {
3258
3390
  }
3259
3391
  const target = await readTarget4(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
3260
3392
  const baseline = baselineFor(lock, sheet.locale);
3261
- const { summary, accepted, withheld } = importLocale({
3393
+ const { summary, accepted } = importLocale({
3262
3394
  sheet,
3263
3395
  source: ctx.source,
3264
3396
  target,
@@ -3282,7 +3414,7 @@ async function runSheet(ctx, sheet, lock) {
3282
3414
  path
3283
3415
  );
3284
3416
  }
3285
- return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline, withheld) };
3417
+ return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted) };
3286
3418
  }
3287
3419
  async function importWorkbook(input, deps = {}) {
3288
3420
  const config = input.config;