@verbatra/sdk 0.5.0-next.2 → 0.5.0-next.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/dist/index.cjs +655 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -15
- package/dist/index.d.ts +112 -15
- package/dist/index.js +652 -66
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -502,6 +502,76 @@ function validateRequest(request) {
|
|
|
502
502
|
}
|
|
503
503
|
return parsed.data;
|
|
504
504
|
}
|
|
505
|
+
var LENGTH_RATIO_MIN = 0.35;
|
|
506
|
+
var LENGTH_RATIO_MAX = 3;
|
|
507
|
+
var LENGTH_RATIO_MIN_SOURCE_LENGTH = 12;
|
|
508
|
+
var UNICODE_LETTER = /\p{L}/u;
|
|
509
|
+
var DEGRADATION_NOTICE_CODES = /* @__PURE__ */ new Set([
|
|
510
|
+
"FORMALITY_DOWNGRADED",
|
|
511
|
+
"GLOSSARY_IGNORED"
|
|
512
|
+
]);
|
|
513
|
+
function isLengthRatioOutlier(sourceValue, translatedValue) {
|
|
514
|
+
const trimmedSource = sourceValue.trim();
|
|
515
|
+
if (trimmedSource.length < LENGTH_RATIO_MIN_SOURCE_LENGTH) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
const ratio = translatedValue.trim().length / trimmedSource.length;
|
|
519
|
+
return ratio < LENGTH_RATIO_MIN || ratio > LENGTH_RATIO_MAX;
|
|
520
|
+
}
|
|
521
|
+
function isEqualsSource(input) {
|
|
522
|
+
const trimmedSource = input.sourceValue.trim();
|
|
523
|
+
const trimmedTranslated = input.translatedValue.trim();
|
|
524
|
+
return trimmedTranslated === trimmedSource && input.targetLocale !== input.sourceLocale && UNICODE_LETTER.test(trimmedSource);
|
|
525
|
+
}
|
|
526
|
+
function isGlossaryTermMissed(input) {
|
|
527
|
+
const glossary = input.glossary;
|
|
528
|
+
if (glossary === void 0 || Object.keys(glossary).length === 0) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
const sourceLower = input.sourceValue.toLowerCase();
|
|
532
|
+
const translatedLower = input.translatedValue.toLowerCase();
|
|
533
|
+
for (const [sourceTerm, targetTerm] of Object.entries(glossary)) {
|
|
534
|
+
const sourceHit = sourceLower.includes(sourceTerm.toLowerCase());
|
|
535
|
+
const targetHit = translatedLower.includes(targetTerm.toLowerCase());
|
|
536
|
+
if (sourceHit && !targetHit) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
function isIntegrityReordered(integrity) {
|
|
543
|
+
return integrity.matches && integrity.reordered;
|
|
544
|
+
}
|
|
545
|
+
function computeReviewFlags(input) {
|
|
546
|
+
const reasons = [];
|
|
547
|
+
if (isLengthRatioOutlier(input.sourceValue, input.translatedValue)) {
|
|
548
|
+
reasons.push("LENGTH_RATIO_OUTLIER");
|
|
549
|
+
}
|
|
550
|
+
if (isEqualsSource(input)) {
|
|
551
|
+
reasons.push("EQUALS_SOURCE");
|
|
552
|
+
}
|
|
553
|
+
if (isGlossaryTermMissed(input)) {
|
|
554
|
+
reasons.push("GLOSSARY_TERM_MISSED");
|
|
555
|
+
}
|
|
556
|
+
if (isIntegrityReordered(input.integrity)) {
|
|
557
|
+
reasons.push("INTEGRITY_REORDERED");
|
|
558
|
+
}
|
|
559
|
+
return reasons.length > 0 ? { status: "review", reasons } : void 0;
|
|
560
|
+
}
|
|
561
|
+
function applyProviderDegraded(reviewFlags, notices, acceptedKeys) {
|
|
562
|
+
if (!notices.some((notice) => DEGRADATION_NOTICE_CODES.has(notice.code))) {
|
|
563
|
+
return reviewFlags;
|
|
564
|
+
}
|
|
565
|
+
const next = new Map(reviewFlags);
|
|
566
|
+
for (const key of acceptedKeys) {
|
|
567
|
+
const existing = next.get(key);
|
|
568
|
+
next.set(key, {
|
|
569
|
+
status: "review",
|
|
570
|
+
reasons: existing !== void 0 ? [...existing.reasons, "PROVIDER_DEGRADED"] : ["PROVIDER_DEGRADED"]
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return next;
|
|
574
|
+
}
|
|
505
575
|
function toIntegrityInputs(entries, values) {
|
|
506
576
|
const inputs = [];
|
|
507
577
|
for (const entry of entries) {
|
|
@@ -597,7 +667,33 @@ async function runLlmTranslation(request, mechanism) {
|
|
|
597
667
|
request.extractPlaceholders,
|
|
598
668
|
request.comparePlaceholders
|
|
599
669
|
);
|
|
600
|
-
|
|
670
|
+
const notices = [];
|
|
671
|
+
const reviewFlags = applyProviderDegraded(buildReviewFlags(data, values, integrity), notices, [
|
|
672
|
+
...values.keys()
|
|
673
|
+
]);
|
|
674
|
+
return usage === void 0 ? { values, integrity, notices, reviewFlags } : { values, integrity, usage, notices, reviewFlags };
|
|
675
|
+
}
|
|
676
|
+
function buildReviewFlags(data, values, integrity) {
|
|
677
|
+
const reviewFlags = /* @__PURE__ */ new Map();
|
|
678
|
+
for (const entry of data.entries) {
|
|
679
|
+
const translatedValue = values.get(entry.key);
|
|
680
|
+
const entryIntegrity = integrity.get(entry.key);
|
|
681
|
+
if (translatedValue === void 0 || entryIntegrity === void 0) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const flag = computeReviewFlags({
|
|
685
|
+
sourceValue: entry.value,
|
|
686
|
+
translatedValue,
|
|
687
|
+
sourceLocale: data.sourceLocale,
|
|
688
|
+
targetLocale: data.targetLocale,
|
|
689
|
+
integrity: entryIntegrity,
|
|
690
|
+
glossary: data.glossary
|
|
691
|
+
});
|
|
692
|
+
if (flag !== void 0) {
|
|
693
|
+
reviewFlags.set(entry.key, flag);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return reviewFlags;
|
|
601
697
|
}
|
|
602
698
|
async function requestTranslations(mechanism, data, signal) {
|
|
603
699
|
const payloadJson = JSON.stringify(buildDataPayload(data));
|
|
@@ -807,6 +903,50 @@ function createDefaultClient2() {
|
|
|
807
903
|
};
|
|
808
904
|
return { client, freeAccount };
|
|
809
905
|
}
|
|
906
|
+
var DEEPL_MAX_TEXTS_PER_REQUEST = 50;
|
|
907
|
+
var DEEPL_MAX_PAYLOAD_BYTES = 128 * 1024;
|
|
908
|
+
var DEEPL_PAYLOAD_OVERHEAD_RESERVE_BYTES = 4 * 1024;
|
|
909
|
+
var DEEPL_MAX_TEXT_PAYLOAD_BYTES = DEEPL_MAX_PAYLOAD_BYTES - DEEPL_PAYLOAD_OVERHEAD_RESERVE_BYTES;
|
|
910
|
+
function estimateWireBytes(text) {
|
|
911
|
+
return encodeURIComponent(text).length;
|
|
912
|
+
}
|
|
913
|
+
function chunkTextsForDeepL(texts) {
|
|
914
|
+
const chunks = [];
|
|
915
|
+
let current = [];
|
|
916
|
+
let currentBytes = 0;
|
|
917
|
+
for (const text of texts) {
|
|
918
|
+
const textBytes = estimateWireBytes(text);
|
|
919
|
+
const startsNewChunk = current.length > 0 && (current.length >= DEEPL_MAX_TEXTS_PER_REQUEST || currentBytes + textBytes > DEEPL_MAX_TEXT_PAYLOAD_BYTES);
|
|
920
|
+
if (startsNewChunk) {
|
|
921
|
+
chunks.push(current);
|
|
922
|
+
current = [];
|
|
923
|
+
currentBytes = 0;
|
|
924
|
+
}
|
|
925
|
+
current.push(text);
|
|
926
|
+
currentBytes += textBytes;
|
|
927
|
+
}
|
|
928
|
+
if (current.length > 0) {
|
|
929
|
+
chunks.push(current);
|
|
930
|
+
}
|
|
931
|
+
return chunks;
|
|
932
|
+
}
|
|
933
|
+
var DEPRECATED_BARE_TARGET_CODES = /* @__PURE__ */ new Set(["en", "pt"]);
|
|
934
|
+
function assertValidDeepLSourceLocale(locale) {
|
|
935
|
+
if (locale.includes("-")) {
|
|
936
|
+
throw new ProviderError(
|
|
937
|
+
"INVALID_REQUEST",
|
|
938
|
+
`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").`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function assertValidDeepLTargetLocale(locale) {
|
|
943
|
+
if (DEPRECATED_BARE_TARGET_CODES.has(locale.toLowerCase())) {
|
|
944
|
+
throw new ProviderError(
|
|
945
|
+
"INVALID_REQUEST",
|
|
946
|
+
`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).`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
810
950
|
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.";
|
|
811
951
|
function partitionByPlaceholders(entries) {
|
|
812
952
|
const protectable = [];
|
|
@@ -886,6 +1026,8 @@ function resolveClient(deps) {
|
|
|
886
1026
|
}
|
|
887
1027
|
async function translate(bundle, config, request) {
|
|
888
1028
|
const data = validateRequest(request);
|
|
1029
|
+
assertValidDeepLSourceLocale(data.sourceLocale);
|
|
1030
|
+
assertValidDeepLTargetLocale(data.targetLocale);
|
|
889
1031
|
const { protectable, unprotectable } = partitionByPlaceholders(data.entries);
|
|
890
1032
|
const genericGlossarySupplied = request.glossary !== void 0 && Object.keys(request.glossary).length > 0;
|
|
891
1033
|
const { options, notices } = buildTranslateOptions({
|
|
@@ -906,14 +1048,41 @@ async function translate(bundle, config, request) {
|
|
|
906
1048
|
if (unprotectable.length > 0) {
|
|
907
1049
|
notices.push({ code: "PLACEHOLDER_UNSUPPORTED", message: PLACEHOLDER_UNSUPPORTED_MESSAGE });
|
|
908
1050
|
}
|
|
909
|
-
|
|
1051
|
+
const reviewFlags = applyProviderDegraded(
|
|
1052
|
+
buildReviewFlags2(protectable, data, values, integrity, request.glossary),
|
|
1053
|
+
notices,
|
|
1054
|
+
[...values.keys()]
|
|
1055
|
+
);
|
|
1056
|
+
return { values, integrity, notices, reviewFlags };
|
|
1057
|
+
}
|
|
1058
|
+
function buildReviewFlags2(protectable, data, values, integrity, glossary) {
|
|
1059
|
+
const reviewFlags = /* @__PURE__ */ new Map();
|
|
1060
|
+
for (const entry of protectable) {
|
|
1061
|
+
const translatedValue = values.get(entry.key);
|
|
1062
|
+
const entryIntegrity = integrity.get(entry.key);
|
|
1063
|
+
if (translatedValue === void 0 || entryIntegrity === void 0) {
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
const flag = computeReviewFlags({
|
|
1067
|
+
sourceValue: entry.value,
|
|
1068
|
+
translatedValue,
|
|
1069
|
+
sourceLocale: data.sourceLocale,
|
|
1070
|
+
targetLocale: data.targetLocale,
|
|
1071
|
+
integrity: entryIntegrity,
|
|
1072
|
+
glossary
|
|
1073
|
+
});
|
|
1074
|
+
if (flag !== void 0) {
|
|
1075
|
+
reviewFlags.set(entry.key, flag);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return reviewFlags;
|
|
910
1079
|
}
|
|
911
1080
|
async function translateProtectable(client, data, protectable, options, extract, compare, signal) {
|
|
912
1081
|
if (protectable.length === 0) {
|
|
913
1082
|
return { values: /* @__PURE__ */ new Map(), integrity: /* @__PURE__ */ new Map() };
|
|
914
1083
|
}
|
|
915
1084
|
const texts = protectable.map((entry) => entry.value);
|
|
916
|
-
const results = await
|
|
1085
|
+
const results = await callClientChunked(
|
|
917
1086
|
client,
|
|
918
1087
|
texts,
|
|
919
1088
|
data.sourceLocale,
|
|
@@ -931,6 +1100,14 @@ function callClient2(client, texts, sourceLang, targetLang, options, signal) {
|
|
|
931
1100
|
signal
|
|
932
1101
|
);
|
|
933
1102
|
}
|
|
1103
|
+
async function callClientChunked(client, texts, sourceLang, targetLang, options, signal) {
|
|
1104
|
+
const results = [];
|
|
1105
|
+
for (const chunk2 of chunkTextsForDeepL(texts)) {
|
|
1106
|
+
const chunkResults = await callClient2(client, chunk2, sourceLang, targetLang, options, signal);
|
|
1107
|
+
results.push(...chunkResults);
|
|
1108
|
+
}
|
|
1109
|
+
return results;
|
|
1110
|
+
}
|
|
934
1111
|
var geminiConfigSchema = zod.z.object({
|
|
935
1112
|
model: zod.z.string().min(1),
|
|
936
1113
|
maxOutputTokens: zod.z.number().int().positive()
|
|
@@ -1402,6 +1579,18 @@ function buildProvider(config) {
|
|
|
1402
1579
|
|
|
1403
1580
|
// src/config/schema.ts
|
|
1404
1581
|
var DEFAULT_MAX_BATCH_SIZE = 50;
|
|
1582
|
+
var DEFAULT_BUDGET_BEHAVIOR = "warn";
|
|
1583
|
+
function findCaseInsensitiveDuplicate(locales) {
|
|
1584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1585
|
+
for (const locale of locales) {
|
|
1586
|
+
const key = locale.toLowerCase();
|
|
1587
|
+
if (seen.has(key)) {
|
|
1588
|
+
return locale;
|
|
1589
|
+
}
|
|
1590
|
+
seen.add(key);
|
|
1591
|
+
}
|
|
1592
|
+
return void 0;
|
|
1593
|
+
}
|
|
1405
1594
|
var verbatraConfigSchema = zod.z.strictObject({
|
|
1406
1595
|
sourceLocale: zod.z.string().min(1),
|
|
1407
1596
|
targetLocales: zod.z.array(zod.z.string().min(1)).min(1),
|
|
@@ -1439,10 +1628,34 @@ var verbatraConfigSchema = zod.z.strictObject({
|
|
|
1439
1628
|
* be a positive integer (zero, negative, or non-integer is rejected, never coerced). When absent,
|
|
1440
1629
|
* {@link DEFAULT_MAX_BATCH_SIZE} applies.
|
|
1441
1630
|
*/
|
|
1442
|
-
maxBatchSize: zod.z.number().int().positive().optional()
|
|
1631
|
+
maxBatchSize: zod.z.number().int().positive().optional(),
|
|
1632
|
+
/**
|
|
1633
|
+
* Optional whole-run token ceiling (input plus output tokens summed across every provider call, main
|
|
1634
|
+
* translation and plural generation alike, across all target locales). Checked after each completed
|
|
1635
|
+
* sub-batch, never mid-batch: the sub-batch whose completion crosses the ceiling is retained and
|
|
1636
|
+
* counted, since a call already in flight cannot be undone. Must be a positive integer. Config-only,
|
|
1637
|
+
* no CLI flag. Absent means no budget is enforced. Inert (never a false trip) against a token-less
|
|
1638
|
+
* provider such as DeepL, since it never reports usage to measure against the ceiling.
|
|
1639
|
+
*/
|
|
1640
|
+
maxTokens: zod.z.number().int().positive().optional(),
|
|
1641
|
+
/**
|
|
1642
|
+
* What happens once `maxTokens` is reached: `"warn"` (default) flags it and lets the run continue
|
|
1643
|
+
* unchanged; `"stop"` withholds every not-yet-attempted key for the rest of the run (the current
|
|
1644
|
+
* locale's remaining candidates and every later locale's), retried automatically next run. Present
|
|
1645
|
+
* without `maxTokens` is accepted and has no effect. Never changes the command's exit code.
|
|
1646
|
+
*/
|
|
1647
|
+
budgetBehavior: zod.z.enum(["warn", "stop"]).optional()
|
|
1443
1648
|
}).refine((config) => !config.targetLocales.includes(config.sourceLocale), {
|
|
1444
1649
|
message: "targetLocales must not include the source locale",
|
|
1445
1650
|
path: ["targetLocales"]
|
|
1651
|
+
}).refine((config) => findCaseInsensitiveDuplicate(config.targetLocales) === void 0, {
|
|
1652
|
+
error: (issue) => {
|
|
1653
|
+
const duplicate = findCaseInsensitiveDuplicate(
|
|
1654
|
+
issue.input.targetLocales
|
|
1655
|
+
);
|
|
1656
|
+
return `targetLocales must not contain case-insensitively duplicate locales: "${duplicate}"`;
|
|
1657
|
+
},
|
|
1658
|
+
path: ["targetLocales"]
|
|
1446
1659
|
}).refine((config) => config.files.pattern.includes(LOCALE_TOKEN), {
|
|
1447
1660
|
message: `files.pattern must contain the ${LOCALE_TOKEN} token`,
|
|
1448
1661
|
path: ["files", "pattern"]
|
|
@@ -1865,7 +2078,7 @@ var AdapterError = class extends Error {
|
|
|
1865
2078
|
var MAX_DEPTH = 100;
|
|
1866
2079
|
var MAX_INPUT_BYTES = 16 * 1024 * 1024;
|
|
1867
2080
|
var jsonTreeSchema = zod.z.lazy(
|
|
1868
|
-
() => zod.z.union([zod.z.string(), zod.z.record(zod.z.string(), jsonTreeSchema)])
|
|
2081
|
+
() => zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null(), zod.z.record(zod.z.string(), jsonTreeSchema)])
|
|
1869
2082
|
);
|
|
1870
2083
|
var rootSchema = zod.z.record(zod.z.string(), jsonTreeSchema);
|
|
1871
2084
|
function assertWithinDepth(value, max) {
|
|
@@ -1893,7 +2106,7 @@ function assertJsonRecord(value) {
|
|
|
1893
2106
|
if (!result.success) {
|
|
1894
2107
|
throw new AdapterError(
|
|
1895
2108
|
"INVALID_STRUCTURE",
|
|
1896
|
-
"The file is not a valid object (expected nested objects of string
|
|
2109
|
+
"The file is not a valid object (expected nested objects of string, number, boolean, or null leaves)."
|
|
1897
2110
|
);
|
|
1898
2111
|
}
|
|
1899
2112
|
return result.data;
|
|
@@ -1911,6 +2124,9 @@ function serializeJsonTree(tree) {
|
|
|
1911
2124
|
return `${JSON.stringify(tree, null, 2)}
|
|
1912
2125
|
`;
|
|
1913
2126
|
}
|
|
2127
|
+
function isJsonNode(value) {
|
|
2128
|
+
return typeof value === "object" && value !== null;
|
|
2129
|
+
}
|
|
1914
2130
|
function namespaceOf(filePath) {
|
|
1915
2131
|
return path.basename(filePath, path.extname(filePath));
|
|
1916
2132
|
}
|
|
@@ -1938,9 +2154,25 @@ function buildCanHandle(extensions, sniff) {
|
|
|
1938
2154
|
return sample === void 0 || sniff === void 0 || sniff(sample);
|
|
1939
2155
|
};
|
|
1940
2156
|
}
|
|
2157
|
+
async function fsyncPath(path) {
|
|
2158
|
+
const handle = await promises.open(path, "r");
|
|
2159
|
+
try {
|
|
2160
|
+
await handle.sync();
|
|
2161
|
+
} finally {
|
|
2162
|
+
await handle.close();
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
async function fsyncDirBestEffort(path) {
|
|
2166
|
+
try {
|
|
2167
|
+
await fsyncPath(path);
|
|
2168
|
+
} catch {
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
1941
2171
|
var nodeOps = {
|
|
1942
2172
|
writeFile: (path, data) => promises.writeFile(path, data, "utf8"),
|
|
2173
|
+
fsyncFile: (path) => fsyncPath(path),
|
|
1943
2174
|
rename: (from, to) => promises.rename(from, to),
|
|
2175
|
+
fsyncDir: (path) => fsyncDirBestEffort(path),
|
|
1944
2176
|
rm: (path) => promises.rm(path, { force: true })
|
|
1945
2177
|
};
|
|
1946
2178
|
async function cleanup(ops, tmp) {
|
|
@@ -1952,15 +2184,20 @@ async function cleanup(ops, tmp) {
|
|
|
1952
2184
|
function tempFileName2(path$1) {
|
|
1953
2185
|
return path.join(path.dirname(path$1), `.${path.basename(path$1)}.tmp-${process.pid}-${Date.now()}-${crypto.randomUUID()}`);
|
|
1954
2186
|
}
|
|
1955
|
-
async function atomicWriteFile(path, data, ops = nodeOps) {
|
|
1956
|
-
const tmp = tempFileName2(path);
|
|
2187
|
+
async function atomicWriteFile(path$1, data, ops = nodeOps) {
|
|
2188
|
+
const tmp = tempFileName2(path$1);
|
|
1957
2189
|
try {
|
|
1958
2190
|
await ops.writeFile(tmp, data);
|
|
1959
|
-
await ops.
|
|
2191
|
+
await ops.fsyncFile(tmp);
|
|
2192
|
+
await ops.rename(tmp, path$1);
|
|
1960
2193
|
} catch (error) {
|
|
1961
2194
|
await cleanup(ops, tmp);
|
|
1962
2195
|
throw error;
|
|
1963
2196
|
}
|
|
2197
|
+
try {
|
|
2198
|
+
await ops.fsyncDir(path.dirname(path$1));
|
|
2199
|
+
} catch {
|
|
2200
|
+
}
|
|
1964
2201
|
}
|
|
1965
2202
|
function stripLeadingBom(content) {
|
|
1966
2203
|
return content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
@@ -2082,20 +2319,24 @@ function addLeaf(ctx, segments, key, value) {
|
|
|
2082
2319
|
);
|
|
2083
2320
|
}
|
|
2084
2321
|
ctx.claimed.set(effectivePath, mapKey);
|
|
2322
|
+
if (typeof value !== "string") {
|
|
2323
|
+
ctx.excluded.push(mapKey);
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2085
2326
|
const { placeholders, isPlural } = ctx.derive(key, value);
|
|
2086
2327
|
ctx.out.set(mapKey, { key: mapKey, namespace: ctx.namespace, value, placeholders, isPlural });
|
|
2087
2328
|
}
|
|
2088
2329
|
function addEntries(ctx, prefix, node) {
|
|
2089
2330
|
for (const [key, value] of Object.entries(node)) {
|
|
2090
2331
|
const segments = [...prefix, key];
|
|
2091
|
-
if (
|
|
2092
|
-
addLeaf(ctx, segments, key, value);
|
|
2093
|
-
} else {
|
|
2332
|
+
if (isJsonNode(value)) {
|
|
2094
2333
|
addEntries(ctx, segments, value);
|
|
2334
|
+
} else {
|
|
2335
|
+
addLeaf(ctx, segments, key, value);
|
|
2095
2336
|
}
|
|
2096
2337
|
}
|
|
2097
2338
|
}
|
|
2098
|
-
function addPathEntries(node, prefix, namespace, derive, out, claimedPaths) {
|
|
2339
|
+
function addPathEntries(node, prefix, namespace, derive, out, claimedPaths, excluded) {
|
|
2099
2340
|
for (const [key, value] of Object.entries(node)) {
|
|
2100
2341
|
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
2101
2342
|
if (claimedPaths.has(path)) {
|
|
@@ -2105,22 +2346,26 @@ function addPathEntries(node, prefix, namespace, derive, out, claimedPaths) {
|
|
|
2105
2346
|
);
|
|
2106
2347
|
}
|
|
2107
2348
|
claimedPaths.add(path);
|
|
2108
|
-
if (
|
|
2349
|
+
if (isJsonNode(value)) {
|
|
2350
|
+
addPathEntries(value, path, namespace, derive, out, claimedPaths, excluded);
|
|
2351
|
+
} else if (typeof value === "string") {
|
|
2109
2352
|
const { placeholders, isPlural } = derive(key, value);
|
|
2110
2353
|
out.set(path, { key: path, namespace, value, placeholders, isPlural });
|
|
2111
2354
|
} else {
|
|
2112
|
-
|
|
2355
|
+
excluded.push(path);
|
|
2113
2356
|
}
|
|
2114
2357
|
}
|
|
2115
2358
|
}
|
|
2116
2359
|
function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
|
|
2117
2360
|
const out = /* @__PURE__ */ new Map();
|
|
2118
2361
|
if (keyMode === "path-notation") {
|
|
2119
|
-
|
|
2120
|
-
|
|
2362
|
+
const excluded = [];
|
|
2363
|
+
addPathEntries(tree, "", namespace, derive, out, /* @__PURE__ */ new Set(), excluded);
|
|
2364
|
+
return { entries: out, excludedLeafPaths: excluded };
|
|
2121
2365
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2366
|
+
const ctx = { namespace, derive, out, claimed: /* @__PURE__ */ new Map(), excluded: [] };
|
|
2367
|
+
addEntries(ctx, [], tree);
|
|
2368
|
+
return { entries: out, excludedLeafPaths: ctx.excluded };
|
|
2124
2369
|
}
|
|
2125
2370
|
function emptyNode() {
|
|
2126
2371
|
return /* @__PURE__ */ Object.create(null);
|
|
@@ -2158,11 +2403,24 @@ function unflattenEntries(entries) {
|
|
|
2158
2403
|
}
|
|
2159
2404
|
return root;
|
|
2160
2405
|
}
|
|
2161
|
-
function
|
|
2406
|
+
function mergeDescriptions(entries, content, deriveDescriptions) {
|
|
2407
|
+
if (!deriveDescriptions) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
for (const [key, description] of deriveDescriptions(content)) {
|
|
2411
|
+
const entry = entries.get(key);
|
|
2412
|
+
if (entry !== void 0) {
|
|
2413
|
+
entries.set(key, { ...entry, description });
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
function toEntries(content, namespace, parse3, deriveEntry, keyMode, validateTree, deriveDescriptions) {
|
|
2162
2418
|
try {
|
|
2163
2419
|
const tree = parse3(content);
|
|
2164
2420
|
validateTree?.(tree);
|
|
2165
|
-
|
|
2421
|
+
const result = flattenTree(tree, namespace, deriveEntry, keyMode);
|
|
2422
|
+
mergeDescriptions(result.entries, content, deriveDescriptions);
|
|
2423
|
+
return result;
|
|
2166
2424
|
} catch (error) {
|
|
2167
2425
|
rethrowStructured(error, "The file could not be parsed.");
|
|
2168
2426
|
}
|
|
@@ -2181,6 +2439,7 @@ function createTreeFileAdapter(options) {
|
|
|
2181
2439
|
validateTree,
|
|
2182
2440
|
buildWriteTree,
|
|
2183
2441
|
comparePlaceholders,
|
|
2442
|
+
deriveDescriptions,
|
|
2184
2443
|
keyMode = "literal-leaf"
|
|
2185
2444
|
} = options;
|
|
2186
2445
|
return {
|
|
@@ -2192,10 +2451,18 @@ function createTreeFileAdapter(options) {
|
|
|
2192
2451
|
async read(filePath, locale) {
|
|
2193
2452
|
const content = await readFileContent(filePath);
|
|
2194
2453
|
const namespace = namespaceOf(filePath);
|
|
2195
|
-
const entries = toEntries(
|
|
2454
|
+
const { entries, excludedLeafPaths } = toEntries(
|
|
2455
|
+
content,
|
|
2456
|
+
namespace,
|
|
2457
|
+
parse3,
|
|
2458
|
+
deriveEntry,
|
|
2459
|
+
keyMode,
|
|
2460
|
+
validateTree,
|
|
2461
|
+
deriveDescriptions
|
|
2462
|
+
);
|
|
2196
2463
|
const resource = { locale, namespace, format, entries };
|
|
2197
2464
|
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
2198
|
-
return { resource, invalidIcuKeys };
|
|
2465
|
+
return { resource, invalidIcuKeys, excludedLeafPaths };
|
|
2199
2466
|
},
|
|
2200
2467
|
async write(resource, filePath) {
|
|
2201
2468
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
@@ -2233,6 +2500,31 @@ function stripArbMetadata(tree) {
|
|
|
2233
2500
|
function originalKey(encoded) {
|
|
2234
2501
|
return decodeKeyToSegments(encoded).join(".");
|
|
2235
2502
|
}
|
|
2503
|
+
function messageKeyForMetadata(key) {
|
|
2504
|
+
return isMetadataKey(key) && !key.startsWith("@@") ? key.slice(1) : null;
|
|
2505
|
+
}
|
|
2506
|
+
function descriptionOf(value) {
|
|
2507
|
+
if (typeof value !== "object" || value === null) {
|
|
2508
|
+
return void 0;
|
|
2509
|
+
}
|
|
2510
|
+
const description = value.description;
|
|
2511
|
+
return typeof description === "string" ? description : void 0;
|
|
2512
|
+
}
|
|
2513
|
+
function extractArbDescriptions(content) {
|
|
2514
|
+
const tree = parseArbObject(content);
|
|
2515
|
+
const out = /* @__PURE__ */ new Map();
|
|
2516
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
2517
|
+
const messageKey = messageKeyForMetadata(key);
|
|
2518
|
+
if (messageKey === null) {
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2521
|
+
const description = descriptionOf(value);
|
|
2522
|
+
if (description !== void 0) {
|
|
2523
|
+
out.set(encodeSegment(messageKey), description);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
return out;
|
|
2527
|
+
}
|
|
2236
2528
|
function messagesFromEntries(entries) {
|
|
2237
2529
|
const out = /* @__PURE__ */ new Map();
|
|
2238
2530
|
for (const [key, entry] of entries) {
|
|
@@ -2264,8 +2556,10 @@ async function buildArbWriteTree(entries, filePath) {
|
|
|
2264
2556
|
const translated = isMetadataKey(key) ? void 0 : messages.get(key);
|
|
2265
2557
|
if (translated !== void 0) {
|
|
2266
2558
|
consumed.add(key);
|
|
2559
|
+
out[key] = translated;
|
|
2560
|
+
} else if (isMetadataKey(key) || typeof value === "string") {
|
|
2561
|
+
out[key] = value;
|
|
2267
2562
|
}
|
|
2268
|
-
out[key] = translated ?? value;
|
|
2269
2563
|
}
|
|
2270
2564
|
for (const [key, value] of messages) {
|
|
2271
2565
|
if (!consumed.has(key)) {
|
|
@@ -2289,7 +2583,8 @@ function createArbAdapter() {
|
|
|
2289
2583
|
computeInvalidIcuKeys: icuInvalidKeys,
|
|
2290
2584
|
validateMessage: icuIsValid,
|
|
2291
2585
|
comparePlaceholders: compareIcuPlaceholders,
|
|
2292
|
-
buildWriteTree: buildArbWriteTree
|
|
2586
|
+
buildWriteTree: buildArbWriteTree,
|
|
2587
|
+
deriveDescriptions: extractArbDescriptions
|
|
2293
2588
|
});
|
|
2294
2589
|
}
|
|
2295
2590
|
function createJsonFileAdapter(options) {
|
|
@@ -2358,7 +2653,7 @@ function createNextIntlJsonAdapter() {
|
|
|
2358
2653
|
}
|
|
2359
2654
|
function assertNoDottedNestedKey(tree) {
|
|
2360
2655
|
for (const [key, value] of Object.entries(tree)) {
|
|
2361
|
-
if (
|
|
2656
|
+
if (!isJsonNode(value)) {
|
|
2362
2657
|
continue;
|
|
2363
2658
|
}
|
|
2364
2659
|
if (key.includes(".")) {
|
|
@@ -2374,7 +2669,7 @@ function assertNotMixed(tree) {
|
|
|
2374
2669
|
let hasNested = false;
|
|
2375
2670
|
let hasFlatDottedKey = false;
|
|
2376
2671
|
for (const [key, value] of Object.entries(tree)) {
|
|
2377
|
-
if (
|
|
2672
|
+
if (isJsonNode(value)) {
|
|
2378
2673
|
hasNested = true;
|
|
2379
2674
|
} else if (key.includes(".")) {
|
|
2380
2675
|
hasFlatDottedKey = true;
|
|
@@ -2535,7 +2830,7 @@ function createFlatFileAdapter(options) {
|
|
|
2535
2830
|
const entries = toEntries2(content, namespace, parseEntries);
|
|
2536
2831
|
const resource = { locale, namespace, format, entries };
|
|
2537
2832
|
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
2538
|
-
return { resource, invalidIcuKeys };
|
|
2833
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
2539
2834
|
},
|
|
2540
2835
|
async write(resource, filePath) {
|
|
2541
2836
|
const data = await serializeEntries(resource.entries, filePath);
|
|
@@ -2595,16 +2890,33 @@ function parseXml(content) {
|
|
|
2595
2890
|
}
|
|
2596
2891
|
return { doc, root };
|
|
2597
2892
|
}
|
|
2893
|
+
function firstNoteText(parent) {
|
|
2894
|
+
const note = childByName(parent, "note");
|
|
2895
|
+
if (note === null) {
|
|
2896
|
+
return void 0;
|
|
2897
|
+
}
|
|
2898
|
+
const text = (note.textContent ?? "").trim();
|
|
2899
|
+
return text === "" ? void 0 : text;
|
|
2900
|
+
}
|
|
2901
|
+
function transUnitDescription(tu) {
|
|
2902
|
+
return firstNoteText(tu);
|
|
2903
|
+
}
|
|
2904
|
+
function unitDescription(unit) {
|
|
2905
|
+
const notes = childByName(unit, "notes");
|
|
2906
|
+
return notes === null ? void 0 : firstNoteText(notes);
|
|
2907
|
+
}
|
|
2598
2908
|
function walkXliff12(root) {
|
|
2599
2909
|
const units = [];
|
|
2600
2910
|
collectByTag(root, "trans-unit").forEach((tu, index) => {
|
|
2601
2911
|
const source = childByName(tu, "source");
|
|
2602
2912
|
if (source !== null) {
|
|
2913
|
+
const description = transUnitDescription(tu);
|
|
2603
2914
|
units.push({
|
|
2604
2915
|
key: unitKey(tu, index),
|
|
2605
2916
|
source,
|
|
2606
2917
|
target: childByName(tu, "target"),
|
|
2607
|
-
container: tu
|
|
2918
|
+
container: tu,
|
|
2919
|
+
...description !== void 0 ? { description } : {}
|
|
2608
2920
|
});
|
|
2609
2921
|
}
|
|
2610
2922
|
});
|
|
@@ -2614,12 +2926,19 @@ function walkXliff20(root) {
|
|
|
2614
2926
|
const units = [];
|
|
2615
2927
|
collectByTag(root, "unit").forEach((unit, index) => {
|
|
2616
2928
|
const baseKey = unitKey(unit, index);
|
|
2929
|
+
const description = unitDescription(unit);
|
|
2617
2930
|
const segments = elementChildren(unit).filter((el) => el.localName === "segment");
|
|
2618
2931
|
segments.forEach((segment, segIndex) => {
|
|
2619
2932
|
const source = childByName(segment, "source");
|
|
2620
2933
|
if (source !== null) {
|
|
2621
2934
|
const key = segments.length > 1 ? `${baseKey}#${segIndex}` : baseKey;
|
|
2622
|
-
units.push({
|
|
2935
|
+
units.push({
|
|
2936
|
+
key,
|
|
2937
|
+
source,
|
|
2938
|
+
target: childByName(segment, "target"),
|
|
2939
|
+
container: segment,
|
|
2940
|
+
...description !== void 0 ? { description } : {}
|
|
2941
|
+
});
|
|
2623
2942
|
}
|
|
2624
2943
|
});
|
|
2625
2944
|
});
|
|
@@ -2658,7 +2977,8 @@ function parseXliffEntries(content, namespace) {
|
|
|
2658
2977
|
namespace,
|
|
2659
2978
|
value,
|
|
2660
2979
|
placeholders: extractXliffPlaceholders(value),
|
|
2661
|
-
isPlural: false
|
|
2980
|
+
isPlural: false,
|
|
2981
|
+
...unit.description !== void 0 ? { description: unit.description } : {}
|
|
2662
2982
|
});
|
|
2663
2983
|
}
|
|
2664
2984
|
return out;
|
|
@@ -2964,6 +3284,46 @@ function selectProvider(config, createProvider = buildProvider) {
|
|
|
2964
3284
|
}
|
|
2965
3285
|
}
|
|
2966
3286
|
|
|
3287
|
+
// src/flow/budget.ts
|
|
3288
|
+
function createBudgetTracker(maxTokens, behavior) {
|
|
3289
|
+
return { maxTokens, behavior, tokensUsed: 0, usageSeen: false, exceeded: false, stopped: false };
|
|
3290
|
+
}
|
|
3291
|
+
function foldTrackerUsage(tracker, usage) {
|
|
3292
|
+
if (usage === void 0) {
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
tracker.usageSeen = true;
|
|
3296
|
+
tracker.tokensUsed += usage.inputTokens + usage.outputTokens;
|
|
3297
|
+
}
|
|
3298
|
+
function checkBudgetTrip(tracker) {
|
|
3299
|
+
if (tracker.maxTokens === void 0 || tracker.exceeded || tracker.tokensUsed < tracker.maxTokens) {
|
|
3300
|
+
return false;
|
|
3301
|
+
}
|
|
3302
|
+
tracker.exceeded = true;
|
|
3303
|
+
if (tracker.behavior === "stop") {
|
|
3304
|
+
tracker.stopped = true;
|
|
3305
|
+
}
|
|
3306
|
+
return true;
|
|
3307
|
+
}
|
|
3308
|
+
function toBudgetSummary(tracker) {
|
|
3309
|
+
if (tracker.maxTokens === void 0) {
|
|
3310
|
+
return void 0;
|
|
3311
|
+
}
|
|
3312
|
+
return {
|
|
3313
|
+
maxTokens: tracker.maxTokens,
|
|
3314
|
+
behavior: tracker.behavior,
|
|
3315
|
+
supported: tracker.usageSeen,
|
|
3316
|
+
tokensUsed: tracker.tokensUsed,
|
|
3317
|
+
exceeded: tracker.exceeded
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
function budgetExceededNotice(tracker) {
|
|
3321
|
+
return {
|
|
3322
|
+
code: "BUDGET_TOKENS_EXCEEDED",
|
|
3323
|
+
message: `The run's cumulative token usage (${tracker.tokensUsed}) reached the configured budget of ${tracker.maxTokens} tokens (behavior: ${tracker.behavior}).`
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
|
|
2967
3327
|
// src/flow/locale-failure.ts
|
|
2968
3328
|
function describeError(error) {
|
|
2969
3329
|
if (error instanceof Error) {
|
|
@@ -2983,8 +3343,10 @@ function failureSummary(locale, error) {
|
|
|
2983
3343
|
invalidIcuSource: [],
|
|
2984
3344
|
integrityMismatches: [],
|
|
2985
3345
|
providerFailures: [],
|
|
3346
|
+
budgetWithheld: [],
|
|
2986
3347
|
generated: [],
|
|
2987
3348
|
notices: [],
|
|
3349
|
+
needsReview: [],
|
|
2988
3350
|
error: describeError(error)
|
|
2989
3351
|
};
|
|
2990
3352
|
}
|
|
@@ -3154,7 +3516,43 @@ function planPluralGeneration(source, targetLocale, format) {
|
|
|
3154
3516
|
return { items };
|
|
3155
3517
|
}
|
|
3156
3518
|
|
|
3519
|
+
// src/flow/usage.ts
|
|
3520
|
+
function createUsageAccumulator() {
|
|
3521
|
+
return { total: void 0 };
|
|
3522
|
+
}
|
|
3523
|
+
function foldUsage(accumulator, usage) {
|
|
3524
|
+
if (usage === void 0) {
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
const prior = accumulator.total ?? { inputTokens: 0, outputTokens: 0 };
|
|
3528
|
+
accumulator.total = {
|
|
3529
|
+
inputTokens: prior.inputTokens + usage.inputTokens,
|
|
3530
|
+
outputTokens: prior.outputTokens + usage.outputTokens
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
function combineUsage(a, b) {
|
|
3534
|
+
if (a === void 0) {
|
|
3535
|
+
return b;
|
|
3536
|
+
}
|
|
3537
|
+
if (b === void 0) {
|
|
3538
|
+
return a;
|
|
3539
|
+
}
|
|
3540
|
+
return {
|
|
3541
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
3542
|
+
outputTokens: a.outputTokens + b.outputTokens
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3157
3546
|
// src/flow/plural-generation.ts
|
|
3547
|
+
var EMPTY_RESULT = {
|
|
3548
|
+
accepted: [],
|
|
3549
|
+
withheld: [],
|
|
3550
|
+
providerFailures: [],
|
|
3551
|
+
budgetWithheld: [],
|
|
3552
|
+
notices: [],
|
|
3553
|
+
usage: void 0,
|
|
3554
|
+
tripped: false
|
|
3555
|
+
};
|
|
3158
3556
|
function generatedLockHash(governingEntries, category) {
|
|
3159
3557
|
const governingHashes = governingEntries.map(contentHash).sort();
|
|
3160
3558
|
return contentHash({
|
|
@@ -3193,23 +3591,45 @@ async function generatePluralForms(context) {
|
|
|
3193
3591
|
const plan = planPluralGeneration(context.source, context.targetLocale, context.format);
|
|
3194
3592
|
const stale = staleItems(plan.items, context.baseline);
|
|
3195
3593
|
if (stale.length === 0) {
|
|
3196
|
-
return
|
|
3594
|
+
return EMPTY_RESULT;
|
|
3197
3595
|
}
|
|
3198
3596
|
const accepted = [];
|
|
3199
3597
|
const withheld = [];
|
|
3200
3598
|
const providerFailures = [];
|
|
3599
|
+
const budgetWithheld = [];
|
|
3201
3600
|
const notices = [];
|
|
3601
|
+
const usage = createUsageAccumulator();
|
|
3602
|
+
let tripped = false;
|
|
3202
3603
|
for (const batch of chunk(stale, context.maxBatchSize)) {
|
|
3203
|
-
|
|
3604
|
+
if (context.budget.stopped) {
|
|
3605
|
+
for (const item of batch) {
|
|
3606
|
+
budgetWithheld.push(item.targetKey);
|
|
3607
|
+
}
|
|
3608
|
+
continue;
|
|
3609
|
+
}
|
|
3610
|
+
const subResult = await runGenerationSubBatch(
|
|
3204
3611
|
context,
|
|
3205
3612
|
batch,
|
|
3206
3613
|
accepted,
|
|
3207
3614
|
withheld,
|
|
3208
3615
|
providerFailures
|
|
3209
3616
|
);
|
|
3210
|
-
notices.push(...
|
|
3617
|
+
notices.push(...subResult.notices);
|
|
3618
|
+
foldUsage(usage, subResult.usage);
|
|
3619
|
+
foldTrackerUsage(context.budget, subResult.usage);
|
|
3620
|
+
if (checkBudgetTrip(context.budget)) {
|
|
3621
|
+
tripped = true;
|
|
3622
|
+
}
|
|
3211
3623
|
}
|
|
3212
|
-
return {
|
|
3624
|
+
return {
|
|
3625
|
+
accepted,
|
|
3626
|
+
withheld,
|
|
3627
|
+
providerFailures,
|
|
3628
|
+
budgetWithheld,
|
|
3629
|
+
notices,
|
|
3630
|
+
usage: usage.total,
|
|
3631
|
+
tripped
|
|
3632
|
+
};
|
|
3213
3633
|
}
|
|
3214
3634
|
async function runGenerationSubBatch(context, batch, accepted, withheld, providerFailures) {
|
|
3215
3635
|
let result;
|
|
@@ -3220,12 +3640,12 @@ async function runGenerationSubBatch(context, batch, accepted, withheld, provide
|
|
|
3220
3640
|
for (const item of batch) {
|
|
3221
3641
|
providerFailures.push(item.targetKey);
|
|
3222
3642
|
}
|
|
3223
|
-
return [subBatchFailedNotice(batch.length, error)];
|
|
3643
|
+
return { notices: [subBatchFailedNotice(batch.length, error)], usage: void 0 };
|
|
3224
3644
|
}
|
|
3225
3645
|
for (const item of batch) {
|
|
3226
3646
|
foldGenerationItem(item, result, accepted, withheld, providerFailures);
|
|
3227
3647
|
}
|
|
3228
|
-
return readNotices(result);
|
|
3648
|
+
return { notices: readNotices(result), usage: result.usage };
|
|
3229
3649
|
}
|
|
3230
3650
|
function foldGenerationItem(item, result, accepted, withheld, providerFailures) {
|
|
3231
3651
|
const value = result.values.get(item.targetKey);
|
|
@@ -3293,6 +3713,7 @@ async function runLocale(params) {
|
|
|
3293
3713
|
generated: [],
|
|
3294
3714
|
integrityMismatches: [],
|
|
3295
3715
|
providerFailures: [],
|
|
3716
|
+
budgetWithheld: [],
|
|
3296
3717
|
pruned,
|
|
3297
3718
|
notices: sdkNotices
|
|
3298
3719
|
}),
|
|
@@ -3300,16 +3721,21 @@ async function runLocale(params) {
|
|
|
3300
3721
|
};
|
|
3301
3722
|
}
|
|
3302
3723
|
const entries = toTranslate.map((key) => params.source.entries.get(key)).filter((entry) => entry !== void 0);
|
|
3724
|
+
const startedStopped = params.budget.stopped;
|
|
3303
3725
|
const accepted = /* @__PURE__ */ new Map();
|
|
3304
3726
|
const integrityMismatches = [];
|
|
3305
3727
|
const providerFailures = [];
|
|
3306
|
-
const
|
|
3728
|
+
const budgetWithheld = [];
|
|
3729
|
+
const reviewFlags = /* @__PURE__ */ new Map();
|
|
3730
|
+
const translation = await translateAndCheck(
|
|
3307
3731
|
provider,
|
|
3308
3732
|
params,
|
|
3309
3733
|
entries,
|
|
3310
3734
|
accepted,
|
|
3311
3735
|
integrityMismatches,
|
|
3312
|
-
providerFailures
|
|
3736
|
+
providerFailures,
|
|
3737
|
+
budgetWithheld,
|
|
3738
|
+
reviewFlags
|
|
3313
3739
|
);
|
|
3314
3740
|
const merged = new Map(target.entries);
|
|
3315
3741
|
for (const key of pruned) {
|
|
@@ -3335,16 +3761,20 @@ async function runLocale(params) {
|
|
|
3335
3761
|
const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
|
|
3336
3762
|
const notices = [
|
|
3337
3763
|
...pluralNotices,
|
|
3338
|
-
...
|
|
3339
|
-
...generation.notices
|
|
3764
|
+
...translation.notices,
|
|
3765
|
+
...generation.notices,
|
|
3766
|
+
...budgetLocaleNotices(params.budget, startedStopped, translation.tripped, generation.tripped)
|
|
3340
3767
|
];
|
|
3341
3768
|
const withheld = /* @__PURE__ */ new Set([
|
|
3342
3769
|
...integrityMismatches,
|
|
3343
3770
|
...providerFailures,
|
|
3344
3771
|
...invalidIcuSource,
|
|
3345
3772
|
...generation.withheld,
|
|
3346
|
-
...generation.providerFailures
|
|
3773
|
+
...generation.providerFailures,
|
|
3774
|
+
// Source-present keys withheld by the budget guardrail must keep their prior lock hash too.
|
|
3775
|
+
...budgetWithheld
|
|
3347
3776
|
]);
|
|
3777
|
+
const localeUsage = combineUsage(translation.usage, generation.usage);
|
|
3348
3778
|
return {
|
|
3349
3779
|
summary: baseSummary({
|
|
3350
3780
|
locale: params.targetLocale,
|
|
@@ -3357,15 +3787,27 @@ async function runLocale(params) {
|
|
|
3357
3787
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
3358
3788
|
// A generation sub-batch whose provider call itself threw is a provider failure, never integrity.
|
|
3359
3789
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
3790
|
+
budgetWithheld: [...budgetWithheld, ...generation.budgetWithheld].sort(),
|
|
3360
3791
|
pruned,
|
|
3361
|
-
notices
|
|
3792
|
+
notices,
|
|
3793
|
+
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
3794
|
+
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
3362
3795
|
}),
|
|
3363
3796
|
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
3364
3797
|
};
|
|
3365
3798
|
}
|
|
3799
|
+
var NO_GENERATION_RESULT = {
|
|
3800
|
+
accepted: [],
|
|
3801
|
+
withheld: [],
|
|
3802
|
+
providerFailures: [],
|
|
3803
|
+
budgetWithheld: [],
|
|
3804
|
+
notices: [],
|
|
3805
|
+
usage: void 0,
|
|
3806
|
+
tripped: false
|
|
3807
|
+
};
|
|
3366
3808
|
async function runGeneration(params, provider) {
|
|
3367
3809
|
if (!params.generatePlurals || provider.kind !== "llm") {
|
|
3368
|
-
return
|
|
3810
|
+
return NO_GENERATION_RESULT;
|
|
3369
3811
|
}
|
|
3370
3812
|
return generatePluralForms({
|
|
3371
3813
|
source: params.source,
|
|
@@ -3377,9 +3819,13 @@ async function runGeneration(params, provider) {
|
|
|
3377
3819
|
glossary: params.glossary,
|
|
3378
3820
|
tone: params.tone,
|
|
3379
3821
|
baseline: params.baseline,
|
|
3380
|
-
maxBatchSize: params.maxBatchSize
|
|
3822
|
+
maxBatchSize: params.maxBatchSize,
|
|
3823
|
+
budget: params.budget
|
|
3381
3824
|
});
|
|
3382
3825
|
}
|
|
3826
|
+
function budgetLocaleNotices(budget, startedStopped, mainTripped, generationTripped) {
|
|
3827
|
+
return startedStopped || mainTripped || generationTripped ? [budgetExceededNotice(budget)] : [];
|
|
3828
|
+
}
|
|
3383
3829
|
function pluralNoticeFor(params, merged) {
|
|
3384
3830
|
if (params.format !== "i18next-json") {
|
|
3385
3831
|
return [];
|
|
@@ -3400,26 +3846,53 @@ function baseSummary(parts) {
|
|
|
3400
3846
|
invalidIcuSource: parts.invalidIcuSource,
|
|
3401
3847
|
integrityMismatches: parts.integrityMismatches,
|
|
3402
3848
|
providerFailures: parts.providerFailures,
|
|
3849
|
+
budgetWithheld: parts.budgetWithheld,
|
|
3403
3850
|
generated: parts.generated,
|
|
3404
|
-
notices: parts.notices
|
|
3851
|
+
notices: parts.notices,
|
|
3852
|
+
needsReview: parts.needsReview ?? [],
|
|
3853
|
+
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
3405
3854
|
};
|
|
3406
3855
|
}
|
|
3407
|
-
|
|
3856
|
+
function needsReviewFor(acceptedKeys, reviewFlags) {
|
|
3857
|
+
const entries = [];
|
|
3858
|
+
for (const key of acceptedKeys) {
|
|
3859
|
+
const flag = reviewFlags.get(key);
|
|
3860
|
+
if (flag !== void 0) {
|
|
3861
|
+
entries.push({ key, reasons: flag.reasons });
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
return entries.sort((a, b) => a.key < b.key ? -1 : 1);
|
|
3865
|
+
}
|
|
3866
|
+
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches, providerFailures, budgetWithheld, reviewFlags) {
|
|
3408
3867
|
const notices = [];
|
|
3868
|
+
const usage = createUsageAccumulator();
|
|
3869
|
+
let tripped = false;
|
|
3409
3870
|
for (const batch of chunk(entries, params.maxBatchSize)) {
|
|
3410
|
-
|
|
3871
|
+
if (params.budget.stopped) {
|
|
3872
|
+
for (const entry of batch) {
|
|
3873
|
+
budgetWithheld.push(entry.key);
|
|
3874
|
+
}
|
|
3875
|
+
continue;
|
|
3876
|
+
}
|
|
3877
|
+
const subResult = await runSubBatch(
|
|
3411
3878
|
provider,
|
|
3412
3879
|
params,
|
|
3413
3880
|
batch,
|
|
3414
3881
|
accepted,
|
|
3415
3882
|
integrityMismatches,
|
|
3416
|
-
providerFailures
|
|
3883
|
+
providerFailures,
|
|
3884
|
+
reviewFlags
|
|
3417
3885
|
);
|
|
3418
|
-
notices.push(...
|
|
3886
|
+
notices.push(...subResult.notices);
|
|
3887
|
+
foldUsage(usage, subResult.usage);
|
|
3888
|
+
foldTrackerUsage(params.budget, subResult.usage);
|
|
3889
|
+
if (checkBudgetTrip(params.budget)) {
|
|
3890
|
+
tripped = true;
|
|
3891
|
+
}
|
|
3419
3892
|
}
|
|
3420
|
-
return notices;
|
|
3893
|
+
return { notices, tripped, usage: usage.total };
|
|
3421
3894
|
}
|
|
3422
|
-
async function runSubBatch(provider, params, batch, accepted, integrityMismatches, providerFailures) {
|
|
3895
|
+
async function runSubBatch(provider, params, batch, accepted, integrityMismatches, providerFailures, reviewFlags) {
|
|
3423
3896
|
let result;
|
|
3424
3897
|
try {
|
|
3425
3898
|
result = await provider.translateBatch(buildRequest3(params, batch));
|
|
@@ -3427,12 +3900,17 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
3427
3900
|
for (const entry of batch) {
|
|
3428
3901
|
providerFailures.push(entry.key);
|
|
3429
3902
|
}
|
|
3430
|
-
return [subBatchFailedNotice(batch.length, error)];
|
|
3903
|
+
return { notices: [subBatchFailedNotice(batch.length, error)], usage: void 0 };
|
|
3431
3904
|
}
|
|
3432
3905
|
for (const entry of batch) {
|
|
3433
3906
|
foldEntryResult(entry, result, accepted, integrityMismatches, providerFailures);
|
|
3434
3907
|
}
|
|
3435
|
-
|
|
3908
|
+
if (result.reviewFlags !== void 0) {
|
|
3909
|
+
for (const [key, flag] of result.reviewFlags) {
|
|
3910
|
+
reviewFlags.set(key, flag);
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
return { notices: readNotices(result), usage: result.usage };
|
|
3436
3914
|
}
|
|
3437
3915
|
function foldEntryResult(entry, result, accepted, integrityMismatches, providerFailures) {
|
|
3438
3916
|
const value = result.values.get(entry.key);
|
|
@@ -3490,6 +3968,10 @@ async function translate2(input, deps = {}) {
|
|
|
3490
3968
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
3491
3969
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
3492
3970
|
const fs = deps.fs ?? defaultFs;
|
|
3971
|
+
const budget = createBudgetTracker(
|
|
3972
|
+
config.maxTokens,
|
|
3973
|
+
config.budgetBehavior ?? DEFAULT_BUDGET_BEHAVIOR
|
|
3974
|
+
);
|
|
3493
3975
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3494
3976
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
3495
3977
|
const source = await readSource(config, cwd, fs, adapter);
|
|
@@ -3514,7 +3996,8 @@ async function translate2(input, deps = {}) {
|
|
|
3514
3996
|
prune,
|
|
3515
3997
|
generatePlurals,
|
|
3516
3998
|
maxBatchSize,
|
|
3517
|
-
fs
|
|
3999
|
+
fs,
|
|
4000
|
+
budget
|
|
3518
4001
|
};
|
|
3519
4002
|
const { summary, lockEntries } = await runLocale(params);
|
|
3520
4003
|
if (!dryRun) {
|
|
@@ -3527,7 +4010,19 @@ async function translate2(input, deps = {}) {
|
|
|
3527
4010
|
}
|
|
3528
4011
|
}
|
|
3529
4012
|
const { succeeded, failed } = partition(summaries);
|
|
3530
|
-
|
|
4013
|
+
const usage = summaries.reduce(
|
|
4014
|
+
(total, summary) => combineUsage(total, summary.usage),
|
|
4015
|
+
void 0
|
|
4016
|
+
);
|
|
4017
|
+
const budgetSummary = toBudgetSummary(budget);
|
|
4018
|
+
return {
|
|
4019
|
+
dryRun,
|
|
4020
|
+
locales: summaries,
|
|
4021
|
+
succeeded,
|
|
4022
|
+
failed,
|
|
4023
|
+
...usage !== void 0 ? { usage } : {},
|
|
4024
|
+
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4025
|
+
};
|
|
3531
4026
|
}
|
|
3532
4027
|
var ExchangeError = class extends Error {
|
|
3533
4028
|
code;
|
|
@@ -3549,10 +4044,13 @@ var INSTRUCTIONS_LINES = [
|
|
|
3549
4044
|
" structure and the argument names exactly. Translate only the human-readable text.",
|
|
3550
4045
|
"6. Do not edit the 'Key' column or the hidden 'Source hash' column. They map your translation",
|
|
3551
4046
|
" back to the right string. You may sort or filter rows freely; mapping does not depend on row order.",
|
|
4047
|
+
"7. The 'Context' column, when present, is reference text from the source file explaining what the",
|
|
4048
|
+
" string means or where it is used. It is read-only; leave it as-is.",
|
|
3552
4049
|
"",
|
|
3553
4050
|
"Status values:",
|
|
3554
4051
|
" new - this string has no translation yet.",
|
|
3555
4052
|
" changed - the source string changed and the translation needs updating.",
|
|
4053
|
+
" unchanged - already up to date, included for reference. No action needed.",
|
|
3556
4054
|
"",
|
|
3557
4055
|
"When you are done, save the file and send it back. verbatra checks every value on import:",
|
|
3558
4056
|
"a value with a broken placeholder, invalid ICU, or a source that changed since export is",
|
|
@@ -3564,7 +4062,10 @@ var COLUMN = {
|
|
|
3564
4062
|
current: 3,
|
|
3565
4063
|
status: 4,
|
|
3566
4064
|
translation: 5,
|
|
3567
|
-
sourceHash: 6
|
|
4065
|
+
sourceHash: 6,
|
|
4066
|
+
context: 7,
|
|
4067
|
+
reviewStatus: 8,
|
|
4068
|
+
reviewReasons: 9
|
|
3568
4069
|
};
|
|
3569
4070
|
var HEADERS = [
|
|
3570
4071
|
"Key",
|
|
@@ -3572,7 +4073,10 @@ var HEADERS = [
|
|
|
3572
4073
|
"Current translation",
|
|
3573
4074
|
"Status",
|
|
3574
4075
|
"Translation",
|
|
3575
|
-
"Source hash"
|
|
4076
|
+
"Source hash",
|
|
4077
|
+
"Context",
|
|
4078
|
+
"Review status",
|
|
4079
|
+
"Review reasons"
|
|
3576
4080
|
];
|
|
3577
4081
|
var HEADER_ROW = 1;
|
|
3578
4082
|
var INSTRUCTIONS_SHEET_NAME = "Instructions";
|
|
@@ -3591,7 +4095,10 @@ var COLUMN_WIDTHS = {
|
|
|
3591
4095
|
[COLUMN.source]: 50,
|
|
3592
4096
|
[COLUMN.current]: 50,
|
|
3593
4097
|
[COLUMN.status]: 12,
|
|
3594
|
-
[COLUMN.translation]: 50
|
|
4098
|
+
[COLUMN.translation]: 50,
|
|
4099
|
+
[COLUMN.context]: 50,
|
|
4100
|
+
[COLUMN.reviewStatus]: 12,
|
|
4101
|
+
[COLUMN.reviewReasons]: 40
|
|
3595
4102
|
};
|
|
3596
4103
|
var TEXT_NUMBER_FORMAT = "@";
|
|
3597
4104
|
function styleHeader(sheet) {
|
|
@@ -3622,7 +4129,10 @@ function writeRow(sheet, sheetRow) {
|
|
|
3622
4129
|
translationCell.numFmt = TEXT_NUMBER_FORMAT;
|
|
3623
4130
|
translationCell.value = sheetRow.translation === "" ? null : sheetRow.translation;
|
|
3624
4131
|
row.getCell(COLUMN.sourceHash).value = sheetRow.sourceHash;
|
|
3625
|
-
|
|
4132
|
+
row.getCell(COLUMN.context).value = sheetRow.context;
|
|
4133
|
+
row.getCell(COLUMN.reviewStatus).value = sheetRow.reviewStatus;
|
|
4134
|
+
row.getCell(COLUMN.reviewReasons).value = sheetRow.reviewReasons;
|
|
4135
|
+
for (let column = COLUMN.key; column <= COLUMN.reviewReasons; column += 1) {
|
|
3626
4136
|
const cell = row.getCell(column);
|
|
3627
4137
|
cell.protection = { locked: column !== COLUMN.translation };
|
|
3628
4138
|
if (column !== COLUMN.translation) {
|
|
@@ -3647,6 +4157,26 @@ function assertValidWorksheetName(locale) {
|
|
|
3647
4157
|
);
|
|
3648
4158
|
}
|
|
3649
4159
|
}
|
|
4160
|
+
function assertNoWorksheetNameCollisions(sheets) {
|
|
4161
|
+
const reservedKey = INSTRUCTIONS_SHEET_NAME.toLowerCase();
|
|
4162
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4163
|
+
for (const sheet of sheets) {
|
|
4164
|
+
const key = sheet.locale.toLowerCase();
|
|
4165
|
+
if (key === reservedKey) {
|
|
4166
|
+
throw new ExchangeError(
|
|
4167
|
+
"WORKBOOK_INVALID",
|
|
4168
|
+
`The locale "${sheet.locale}" cannot be an Excel worksheet name: it collides with the reserved "${INSTRUCTIONS_SHEET_NAME}" sheet.`
|
|
4169
|
+
);
|
|
4170
|
+
}
|
|
4171
|
+
if (seen.has(key)) {
|
|
4172
|
+
throw new ExchangeError(
|
|
4173
|
+
"WORKBOOK_INVALID",
|
|
4174
|
+
`The locale "${sheet.locale}" cannot be an Excel worksheet name: it collides with another target locale's sheet name.`
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
seen.add(key);
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
3650
4180
|
async function buildDataSheet(workbook, sheet) {
|
|
3651
4181
|
assertValidWorksheetName(sheet.locale);
|
|
3652
4182
|
const worksheet = workbook.addWorksheet(sheet.locale);
|
|
@@ -3673,6 +4203,7 @@ function buildInstructionsSheet(workbook) {
|
|
|
3673
4203
|
sheet.getRow(1).font = { bold: true };
|
|
3674
4204
|
}
|
|
3675
4205
|
async function buildWorkbook(model) {
|
|
4206
|
+
assertNoWorksheetNameCollisions(model.sheets);
|
|
3676
4207
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
3677
4208
|
buildInstructionsSheet(workbook);
|
|
3678
4209
|
for (const sheet of model.sheets) {
|
|
@@ -3773,9 +4304,14 @@ var rowSchema = zod.z.object({
|
|
|
3773
4304
|
key: zod.z.string().min(1),
|
|
3774
4305
|
source: zod.z.string(),
|
|
3775
4306
|
currentTarget: zod.z.string(),
|
|
3776
|
-
status: zod.z.enum(["new", "changed"]),
|
|
4307
|
+
status: zod.z.enum(["new", "changed", "unchanged"]),
|
|
3777
4308
|
sourceHash: zod.z.string(),
|
|
3778
|
-
translation: zod.z.string()
|
|
4309
|
+
translation: zod.z.string(),
|
|
4310
|
+
context: zod.z.string(),
|
|
4311
|
+
// A legacy workbook exported before these columns existed has no such cell; cellString then yields
|
|
4312
|
+
// "", and .catch maps it to "ok" / "" rather than rejecting the row.
|
|
4313
|
+
reviewStatus: zod.z.enum(["ok", "review"]).catch("ok"),
|
|
4314
|
+
reviewReasons: zod.z.string().catch("")
|
|
3779
4315
|
});
|
|
3780
4316
|
function cellString(cell) {
|
|
3781
4317
|
const value = cell.value;
|
|
@@ -3808,7 +4344,13 @@ function parseRow(sheet, row) {
|
|
|
3808
4344
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
3809
4345
|
status: cellString(row.getCell(COLUMN.status)),
|
|
3810
4346
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
3811
|
-
translation: cellString(row.getCell(COLUMN.translation))
|
|
4347
|
+
translation: cellString(row.getCell(COLUMN.translation)),
|
|
4348
|
+
// A workbook built before the Context column existed has no cell here; getCell auto-vivifies an
|
|
4349
|
+
// empty one, so cellString yields "" and the row still validates, keeping import backward-compatible.
|
|
4350
|
+
context: cellString(row.getCell(COLUMN.context)),
|
|
4351
|
+
// Same backward-compatible pattern for a workbook built before the Review columns existed.
|
|
4352
|
+
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
4353
|
+
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
3812
4354
|
};
|
|
3813
4355
|
const result = rowSchema.safeParse(candidate);
|
|
3814
4356
|
if (!result.success) {
|
|
@@ -3882,7 +4424,34 @@ async function readTarget3(cwd, config, adapter, fs, locale) {
|
|
|
3882
4424
|
}
|
|
3883
4425
|
return (await adapter.read(path, locale)).resource;
|
|
3884
4426
|
}
|
|
3885
|
-
function
|
|
4427
|
+
function reasonLabel(reason) {
|
|
4428
|
+
return reason.toLowerCase().replace(/_/g, "-");
|
|
4429
|
+
}
|
|
4430
|
+
function reviewColumns(flag) {
|
|
4431
|
+
if (flag === void 0) {
|
|
4432
|
+
return { reviewStatus: "ok", reviewReasons: "" };
|
|
4433
|
+
}
|
|
4434
|
+
return { reviewStatus: "review", reviewReasons: flag.reasons.map(reasonLabel).join(", ") };
|
|
4435
|
+
}
|
|
4436
|
+
function computeRowReview(adapter, sourceValue, currentTarget, sourceLocale, targetLocale, glossary) {
|
|
4437
|
+
if (currentTarget === "") {
|
|
4438
|
+
return { reviewStatus: "ok", reviewReasons: "" };
|
|
4439
|
+
}
|
|
4440
|
+
const integrity = adapter.comparePlaceholders?.(sourceValue, currentTarget) ?? checkPlaceholders(
|
|
4441
|
+
adapter.extractPlaceholders(sourceValue),
|
|
4442
|
+
adapter.extractPlaceholders(currentTarget)
|
|
4443
|
+
);
|
|
4444
|
+
const flag = computeReviewFlags({
|
|
4445
|
+
sourceValue,
|
|
4446
|
+
translatedValue: currentTarget,
|
|
4447
|
+
sourceLocale,
|
|
4448
|
+
targetLocale,
|
|
4449
|
+
integrity,
|
|
4450
|
+
glossary
|
|
4451
|
+
});
|
|
4452
|
+
return reviewColumns(flag);
|
|
4453
|
+
}
|
|
4454
|
+
function buildRows(source, target, baseline, includeUnchanged, adapter, glossary) {
|
|
3886
4455
|
const diff2 = diffResources(source, target, { baseline });
|
|
3887
4456
|
const rows = [];
|
|
3888
4457
|
const add = (keys, status) => {
|
|
@@ -3891,20 +4460,30 @@ function buildRows(source, target, baseline, includeUnchanged) {
|
|
|
3891
4460
|
if (sourceEntry === void 0) {
|
|
3892
4461
|
continue;
|
|
3893
4462
|
}
|
|
4463
|
+
const currentTarget = target.entries.get(key)?.value ?? "";
|
|
3894
4464
|
rows.push({
|
|
3895
4465
|
key,
|
|
3896
4466
|
source: sourceEntry.value,
|
|
3897
|
-
currentTarget
|
|
4467
|
+
currentTarget,
|
|
3898
4468
|
status,
|
|
3899
4469
|
sourceHash: contentHash(sourceEntry),
|
|
3900
|
-
translation: ""
|
|
4470
|
+
translation: "",
|
|
4471
|
+
context: sourceEntry.description ?? "",
|
|
4472
|
+
...computeRowReview(
|
|
4473
|
+
adapter,
|
|
4474
|
+
sourceEntry.value,
|
|
4475
|
+
currentTarget,
|
|
4476
|
+
source.locale,
|
|
4477
|
+
target.locale,
|
|
4478
|
+
glossary
|
|
4479
|
+
)
|
|
3901
4480
|
});
|
|
3902
4481
|
}
|
|
3903
4482
|
};
|
|
3904
4483
|
add(diff2.missing, "new");
|
|
3905
4484
|
add(diff2.changed, "changed");
|
|
3906
4485
|
if (includeUnchanged) {
|
|
3907
|
-
add(diff2.unchanged, "
|
|
4486
|
+
add(diff2.unchanged, "unchanged");
|
|
3908
4487
|
}
|
|
3909
4488
|
return [...rows].sort((a, b) => a.key < b.key ? -1 : 1);
|
|
3910
4489
|
}
|
|
@@ -3923,7 +4502,9 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
3923
4502
|
source.resource,
|
|
3924
4503
|
target,
|
|
3925
4504
|
baselineFor(lock, locale),
|
|
3926
|
-
input.includeUnchanged ?? false
|
|
4505
|
+
input.includeUnchanged ?? false,
|
|
4506
|
+
adapter,
|
|
4507
|
+
config.glossary
|
|
3927
4508
|
);
|
|
3928
4509
|
return { locale, rows };
|
|
3929
4510
|
})
|
|
@@ -4024,9 +4605,14 @@ function importLocale(params) {
|
|
|
4024
4605
|
integrityMismatches: [...buckets.mismatches].sort(),
|
|
4025
4606
|
// A workbook import never calls a provider, so a provider-call failure cannot occur here.
|
|
4026
4607
|
providerFailures: [],
|
|
4608
|
+
// A workbook import never calls a provider, so the budget guardrail never withholds anything here.
|
|
4609
|
+
budgetWithheld: [],
|
|
4027
4610
|
// Plural generation is a translate-flow concern; the manual workbook import never generates forms.
|
|
4028
4611
|
generated: [],
|
|
4029
|
-
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : []
|
|
4612
|
+
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
4613
|
+
// A workbook import never calls a provider and never recomputes review flags on its own path; the
|
|
4614
|
+
// Excel workbook's Review status/reasons columns are informational only (see export-workbook.ts).
|
|
4615
|
+
needsReview: []
|
|
4030
4616
|
};
|
|
4031
4617
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
4032
4618
|
}
|