@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 +1 -1
- package/dist/index.cjs +199 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +199 -67
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -709,7 +709,7 @@ interface DiffDeps {
|
|
|
709
709
|
declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
|
|
710
710
|
|
|
711
711
|
/** A stable code for an SDK-originated notice (not a provider notice). */
|
|
712
|
-
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED";
|
|
712
|
+
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED";
|
|
713
713
|
/**
|
|
714
714
|
* A notice raised by the SDK itself (not a provider), structurally identical to a {@link ProviderNotice}
|
|
715
715
|
* so both share the {@link LocaleSummary.notices} channel. Carries only a stable code and a static,
|
|
@@ -746,8 +746,20 @@ interface LocaleSummary {
|
|
|
746
746
|
readonly pruned: readonly string[];
|
|
747
747
|
/** Source keys flagged invalid-ICU that were skipped for translation this run. */
|
|
748
748
|
readonly invalidIcuSource: readonly string[];
|
|
749
|
-
/**
|
|
749
|
+
/**
|
|
750
|
+
* Translated keys that failed the placeholder-integrity check and were withheld. Never includes a
|
|
751
|
+
* key withheld because the provider call itself failed; see {@link LocaleSummary.providerFailures}
|
|
752
|
+
* for that case.
|
|
753
|
+
*/
|
|
750
754
|
readonly integrityMismatches: readonly string[];
|
|
755
|
+
/**
|
|
756
|
+
* Keys withheld because the provider call itself failed (for example a revoked API key, a rate
|
|
757
|
+
* limit, or a network timeout), as distinct from a translation that came back and failed the
|
|
758
|
+
* placeholder-integrity check. Nothing was translated for these keys. The corresponding
|
|
759
|
+
* {@link LocaleSummary.notices} entry carries the secret-free failure code and message. Empty in a
|
|
760
|
+
* dry-run and empty unless a provider call threw.
|
|
761
|
+
*/
|
|
762
|
+
readonly providerFailures: readonly string[];
|
|
751
763
|
/**
|
|
752
764
|
* Plural-category keys verbatra synthesized this run (for example a Polish `items_few` the source
|
|
753
765
|
* never supplied), kept distinct from {@link translated}. Empty unless plural generation was enabled
|
package/dist/index.d.ts
CHANGED
|
@@ -709,7 +709,7 @@ interface DiffDeps {
|
|
|
709
709
|
declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
|
|
710
710
|
|
|
711
711
|
/** A stable code for an SDK-originated notice (not a provider notice). */
|
|
712
|
-
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED";
|
|
712
|
+
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED";
|
|
713
713
|
/**
|
|
714
714
|
* A notice raised by the SDK itself (not a provider), structurally identical to a {@link ProviderNotice}
|
|
715
715
|
* so both share the {@link LocaleSummary.notices} channel. Carries only a stable code and a static,
|
|
@@ -746,8 +746,20 @@ interface LocaleSummary {
|
|
|
746
746
|
readonly pruned: readonly string[];
|
|
747
747
|
/** Source keys flagged invalid-ICU that were skipped for translation this run. */
|
|
748
748
|
readonly invalidIcuSource: readonly string[];
|
|
749
|
-
/**
|
|
749
|
+
/**
|
|
750
|
+
* Translated keys that failed the placeholder-integrity check and were withheld. Never includes a
|
|
751
|
+
* key withheld because the provider call itself failed; see {@link LocaleSummary.providerFailures}
|
|
752
|
+
* for that case.
|
|
753
|
+
*/
|
|
750
754
|
readonly integrityMismatches: readonly string[];
|
|
755
|
+
/**
|
|
756
|
+
* Keys withheld because the provider call itself failed (for example a revoked API key, a rate
|
|
757
|
+
* limit, or a network timeout), as distinct from a translation that came back and failed the
|
|
758
|
+
* placeholder-integrity check. Nothing was translated for these keys. The corresponding
|
|
759
|
+
* {@link LocaleSummary.notices} entry carries the secret-free failure code and message. Empty in a
|
|
760
|
+
* dry-run and empty unless a provider call threw.
|
|
761
|
+
*/
|
|
762
|
+
readonly providerFailures: readonly string[];
|
|
751
763
|
/**
|
|
752
764
|
* Plural-category keys verbatra synthesized this run (for example a Polish `items_few` the source
|
|
753
765
|
* never supplied), kept distinct from {@link translated}. Empty unless plural generation was enabled
|
package/dist/index.js
CHANGED
|
@@ -1152,19 +1152,53 @@ function childMessages(element) {
|
|
|
1152
1152
|
}
|
|
1153
1153
|
return [];
|
|
1154
1154
|
}
|
|
1155
|
-
function
|
|
1155
|
+
function addCount(target, token, count) {
|
|
1156
|
+
target.set(token, (target.get(token) ?? 0) + count);
|
|
1157
|
+
}
|
|
1158
|
+
function combineBranches(branchMultisets) {
|
|
1159
|
+
const combined = /* @__PURE__ */ new Map();
|
|
1160
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1161
|
+
for (const branch of branchMultisets) {
|
|
1162
|
+
for (const token of branch.keys()) {
|
|
1163
|
+
tokens.add(token);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
for (const token of tokens) {
|
|
1167
|
+
const minCount = Math.min(...branchMultisets.map((branch) => branch.get(token) ?? 0));
|
|
1168
|
+
if (minCount > 0) {
|
|
1169
|
+
combined.set(token, minCount);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return combined;
|
|
1173
|
+
}
|
|
1174
|
+
function collect(elements, state) {
|
|
1175
|
+
const result = /* @__PURE__ */ new Map();
|
|
1156
1176
|
for (const element of elements) {
|
|
1157
1177
|
const token = tokenOf(element);
|
|
1158
1178
|
if (token !== void 0) {
|
|
1159
|
-
|
|
1179
|
+
addCount(result, token, 1);
|
|
1160
1180
|
}
|
|
1161
1181
|
if (element.type === TYPE.plural) {
|
|
1162
1182
|
state.isPlural = true;
|
|
1163
1183
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1184
|
+
const branches = childMessages(element);
|
|
1185
|
+
if (branches.length > 0) {
|
|
1186
|
+
const branchMultisets = branches.map((branch) => collect(branch, state));
|
|
1187
|
+
for (const [branchToken, branchCount] of combineBranches(branchMultisets)) {
|
|
1188
|
+
addCount(result, branchToken, branchCount);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
function flatten(multiset) {
|
|
1195
|
+
const flattened = [];
|
|
1196
|
+
for (const [token, count] of multiset) {
|
|
1197
|
+
for (let i = 0; i < count; i += 1) {
|
|
1198
|
+
flattened.push(token);
|
|
1166
1199
|
}
|
|
1167
1200
|
}
|
|
1201
|
+
return flattened;
|
|
1168
1202
|
}
|
|
1169
1203
|
function analyzeIcuValue(value) {
|
|
1170
1204
|
if (!value.includes("{") && !value.includes("<")) {
|
|
@@ -1172,16 +1206,9 @@ function analyzeIcuValue(value) {
|
|
|
1172
1206
|
}
|
|
1173
1207
|
try {
|
|
1174
1208
|
const ast = parse$1(value);
|
|
1175
|
-
const placeholders = [];
|
|
1176
1209
|
const state = { isPlural: false };
|
|
1177
|
-
collect(
|
|
1178
|
-
|
|
1179
|
-
(token) => {
|
|
1180
|
-
placeholders.push(token);
|
|
1181
|
-
},
|
|
1182
|
-
state
|
|
1183
|
-
);
|
|
1184
|
-
return { placeholders, isPlural: state.isPlural, valid: true };
|
|
1210
|
+
const multiset = collect(ast, state);
|
|
1211
|
+
return { placeholders: flatten(multiset), isPlural: state.isPlural, valid: true };
|
|
1185
1212
|
} catch {
|
|
1186
1213
|
return INVALID;
|
|
1187
1214
|
}
|
|
@@ -1443,21 +1470,28 @@ function addEntries(ctx, prefix, node) {
|
|
|
1443
1470
|
}
|
|
1444
1471
|
}
|
|
1445
1472
|
}
|
|
1446
|
-
function addPathEntries(node, prefix, namespace, derive, out) {
|
|
1473
|
+
function addPathEntries(node, prefix, namespace, derive, out, claimedPaths) {
|
|
1447
1474
|
for (const [key, value] of Object.entries(node)) {
|
|
1448
1475
|
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
1476
|
+
if (claimedPaths.has(path)) {
|
|
1477
|
+
throw new AdapterError(
|
|
1478
|
+
"INVALID_STRUCTURE",
|
|
1479
|
+
"A dotted key and a nested key path resolve to the same path."
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
claimedPaths.add(path);
|
|
1449
1483
|
if (typeof value === "string") {
|
|
1450
1484
|
const { placeholders, isPlural } = derive(key, value);
|
|
1451
1485
|
out.set(path, { key: path, namespace, value, placeholders, isPlural });
|
|
1452
1486
|
} else {
|
|
1453
|
-
addPathEntries(value, path, namespace, derive, out);
|
|
1487
|
+
addPathEntries(value, path, namespace, derive, out, claimedPaths);
|
|
1454
1488
|
}
|
|
1455
1489
|
}
|
|
1456
1490
|
}
|
|
1457
1491
|
function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
|
|
1458
1492
|
const out = /* @__PURE__ */ new Map();
|
|
1459
1493
|
if (keyMode === "path-notation") {
|
|
1460
|
-
addPathEntries(tree, "", namespace, derive, out);
|
|
1494
|
+
addPathEntries(tree, "", namespace, derive, out, /* @__PURE__ */ new Set());
|
|
1461
1495
|
return out;
|
|
1462
1496
|
}
|
|
1463
1497
|
addEntries({ namespace, derive, out, claimed: /* @__PURE__ */ new Map() }, [], tree);
|
|
@@ -1579,21 +1613,20 @@ function messagesFromEntries(entries) {
|
|
|
1579
1613
|
}
|
|
1580
1614
|
return out;
|
|
1581
1615
|
}
|
|
1616
|
+
function isEnoent(error) {
|
|
1617
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1618
|
+
}
|
|
1582
1619
|
async function readDestinationPairs(filePath) {
|
|
1583
|
-
let
|
|
1620
|
+
let content;
|
|
1584
1621
|
try {
|
|
1585
|
-
|
|
1586
|
-
|
|
1622
|
+
content = await readFileContent(filePath);
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
if (isEnoent(error)) {
|
|
1587
1625
|
return null;
|
|
1588
1626
|
}
|
|
1589
|
-
|
|
1590
|
-
} catch {
|
|
1591
|
-
return null;
|
|
1592
|
-
}
|
|
1593
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1594
|
-
return null;
|
|
1627
|
+
throw error;
|
|
1595
1628
|
}
|
|
1596
|
-
return Object.entries(
|
|
1629
|
+
return Object.entries(parseArbObject(content));
|
|
1597
1630
|
}
|
|
1598
1631
|
async function buildArbWriteTree(entries, filePath) {
|
|
1599
1632
|
const messages = messagesFromEntries(entries);
|
|
@@ -1694,6 +1727,20 @@ function createNextIntlJsonAdapter() {
|
|
|
1694
1727
|
validateMessage: icuIsValid
|
|
1695
1728
|
});
|
|
1696
1729
|
}
|
|
1730
|
+
function assertNoDottedNestedKey(tree) {
|
|
1731
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
1732
|
+
if (typeof value !== "object") {
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
if (key.includes(".")) {
|
|
1736
|
+
throw new AdapterError(
|
|
1737
|
+
"MIXED_STRUCTURE",
|
|
1738
|
+
"A nested object key contains a literal dot, which is ambiguous with a dotted path."
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
assertNoDottedNestedKey(value);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1697
1744
|
function assertNotMixed(tree) {
|
|
1698
1745
|
let hasNested = false;
|
|
1699
1746
|
let hasFlatDottedKey = false;
|
|
@@ -1710,6 +1757,7 @@ function assertNotMixed(tree) {
|
|
|
1710
1757
|
"The file mixes flat dotted keys with nested objects."
|
|
1711
1758
|
);
|
|
1712
1759
|
}
|
|
1760
|
+
assertNoDottedNestedKey(tree);
|
|
1713
1761
|
}
|
|
1714
1762
|
async function detectStyle(filePath) {
|
|
1715
1763
|
let parsed;
|
|
@@ -2207,6 +2255,7 @@ function failureSummary(locale, error) {
|
|
|
2207
2255
|
pruned: [],
|
|
2208
2256
|
invalidIcuSource: [],
|
|
2209
2257
|
integrityMismatches: [],
|
|
2258
|
+
providerFailures: [],
|
|
2210
2259
|
generated: [],
|
|
2211
2260
|
notices: [],
|
|
2212
2261
|
error: describeError(error)
|
|
@@ -2218,6 +2267,29 @@ function partition(locales) {
|
|
|
2218
2267
|
return { succeeded, failed };
|
|
2219
2268
|
}
|
|
2220
2269
|
|
|
2270
|
+
// src/flow/batching.ts
|
|
2271
|
+
function chunk(items, size) {
|
|
2272
|
+
const chunks = [];
|
|
2273
|
+
for (let index = 0; index < items.length; index += size) {
|
|
2274
|
+
chunks.push(items.slice(index, index + size));
|
|
2275
|
+
}
|
|
2276
|
+
return chunks;
|
|
2277
|
+
}
|
|
2278
|
+
var GENERIC_PROVIDER_FAILURE_MESSAGE = "The provider call failed.";
|
|
2279
|
+
function classifyProviderFailure(error) {
|
|
2280
|
+
if (error instanceof ProviderError) {
|
|
2281
|
+
return { code: error.code, message: error.message };
|
|
2282
|
+
}
|
|
2283
|
+
return { code: "PROVIDER_CALL_FAILED", message: GENERIC_PROVIDER_FAILURE_MESSAGE };
|
|
2284
|
+
}
|
|
2285
|
+
function subBatchFailedNotice(count, error) {
|
|
2286
|
+
const { code, message } = classifyProviderFailure(error);
|
|
2287
|
+
return {
|
|
2288
|
+
code: "SUB_BATCH_FAILED",
|
|
2289
|
+
message: `A sub-batch of ${count} entries failed (${code}: ${message}) and was withheld; it will be retried next run.`
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2221
2293
|
// src/flow/notices.ts
|
|
2222
2294
|
function isNotice(value) {
|
|
2223
2295
|
return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
|
|
@@ -2400,13 +2472,36 @@ async function generatePluralForms(context) {
|
|
|
2400
2472
|
const plan = planPluralGeneration(context.source, context.targetLocale, context.format);
|
|
2401
2473
|
const stale = staleItems(plan.items, context.baseline);
|
|
2402
2474
|
if (stale.length === 0) {
|
|
2403
|
-
return { accepted: [], withheld: [] };
|
|
2475
|
+
return { accepted: [], withheld: [], providerFailures: [], notices: [] };
|
|
2404
2476
|
}
|
|
2405
|
-
const entries = stale.map(syntheticEntry);
|
|
2406
|
-
const result = await context.provider.translateBatch(buildRequest2(context, entries));
|
|
2407
2477
|
const accepted = [];
|
|
2408
2478
|
const withheld = [];
|
|
2409
|
-
|
|
2479
|
+
const providerFailures = [];
|
|
2480
|
+
const notices = [];
|
|
2481
|
+
for (const batch of chunk(stale, context.maxBatchSize)) {
|
|
2482
|
+
const batchNotices = await runGenerationSubBatch(
|
|
2483
|
+
context,
|
|
2484
|
+
batch,
|
|
2485
|
+
accepted,
|
|
2486
|
+
withheld,
|
|
2487
|
+
providerFailures
|
|
2488
|
+
);
|
|
2489
|
+
notices.push(...batchNotices);
|
|
2490
|
+
}
|
|
2491
|
+
return { accepted, withheld, providerFailures, notices };
|
|
2492
|
+
}
|
|
2493
|
+
async function runGenerationSubBatch(context, batch, accepted, withheld, providerFailures) {
|
|
2494
|
+
let result;
|
|
2495
|
+
try {
|
|
2496
|
+
const entries = batch.map(syntheticEntry);
|
|
2497
|
+
result = await context.provider.translateBatch(buildRequest2(context, entries));
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
for (const item of batch) {
|
|
2500
|
+
providerFailures.push(item.targetKey);
|
|
2501
|
+
}
|
|
2502
|
+
return [subBatchFailedNotice(batch.length, error)];
|
|
2503
|
+
}
|
|
2504
|
+
for (const item of batch) {
|
|
2410
2505
|
const value = result.values.get(item.targetKey);
|
|
2411
2506
|
const integrity = result.integrity.get(item.targetKey);
|
|
2412
2507
|
if (value !== void 0 && integrity?.matches === true) {
|
|
@@ -2419,7 +2514,7 @@ async function generatePluralForms(context) {
|
|
|
2419
2514
|
withheld.push(item.targetKey);
|
|
2420
2515
|
}
|
|
2421
2516
|
}
|
|
2422
|
-
return
|
|
2517
|
+
return readNotices(result);
|
|
2423
2518
|
}
|
|
2424
2519
|
|
|
2425
2520
|
// src/flow/locale-run.ts
|
|
@@ -2469,6 +2564,7 @@ async function runLocale(params) {
|
|
|
2469
2564
|
translated: toTranslate,
|
|
2470
2565
|
generated: [],
|
|
2471
2566
|
integrityMismatches: [],
|
|
2567
|
+
providerFailures: [],
|
|
2472
2568
|
pruned,
|
|
2473
2569
|
notices: sdkNotices
|
|
2474
2570
|
}),
|
|
@@ -2478,12 +2574,14 @@ async function runLocale(params) {
|
|
|
2478
2574
|
const entries = toTranslate.map((key) => params.source.entries.get(key)).filter((entry) => entry !== void 0);
|
|
2479
2575
|
const accepted = /* @__PURE__ */ new Map();
|
|
2480
2576
|
const integrityMismatches = [];
|
|
2577
|
+
const providerFailures = [];
|
|
2481
2578
|
const subBatchNotices = await translateAndCheck(
|
|
2482
2579
|
provider,
|
|
2483
2580
|
params,
|
|
2484
2581
|
entries,
|
|
2485
2582
|
accepted,
|
|
2486
|
-
integrityMismatches
|
|
2583
|
+
integrityMismatches,
|
|
2584
|
+
providerFailures
|
|
2487
2585
|
);
|
|
2488
2586
|
const merged = new Map(target.entries);
|
|
2489
2587
|
for (const key of pruned) {
|
|
@@ -2507,8 +2605,18 @@ async function runLocale(params) {
|
|
|
2507
2605
|
path
|
|
2508
2606
|
);
|
|
2509
2607
|
const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
|
|
2510
|
-
const notices = [
|
|
2511
|
-
|
|
2608
|
+
const notices = [
|
|
2609
|
+
...pluralNotices,
|
|
2610
|
+
...subBatchNotices,
|
|
2611
|
+
...generation.notices
|
|
2612
|
+
];
|
|
2613
|
+
const withheld = /* @__PURE__ */ new Set([
|
|
2614
|
+
...integrityMismatches,
|
|
2615
|
+
...providerFailures,
|
|
2616
|
+
...invalidIcuSource,
|
|
2617
|
+
...generation.withheld,
|
|
2618
|
+
...generation.providerFailures
|
|
2619
|
+
]);
|
|
2512
2620
|
return {
|
|
2513
2621
|
summary: baseSummary({
|
|
2514
2622
|
locale: params.targetLocale,
|
|
@@ -2519,6 +2627,8 @@ async function runLocale(params) {
|
|
|
2519
2627
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
2520
2628
|
// Withheld generated forms surface alongside withheld translations: both failed integrity.
|
|
2521
2629
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
2630
|
+
// A generation sub-batch whose provider call itself threw is a provider failure, never integrity.
|
|
2631
|
+
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
2522
2632
|
pruned,
|
|
2523
2633
|
notices
|
|
2524
2634
|
}),
|
|
@@ -2527,7 +2637,7 @@ async function runLocale(params) {
|
|
|
2527
2637
|
}
|
|
2528
2638
|
async function runGeneration(params, provider) {
|
|
2529
2639
|
if (!params.generatePlurals || provider.kind !== "llm") {
|
|
2530
|
-
return { accepted: [], withheld: [] };
|
|
2640
|
+
return { accepted: [], withheld: [], providerFailures: [], notices: [] };
|
|
2531
2641
|
}
|
|
2532
2642
|
return generatePluralForms({
|
|
2533
2643
|
source: params.source,
|
|
@@ -2538,7 +2648,8 @@ async function runGeneration(params, provider) {
|
|
|
2538
2648
|
provider,
|
|
2539
2649
|
glossary: params.glossary,
|
|
2540
2650
|
tone: params.tone,
|
|
2541
|
-
baseline: params.baseline
|
|
2651
|
+
baseline: params.baseline,
|
|
2652
|
+
maxBatchSize: params.maxBatchSize
|
|
2542
2653
|
});
|
|
2543
2654
|
}
|
|
2544
2655
|
function pluralNoticeFor(params, merged) {
|
|
@@ -2560,27 +2671,35 @@ function baseSummary(parts) {
|
|
|
2560
2671
|
pruned: parts.pruned,
|
|
2561
2672
|
invalidIcuSource: parts.invalidIcuSource,
|
|
2562
2673
|
integrityMismatches: parts.integrityMismatches,
|
|
2674
|
+
providerFailures: parts.providerFailures,
|
|
2563
2675
|
generated: parts.generated,
|
|
2564
2676
|
notices: parts.notices
|
|
2565
2677
|
};
|
|
2566
2678
|
}
|
|
2567
|
-
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches) {
|
|
2679
|
+
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches, providerFailures) {
|
|
2568
2680
|
const notices = [];
|
|
2569
2681
|
for (const batch of chunk(entries, params.maxBatchSize)) {
|
|
2570
|
-
const subNotices = await runSubBatch(
|
|
2682
|
+
const subNotices = await runSubBatch(
|
|
2683
|
+
provider,
|
|
2684
|
+
params,
|
|
2685
|
+
batch,
|
|
2686
|
+
accepted,
|
|
2687
|
+
integrityMismatches,
|
|
2688
|
+
providerFailures
|
|
2689
|
+
);
|
|
2571
2690
|
notices.push(...subNotices);
|
|
2572
2691
|
}
|
|
2573
2692
|
return notices;
|
|
2574
2693
|
}
|
|
2575
|
-
async function runSubBatch(provider, params, batch, accepted, integrityMismatches) {
|
|
2694
|
+
async function runSubBatch(provider, params, batch, accepted, integrityMismatches, providerFailures) {
|
|
2576
2695
|
let result;
|
|
2577
2696
|
try {
|
|
2578
2697
|
result = await provider.translateBatch(buildRequest3(params, batch));
|
|
2579
|
-
} catch {
|
|
2698
|
+
} catch (error) {
|
|
2580
2699
|
for (const entry of batch) {
|
|
2581
|
-
|
|
2700
|
+
providerFailures.push(entry.key);
|
|
2582
2701
|
}
|
|
2583
|
-
return [subBatchFailedNotice(batch.length)];
|
|
2702
|
+
return [subBatchFailedNotice(batch.length, error)];
|
|
2584
2703
|
}
|
|
2585
2704
|
for (const entry of batch) {
|
|
2586
2705
|
const value = result.values.get(entry.key);
|
|
@@ -2593,19 +2712,6 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
2593
2712
|
}
|
|
2594
2713
|
return readNotices(result);
|
|
2595
2714
|
}
|
|
2596
|
-
function subBatchFailedNotice(count) {
|
|
2597
|
-
return {
|
|
2598
|
-
code: "SUB_BATCH_FAILED",
|
|
2599
|
-
message: `A sub-batch of ${count} entries failed and was withheld; it will be retried next run.`
|
|
2600
|
-
};
|
|
2601
|
-
}
|
|
2602
|
-
function chunk(items, size) {
|
|
2603
|
-
const chunks = [];
|
|
2604
|
-
for (let index = 0; index < items.length; index += size) {
|
|
2605
|
-
chunks.push(items.slice(index, index + size));
|
|
2606
|
-
}
|
|
2607
|
-
return chunks;
|
|
2608
|
-
}
|
|
2609
2715
|
function computeLockEntries(params, merged, withheld, generated) {
|
|
2610
2716
|
const lockEntries = {};
|
|
2611
2717
|
const sourceBaseKeys = sourcePluralBaseKeys(params.source);
|
|
@@ -2753,6 +2859,7 @@ var COLUMN_WIDTHS = {
|
|
|
2753
2859
|
[COLUMN.status]: 12,
|
|
2754
2860
|
[COLUMN.translation]: 50
|
|
2755
2861
|
};
|
|
2862
|
+
var TEXT_NUMBER_FORMAT = "@";
|
|
2756
2863
|
function styleHeader(sheet) {
|
|
2757
2864
|
const header = sheet.getRow(HEADER_ROW);
|
|
2758
2865
|
HEADERS.forEach((label, index) => {
|
|
@@ -2768,6 +2875,7 @@ function applyColumnGeometry(sheet) {
|
|
|
2768
2875
|
sheet.getColumn(Number(column)).width = width;
|
|
2769
2876
|
}
|
|
2770
2877
|
sheet.getColumn(COLUMN.sourceHash).hidden = true;
|
|
2878
|
+
sheet.getColumn(COLUMN.translation).numFmt = TEXT_NUMBER_FORMAT;
|
|
2771
2879
|
sheet.views = [{ state: "frozen", ySplit: HEADER_ROW }];
|
|
2772
2880
|
}
|
|
2773
2881
|
function writeRow(sheet, sheetRow) {
|
|
@@ -2776,7 +2884,9 @@ function writeRow(sheet, sheetRow) {
|
|
|
2776
2884
|
row.getCell(COLUMN.source).value = sheetRow.source;
|
|
2777
2885
|
row.getCell(COLUMN.current).value = sheetRow.currentTarget;
|
|
2778
2886
|
row.getCell(COLUMN.status).value = sheetRow.status;
|
|
2779
|
-
row.getCell(COLUMN.translation)
|
|
2887
|
+
const translationCell = row.getCell(COLUMN.translation);
|
|
2888
|
+
translationCell.numFmt = TEXT_NUMBER_FORMAT;
|
|
2889
|
+
translationCell.value = sheetRow.translation === "" ? null : sheetRow.translation;
|
|
2780
2890
|
row.getCell(COLUMN.sourceHash).value = sheetRow.sourceHash;
|
|
2781
2891
|
for (let column = COLUMN.key; column <= COLUMN.sourceHash; column += 1) {
|
|
2782
2892
|
const cell = row.getCell(column);
|
|
@@ -3128,9 +3238,20 @@ function judge(row, sourceEntry, adapter) {
|
|
|
3128
3238
|
}
|
|
3129
3239
|
return void 0;
|
|
3130
3240
|
}
|
|
3241
|
+
function trackBlankDrift(row, params, buckets) {
|
|
3242
|
+
const sourceEntry = params.source.entries.get(row.key);
|
|
3243
|
+
if (sourceEntry === void 0) {
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
const priorHash = params.baseline.get(row.key);
|
|
3247
|
+
if (priorHash !== void 0 && priorHash !== contentHash(sourceEntry)) {
|
|
3248
|
+
buckets.blankDrifted.add(row.key);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3131
3251
|
function classifyRows(params, buckets) {
|
|
3132
3252
|
for (const row of params.sheet.rows) {
|
|
3133
3253
|
if (row.translation === "") {
|
|
3254
|
+
trackBlankDrift(row, params, buckets);
|
|
3134
3255
|
continue;
|
|
3135
3256
|
}
|
|
3136
3257
|
if (isUnknownKey(row, params.source, params.target)) {
|
|
@@ -3149,9 +3270,20 @@ function classifyRows(params, buckets) {
|
|
|
3149
3270
|
}
|
|
3150
3271
|
}
|
|
3151
3272
|
}
|
|
3273
|
+
function blankRowBaselineNotice(count) {
|
|
3274
|
+
return {
|
|
3275
|
+
code: "BLANK_ROW_BASELINE_RETAINED",
|
|
3276
|
+
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.`
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3152
3279
|
function importLocale(params) {
|
|
3153
3280
|
const diff2 = diffResources(params.source, params.target, { baseline: params.baseline });
|
|
3154
|
-
const buckets = {
|
|
3281
|
+
const buckets = {
|
|
3282
|
+
accepted: /* @__PURE__ */ new Map(),
|
|
3283
|
+
mismatches: [],
|
|
3284
|
+
withheld: /* @__PURE__ */ new Set(),
|
|
3285
|
+
blankDrifted: /* @__PURE__ */ new Set()
|
|
3286
|
+
};
|
|
3155
3287
|
classifyRows(params, buckets);
|
|
3156
3288
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
3157
3289
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
@@ -3165,9 +3297,11 @@ function importLocale(params) {
|
|
|
3165
3297
|
pruned: [],
|
|
3166
3298
|
invalidIcuSource,
|
|
3167
3299
|
integrityMismatches: [...buckets.mismatches].sort(),
|
|
3300
|
+
// A workbook import never calls a provider, so a provider-call failure cannot occur here.
|
|
3301
|
+
providerFailures: [],
|
|
3168
3302
|
// Plural generation is a translate-flow concern; the manual workbook import never generates forms.
|
|
3169
3303
|
generated: [],
|
|
3170
|
-
notices: []
|
|
3304
|
+
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : []
|
|
3171
3305
|
};
|
|
3172
3306
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
3173
3307
|
}
|
|
@@ -3201,21 +3335,19 @@ function mergeAccepted(target, accepted) {
|
|
|
3201
3335
|
}
|
|
3202
3336
|
return merged;
|
|
3203
3337
|
}
|
|
3204
|
-
function computeLockEntries2(source, merged, baseline,
|
|
3338
|
+
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
3205
3339
|
const entries = {};
|
|
3206
3340
|
for (const key of merged.keys()) {
|
|
3207
3341
|
const sourceEntry = source.entries.get(key);
|
|
3208
3342
|
if (sourceEntry === void 0) {
|
|
3209
3343
|
continue;
|
|
3210
3344
|
}
|
|
3211
|
-
if (
|
|
3212
|
-
|
|
3213
|
-
if (prior !== void 0) {
|
|
3214
|
-
entries[key] = prior;
|
|
3215
|
-
}
|
|
3345
|
+
if (accepted.has(key)) {
|
|
3346
|
+
entries[key] = contentHash(sourceEntry);
|
|
3216
3347
|
continue;
|
|
3217
3348
|
}
|
|
3218
|
-
|
|
3349
|
+
const prior = baseline.get(key);
|
|
3350
|
+
entries[key] = prior !== void 0 ? prior : contentHash(sourceEntry);
|
|
3219
3351
|
}
|
|
3220
3352
|
return entries;
|
|
3221
3353
|
}
|
|
@@ -3228,7 +3360,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
3228
3360
|
}
|
|
3229
3361
|
const target = await readTarget4(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
3230
3362
|
const baseline = baselineFor(lock, sheet.locale);
|
|
3231
|
-
const { summary, accepted
|
|
3363
|
+
const { summary, accepted } = importLocale({
|
|
3232
3364
|
sheet,
|
|
3233
3365
|
source: ctx.source,
|
|
3234
3366
|
target,
|
|
@@ -3252,7 +3384,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
3252
3384
|
path
|
|
3253
3385
|
);
|
|
3254
3386
|
}
|
|
3255
|
-
return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline,
|
|
3387
|
+
return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted) };
|
|
3256
3388
|
}
|
|
3257
3389
|
async function importWorkbook(input, deps = {}) {
|
|
3258
3390
|
const config = input.config;
|