@verbatra/sdk 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/index.cjs +947 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +317 -59
- package/dist/index.d.ts +317 -59
- package/dist/index.js +947 -104
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
1
|
import { dirname, resolve, join, basename, extname } from 'path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
import { cosmiconfig } from 'cosmiconfig';
|
|
4
5
|
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
5
6
|
import { randomUUID } from 'crypto';
|
|
6
7
|
import { rm, access, mkdir, open, writeFile, rename } from 'fs/promises';
|
|
7
|
-
import { z } from 'zod';
|
|
8
8
|
import Anthropic from '@anthropic-ai/sdk';
|
|
9
9
|
import * as deepl from 'deepl-node';
|
|
10
10
|
import { createRequire } from 'module';
|
|
@@ -15,10 +15,107 @@ import { parse, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
|
15
15
|
import { XMLSerializer, DOMParser } from '@xmldom/xmldom';
|
|
16
16
|
import { stringify, parse as parse$1 } from 'yaml';
|
|
17
17
|
import ExcelJS from 'exceljs';
|
|
18
|
-
import { Readable } from 'stream';
|
|
19
18
|
import JSZip from 'jszip';
|
|
19
|
+
import { Readable } from 'stream';
|
|
20
20
|
import { watch as watch$1 } from 'chokidar';
|
|
21
21
|
|
|
22
|
+
// src/cache/translation-memory.ts
|
|
23
|
+
var CACHE_FILE_NAME = "verbatra.cache.json";
|
|
24
|
+
var CURRENT_VERSION = 1;
|
|
25
|
+
var EMPTY_MEMORY = { version: CURRENT_VERSION, entries: {} };
|
|
26
|
+
var MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
|
|
27
|
+
var translationMemorySchema = z.object({
|
|
28
|
+
version: z.number().int().positive(),
|
|
29
|
+
entries: z.record(z.string(), z.record(z.string(), z.record(z.string(), z.string())))
|
|
30
|
+
});
|
|
31
|
+
function cacheFilePath(cwd) {
|
|
32
|
+
return resolve(cwd, CACHE_FILE_NAME);
|
|
33
|
+
}
|
|
34
|
+
var UNUSABLE = { memory: EMPTY_MEMORY, writable: true };
|
|
35
|
+
async function readTranslationMemory(path, fs) {
|
|
36
|
+
let read;
|
|
37
|
+
try {
|
|
38
|
+
read = await fs.readFileBounded(path, MAX_CACHE_FILE_BYTES);
|
|
39
|
+
} catch {
|
|
40
|
+
return UNUSABLE;
|
|
41
|
+
}
|
|
42
|
+
if (read.kind !== "ok") {
|
|
43
|
+
return UNUSABLE;
|
|
44
|
+
}
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(read.content);
|
|
48
|
+
} catch {
|
|
49
|
+
return UNUSABLE;
|
|
50
|
+
}
|
|
51
|
+
const result = translationMemorySchema.safeParse(parsed);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
return UNUSABLE;
|
|
54
|
+
}
|
|
55
|
+
if (result.data.version !== CURRENT_VERSION) {
|
|
56
|
+
return { memory: EMPTY_MEMORY, writable: false };
|
|
57
|
+
}
|
|
58
|
+
return { memory: result.data, writable: true };
|
|
59
|
+
}
|
|
60
|
+
function lookupMemory(memory, fingerprint, locale, contentHash2) {
|
|
61
|
+
return memory.entries[fingerprint]?.[locale]?.[contentHash2];
|
|
62
|
+
}
|
|
63
|
+
function applyAdditions(base, fingerprint, additionsByLocale) {
|
|
64
|
+
if (additionsByLocale.size === 0) {
|
|
65
|
+
return base;
|
|
66
|
+
}
|
|
67
|
+
const fingerprintEntries = {};
|
|
68
|
+
for (const [locale, hashes] of Object.entries(base.entries[fingerprint] ?? {})) {
|
|
69
|
+
fingerprintEntries[locale] = { ...hashes };
|
|
70
|
+
}
|
|
71
|
+
for (const [locale, hashes] of additionsByLocale) {
|
|
72
|
+
fingerprintEntries[locale] = { ...fingerprintEntries[locale], ...hashes };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
version: base.version,
|
|
76
|
+
entries: { ...base.entries, [fingerprint]: fingerprintEntries }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function additionsToRecord(additions) {
|
|
80
|
+
const record = {};
|
|
81
|
+
for (const addition of additions) {
|
|
82
|
+
record[addition.contentHash] = addition.value;
|
|
83
|
+
}
|
|
84
|
+
return record;
|
|
85
|
+
}
|
|
86
|
+
function sortEntries(record) {
|
|
87
|
+
return Object.entries(record).sort(([a], [b]) => a < b ? -1 : 1);
|
|
88
|
+
}
|
|
89
|
+
function serialize(memory) {
|
|
90
|
+
const entries = {};
|
|
91
|
+
for (const [fingerprint, locales] of sortEntries(memory.entries)) {
|
|
92
|
+
const localeMap = {};
|
|
93
|
+
for (const [locale, hashes] of sortEntries(locales)) {
|
|
94
|
+
localeMap[locale] = Object.fromEntries(sortEntries(hashes));
|
|
95
|
+
}
|
|
96
|
+
entries[fingerprint] = localeMap;
|
|
97
|
+
}
|
|
98
|
+
return `${JSON.stringify({ version: memory.version, entries }, null, 2)}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
async function writeTranslationMemory(path, memory, fs) {
|
|
102
|
+
await fs.writeFile(path, serialize(memory));
|
|
103
|
+
}
|
|
104
|
+
async function feedTranslationMemory(cwd, fs, fingerprint, additionsByLocale) {
|
|
105
|
+
if (additionsByLocale.size === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const path = cacheFilePath(cwd);
|
|
110
|
+
const { memory, writable } = await readTranslationMemory(path, fs);
|
|
111
|
+
if (!writable) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await writeTranslationMemory(path, applyAdditions(memory, fingerprint, additionsByLocale), fs);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
22
119
|
// src/config/define-config.ts
|
|
23
120
|
function defineConfig(config) {
|
|
24
121
|
return config;
|
|
@@ -212,7 +309,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
212
309
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
213
310
|
var FNV_PRIME = 1099511628211n;
|
|
214
311
|
var U64_MASK = (1n << 64n) - 1n;
|
|
215
|
-
function
|
|
312
|
+
function stableStringHash(input) {
|
|
216
313
|
let hash = FNV_OFFSET_BASIS;
|
|
217
314
|
for (let index = 0; index < input.length; index += 1) {
|
|
218
315
|
hash ^= BigInt(input.charCodeAt(index));
|
|
@@ -233,7 +330,7 @@ function canonicalize(entry) {
|
|
|
233
330
|
]);
|
|
234
331
|
}
|
|
235
332
|
function contentHash(entry) {
|
|
236
|
-
return
|
|
333
|
+
return stableStringHash(canonicalize(entry));
|
|
237
334
|
}
|
|
238
335
|
function sorted(keys) {
|
|
239
336
|
return [...keys].sort();
|
|
@@ -278,7 +375,8 @@ var SUPPORTED_FORMATS = [
|
|
|
278
375
|
"ngx-translate-json",
|
|
279
376
|
"xliff",
|
|
280
377
|
"yaml",
|
|
281
|
-
"arb"
|
|
378
|
+
"arb",
|
|
379
|
+
"properties"
|
|
282
380
|
];
|
|
283
381
|
var supportedFormatSchema = z.enum(SUPPORTED_FORMATS);
|
|
284
382
|
var translationEntrySchema = z.object({
|
|
@@ -1220,14 +1318,14 @@ function throwAbort(signal) {
|
|
|
1220
1318
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1221
1319
|
}
|
|
1222
1320
|
function delay(ms, signal) {
|
|
1223
|
-
return new Promise((
|
|
1321
|
+
return new Promise((resolve10) => {
|
|
1224
1322
|
const onAbort = () => {
|
|
1225
1323
|
clearTimeout(timer);
|
|
1226
|
-
|
|
1324
|
+
resolve10();
|
|
1227
1325
|
};
|
|
1228
1326
|
const timer = setTimeout(() => {
|
|
1229
1327
|
signal?.removeEventListener("abort", onAbort);
|
|
1230
|
-
|
|
1328
|
+
resolve10();
|
|
1231
1329
|
}, ms);
|
|
1232
1330
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1233
1331
|
});
|
|
@@ -1965,8 +2063,8 @@ async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
|
1965
2063
|
|
|
1966
2064
|
// src/lock/lock-file.ts
|
|
1967
2065
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1968
|
-
var
|
|
1969
|
-
var EMPTY_LOCK = { version:
|
|
2066
|
+
var CURRENT_VERSION2 = 1;
|
|
2067
|
+
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
1970
2068
|
var MAX_LOCK_FILE_BYTES = 16 * 1024 * 1024;
|
|
1971
2069
|
var lockFileSchema = z.object({
|
|
1972
2070
|
version: z.number().int().positive(),
|
|
@@ -1995,10 +2093,10 @@ function parseLockFileRead(read, path) {
|
|
|
1995
2093
|
if (!result.success) {
|
|
1996
2094
|
throw new SdkError("LOCK_FILE_INVALID", `The lock-file at ${path} has an unexpected shape.`);
|
|
1997
2095
|
}
|
|
1998
|
-
if (result.data.version !==
|
|
2096
|
+
if (result.data.version !== CURRENT_VERSION2) {
|
|
1999
2097
|
throw new SdkError(
|
|
2000
2098
|
"LOCK_FILE_INVALID",
|
|
2001
|
-
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${
|
|
2099
|
+
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${CURRENT_VERSION2}.`
|
|
2002
2100
|
);
|
|
2003
2101
|
}
|
|
2004
2102
|
return result.data;
|
|
@@ -2788,7 +2886,7 @@ function createTreeFileAdapter(options) {
|
|
|
2788
2886
|
extensions,
|
|
2789
2887
|
sniff,
|
|
2790
2888
|
parse: parse3,
|
|
2791
|
-
serialize,
|
|
2889
|
+
serialize: serialize2,
|
|
2792
2890
|
deriveEntry,
|
|
2793
2891
|
extractPlaceholders,
|
|
2794
2892
|
computeInvalidIcuKeys,
|
|
@@ -2823,7 +2921,7 @@ function createTreeFileAdapter(options) {
|
|
|
2823
2921
|
},
|
|
2824
2922
|
async write(resource, filePath) {
|
|
2825
2923
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
2826
|
-
await atomicWriteFile(filePath,
|
|
2924
|
+
await atomicWriteFile(filePath, serialize2(tree));
|
|
2827
2925
|
}
|
|
2828
2926
|
};
|
|
2829
2927
|
}
|
|
@@ -3080,6 +3178,401 @@ function createNgxTranslateJsonAdapter() {
|
|
|
3080
3178
|
keyMode: "path-notation"
|
|
3081
3179
|
});
|
|
3082
3180
|
}
|
|
3181
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
3182
|
+
try {
|
|
3183
|
+
return parseEntries(content, namespace);
|
|
3184
|
+
} catch (error) {
|
|
3185
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
function createFlatFileAdapter(options) {
|
|
3189
|
+
const {
|
|
3190
|
+
format,
|
|
3191
|
+
extensions,
|
|
3192
|
+
sniff,
|
|
3193
|
+
parseEntries,
|
|
3194
|
+
serializeEntries,
|
|
3195
|
+
extractPlaceholders,
|
|
3196
|
+
validateMessage,
|
|
3197
|
+
computeInvalidIcuKeys
|
|
3198
|
+
} = options;
|
|
3199
|
+
return {
|
|
3200
|
+
format,
|
|
3201
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
3202
|
+
extractPlaceholders,
|
|
3203
|
+
validateMessage: validateMessage ?? (() => true),
|
|
3204
|
+
async read(filePath, locale) {
|
|
3205
|
+
const content = await readFileContent(filePath);
|
|
3206
|
+
const namespace = namespaceOf(filePath);
|
|
3207
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
3208
|
+
const resource = { locale, namespace, format, entries };
|
|
3209
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3210
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3211
|
+
},
|
|
3212
|
+
async write(resource, filePath) {
|
|
3213
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
3214
|
+
await atomicWriteFile(filePath, data);
|
|
3215
|
+
}
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordinal", "choice"]);
|
|
3219
|
+
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3220
|
+
function matchingBraces(value) {
|
|
3221
|
+
const close = new Int32Array(value.length).fill(-1);
|
|
3222
|
+
const open3 = [];
|
|
3223
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
3224
|
+
const char = value[i];
|
|
3225
|
+
if (char === "{") {
|
|
3226
|
+
open3.push(i);
|
|
3227
|
+
} else if (char === "}") {
|
|
3228
|
+
const start = open3.pop();
|
|
3229
|
+
if (start !== void 0) {
|
|
3230
|
+
close[start] = i;
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
return close;
|
|
3235
|
+
}
|
|
3236
|
+
function findNameEnd(value, from, limit) {
|
|
3237
|
+
for (let i = from; i < limit; i += 1) {
|
|
3238
|
+
const char = value[i];
|
|
3239
|
+
if (char === ",") {
|
|
3240
|
+
return { comma: i, hasBrace: false };
|
|
3241
|
+
}
|
|
3242
|
+
if (char === "{" || char === "}") {
|
|
3243
|
+
return { comma: -1, hasBrace: true };
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
return { comma: -1, hasBrace: false };
|
|
3247
|
+
}
|
|
3248
|
+
function parseArgumentAt(value, open3, close) {
|
|
3249
|
+
const { comma, hasBrace } = findNameEnd(value, open3 + 1, close);
|
|
3250
|
+
if (hasBrace) {
|
|
3251
|
+
return null;
|
|
3252
|
+
}
|
|
3253
|
+
const nameEnd = comma === -1 ? close : comma;
|
|
3254
|
+
const name = value.slice(open3 + 1, nameEnd).trim();
|
|
3255
|
+
if (!ARGUMENT_NAME.test(name)) {
|
|
3256
|
+
return null;
|
|
3257
|
+
}
|
|
3258
|
+
if (comma === -1) {
|
|
3259
|
+
return { name, type: null, styleStart: -1 };
|
|
3260
|
+
}
|
|
3261
|
+
const secondComma = value.indexOf(",", comma + 1);
|
|
3262
|
+
if (secondComma === -1 || secondComma >= close) {
|
|
3263
|
+
return { name, type: value.slice(comma + 1, close).trim(), styleStart: -1 };
|
|
3264
|
+
}
|
|
3265
|
+
return { name, type: value.slice(comma + 1, secondComma).trim(), styleStart: secondComma + 1 };
|
|
3266
|
+
}
|
|
3267
|
+
function canonicalToken(value, arg, close) {
|
|
3268
|
+
if (arg.type === null) {
|
|
3269
|
+
return `{${arg.name}}`;
|
|
3270
|
+
}
|
|
3271
|
+
if (arg.styleStart === -1) {
|
|
3272
|
+
return `{${arg.name},${arg.type}}`;
|
|
3273
|
+
}
|
|
3274
|
+
return `{${arg.name},${arg.type},${value.slice(arg.styleStart, close).trim()}}`;
|
|
3275
|
+
}
|
|
3276
|
+
function emitArgument(value, close, arg, closeIndex, out) {
|
|
3277
|
+
if (arg.type !== null && SUBMESSAGE_TYPES.has(arg.type)) {
|
|
3278
|
+
out.push(`{${arg.name},${arg.type}}`);
|
|
3279
|
+
if (arg.styleStart !== -1) {
|
|
3280
|
+
scanRange(value, close, arg.styleStart, closeIndex, false, out);
|
|
3281
|
+
}
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
out.push(canonicalToken(value, arg, closeIndex));
|
|
3285
|
+
}
|
|
3286
|
+
function scanRange(value, close, start, end, topLevel, out) {
|
|
3287
|
+
let i = start;
|
|
3288
|
+
while (i < end) {
|
|
3289
|
+
if (value[i] !== "{") {
|
|
3290
|
+
i += 1;
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3293
|
+
if (topLevel && value[i + 1] === "{") {
|
|
3294
|
+
i += 2;
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
const closeIndex = close[i] ?? -1;
|
|
3298
|
+
if (closeIndex === -1 || closeIndex >= end) {
|
|
3299
|
+
i += 1;
|
|
3300
|
+
continue;
|
|
3301
|
+
}
|
|
3302
|
+
const arg = parseArgumentAt(value, i, closeIndex);
|
|
3303
|
+
if (arg === null) {
|
|
3304
|
+
i += 1;
|
|
3305
|
+
continue;
|
|
3306
|
+
}
|
|
3307
|
+
emitArgument(value, close, arg, closeIndex, out);
|
|
3308
|
+
i = closeIndex + 1;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
function extractPropertiesPlaceholders(value) {
|
|
3312
|
+
const out = [];
|
|
3313
|
+
scanRange(value, matchingBraces(value), 0, value.length, true, out);
|
|
3314
|
+
return out;
|
|
3315
|
+
}
|
|
3316
|
+
var UNICODE_ESCAPE = /^[0-9a-fA-F]{4}$/;
|
|
3317
|
+
var LEADING_WHITESPACE = /^[ \t\f]+/;
|
|
3318
|
+
var TRAILING_TERMINATOR = /(?:\r\n|\r|\n)$/;
|
|
3319
|
+
function detectLineTerminator(content) {
|
|
3320
|
+
if (content.includes("\r\n")) {
|
|
3321
|
+
return "\r\n";
|
|
3322
|
+
}
|
|
3323
|
+
return content.includes("\r") ? "\r" : "\n";
|
|
3324
|
+
}
|
|
3325
|
+
function isPropertiesWhitespace(char) {
|
|
3326
|
+
return char === " " || char === " " || char === "\f";
|
|
3327
|
+
}
|
|
3328
|
+
function splitPhysicalLines(content) {
|
|
3329
|
+
if (content === "") {
|
|
3330
|
+
return [];
|
|
3331
|
+
}
|
|
3332
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
3333
|
+
if (TRAILING_TERMINATOR.test(content)) {
|
|
3334
|
+
lines.pop();
|
|
3335
|
+
}
|
|
3336
|
+
return lines;
|
|
3337
|
+
}
|
|
3338
|
+
function countTrailingBackslashes(line) {
|
|
3339
|
+
let count = 0;
|
|
3340
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) {
|
|
3341
|
+
count += 1;
|
|
3342
|
+
}
|
|
3343
|
+
return count;
|
|
3344
|
+
}
|
|
3345
|
+
function isContinued(line) {
|
|
3346
|
+
return countTrailingBackslashes(line) % 2 === 1;
|
|
3347
|
+
}
|
|
3348
|
+
function isBlankLine(line) {
|
|
3349
|
+
return line.trim() === "";
|
|
3350
|
+
}
|
|
3351
|
+
function isCommentLine(line) {
|
|
3352
|
+
const trimmed = line.replace(LEADING_WHITESPACE, "");
|
|
3353
|
+
return trimmed.startsWith("#") || trimmed.startsWith("!");
|
|
3354
|
+
}
|
|
3355
|
+
function joinContinuation(lines, start) {
|
|
3356
|
+
const pieces = [];
|
|
3357
|
+
let index = start;
|
|
3358
|
+
let current = lines[start] ?? "";
|
|
3359
|
+
while (isContinued(current) && index + 1 < lines.length) {
|
|
3360
|
+
pieces.push(current.slice(0, -1));
|
|
3361
|
+
index += 1;
|
|
3362
|
+
current = (lines[index] ?? "").replace(LEADING_WHITESPACE, "");
|
|
3363
|
+
}
|
|
3364
|
+
if (isContinued(current)) {
|
|
3365
|
+
current = current.slice(0, -1);
|
|
3366
|
+
}
|
|
3367
|
+
pieces.push(current);
|
|
3368
|
+
return { logical: pieces.join(""), nextIndex: index + 1 };
|
|
3369
|
+
}
|
|
3370
|
+
function decodeSimpleEscape(char) {
|
|
3371
|
+
switch (char) {
|
|
3372
|
+
case "t":
|
|
3373
|
+
return " ";
|
|
3374
|
+
case "n":
|
|
3375
|
+
return "\n";
|
|
3376
|
+
case "r":
|
|
3377
|
+
return "\r";
|
|
3378
|
+
case "f":
|
|
3379
|
+
return "\f";
|
|
3380
|
+
default:
|
|
3381
|
+
return char;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
function decodeUnicodeEscape(raw, at) {
|
|
3385
|
+
const hex = raw.slice(at, at + 4);
|
|
3386
|
+
if (!UNICODE_ESCAPE.test(hex)) {
|
|
3387
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file has a malformed unicode escape.");
|
|
3388
|
+
}
|
|
3389
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
3390
|
+
}
|
|
3391
|
+
function decodeEscapes(raw) {
|
|
3392
|
+
let out = "";
|
|
3393
|
+
let i = 0;
|
|
3394
|
+
while (i < raw.length) {
|
|
3395
|
+
const char = raw[i];
|
|
3396
|
+
if (char !== "\\") {
|
|
3397
|
+
out += char;
|
|
3398
|
+
i += 1;
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
const next = raw[i + 1];
|
|
3402
|
+
if (next === void 0) {
|
|
3403
|
+
break;
|
|
3404
|
+
}
|
|
3405
|
+
if (next === "u") {
|
|
3406
|
+
out += decodeUnicodeEscape(raw, i + 2);
|
|
3407
|
+
i += 6;
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
out += decodeSimpleEscape(next);
|
|
3411
|
+
i += 2;
|
|
3412
|
+
}
|
|
3413
|
+
return out;
|
|
3414
|
+
}
|
|
3415
|
+
function keyEndIndex(logical, from) {
|
|
3416
|
+
let i = from;
|
|
3417
|
+
while (i < logical.length) {
|
|
3418
|
+
const char = logical[i];
|
|
3419
|
+
if (char === "\\") {
|
|
3420
|
+
i += 2;
|
|
3421
|
+
continue;
|
|
3422
|
+
}
|
|
3423
|
+
if (char === void 0 || isPropertiesWhitespace(char) || char === "=" || char === ":") {
|
|
3424
|
+
break;
|
|
3425
|
+
}
|
|
3426
|
+
i += 1;
|
|
3427
|
+
}
|
|
3428
|
+
return i;
|
|
3429
|
+
}
|
|
3430
|
+
function skipWhitespace(logical, from) {
|
|
3431
|
+
let i = from;
|
|
3432
|
+
while (i < logical.length) {
|
|
3433
|
+
const char = logical[i];
|
|
3434
|
+
if (char === void 0 || !isPropertiesWhitespace(char)) {
|
|
3435
|
+
break;
|
|
3436
|
+
}
|
|
3437
|
+
i += 1;
|
|
3438
|
+
}
|
|
3439
|
+
return i;
|
|
3440
|
+
}
|
|
3441
|
+
function parseEntryLine(logical) {
|
|
3442
|
+
const keyStart = skipWhitespace(logical, 0);
|
|
3443
|
+
const keyEnd = keyEndIndex(logical, keyStart);
|
|
3444
|
+
const afterKey = skipWhitespace(logical, keyEnd);
|
|
3445
|
+
const separator = logical[afterKey];
|
|
3446
|
+
const valueStart = separator === "=" || separator === ":" ? skipWhitespace(logical, afterKey + 1) : afterKey;
|
|
3447
|
+
return {
|
|
3448
|
+
key: decodeEscapes(logical.slice(keyStart, keyEnd)),
|
|
3449
|
+
value: decodeEscapes(logical.slice(valueStart))
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
function parseItems(content) {
|
|
3453
|
+
const lines = splitPhysicalLines(content);
|
|
3454
|
+
const items = [];
|
|
3455
|
+
let i = 0;
|
|
3456
|
+
while (i < lines.length) {
|
|
3457
|
+
const line = lines[i] ?? "";
|
|
3458
|
+
if (isBlankLine(line) || isCommentLine(line)) {
|
|
3459
|
+
items.push({ kind: "raw", text: line });
|
|
3460
|
+
i += 1;
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
const { logical, nextIndex } = joinContinuation(lines, i);
|
|
3464
|
+
items.push({ kind: "entry", ...parseEntryLine(logical) });
|
|
3465
|
+
i = nextIndex;
|
|
3466
|
+
}
|
|
3467
|
+
return items;
|
|
3468
|
+
}
|
|
3469
|
+
function parsePropertiesEntries(content, namespace) {
|
|
3470
|
+
const map = /* @__PURE__ */ new Map();
|
|
3471
|
+
for (const item of parseItems(content)) {
|
|
3472
|
+
if (item.kind === "entry") {
|
|
3473
|
+
map.set(item.key, {
|
|
3474
|
+
key: item.key,
|
|
3475
|
+
namespace,
|
|
3476
|
+
value: item.value,
|
|
3477
|
+
placeholders: extractPropertiesPlaceholders(item.value),
|
|
3478
|
+
isPlural: false
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
return map;
|
|
3483
|
+
}
|
|
3484
|
+
function unicodeEscape(code) {
|
|
3485
|
+
return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3486
|
+
}
|
|
3487
|
+
function escapeChar(char, isFirst, isKey) {
|
|
3488
|
+
switch (char) {
|
|
3489
|
+
case "\\":
|
|
3490
|
+
return "\\\\";
|
|
3491
|
+
case " ":
|
|
3492
|
+
return "\\t";
|
|
3493
|
+
case "\n":
|
|
3494
|
+
return "\\n";
|
|
3495
|
+
case "\r":
|
|
3496
|
+
return "\\r";
|
|
3497
|
+
case "\f":
|
|
3498
|
+
return "\\f";
|
|
3499
|
+
case " ":
|
|
3500
|
+
return isKey || isFirst ? "\\ " : " ";
|
|
3501
|
+
}
|
|
3502
|
+
if (isKey && (char === "=" || char === ":" || char === "#" || char === "!")) {
|
|
3503
|
+
return `\\${char}`;
|
|
3504
|
+
}
|
|
3505
|
+
const code = char.charCodeAt(0);
|
|
3506
|
+
return code < 32 || code > 126 ? unicodeEscape(code) : char;
|
|
3507
|
+
}
|
|
3508
|
+
function escapeString(input, isKey) {
|
|
3509
|
+
let out = "";
|
|
3510
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
3511
|
+
const char = input[i];
|
|
3512
|
+
if (char !== void 0) {
|
|
3513
|
+
out += escapeChar(char, i === 0, isKey);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
return out;
|
|
3517
|
+
}
|
|
3518
|
+
function formatEntry(key, value) {
|
|
3519
|
+
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3520
|
+
}
|
|
3521
|
+
function isFileNotFound(error) {
|
|
3522
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3523
|
+
}
|
|
3524
|
+
async function readStructure(filePath) {
|
|
3525
|
+
let outcome;
|
|
3526
|
+
try {
|
|
3527
|
+
outcome = await readBounded2(filePath);
|
|
3528
|
+
} catch (error) {
|
|
3529
|
+
if (isFileNotFound(error)) {
|
|
3530
|
+
return { items: [], terminator: "\n" };
|
|
3531
|
+
}
|
|
3532
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination file could not be read.");
|
|
3533
|
+
}
|
|
3534
|
+
if (outcome.kind === "not-a-file") {
|
|
3535
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
3536
|
+
}
|
|
3537
|
+
if (outcome.kind === "too-large") {
|
|
3538
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
3539
|
+
}
|
|
3540
|
+
return {
|
|
3541
|
+
items: parseItems(outcome.content),
|
|
3542
|
+
terminator: detectLineTerminator(outcome.content)
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
async function serializePropertiesEntries(entries, filePath) {
|
|
3546
|
+
const { items, terminator } = await readStructure(filePath);
|
|
3547
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3548
|
+
const lines = [];
|
|
3549
|
+
for (const item of items) {
|
|
3550
|
+
if (item.kind === "raw") {
|
|
3551
|
+
lines.push(item.text);
|
|
3552
|
+
continue;
|
|
3553
|
+
}
|
|
3554
|
+
const entry = entries.get(item.key);
|
|
3555
|
+
if (entry !== void 0 && !emitted.has(item.key)) {
|
|
3556
|
+
lines.push(formatEntry(item.key, entry.value));
|
|
3557
|
+
emitted.add(item.key);
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
for (const [key, entry] of entries) {
|
|
3561
|
+
if (!emitted.has(key)) {
|
|
3562
|
+
lines.push(formatEntry(key, entry.value));
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
return lines.length === 0 ? "" : `${lines.join(terminator)}${terminator}`;
|
|
3566
|
+
}
|
|
3567
|
+
function createPropertiesAdapter() {
|
|
3568
|
+
return createFlatFileAdapter({
|
|
3569
|
+
format: "properties",
|
|
3570
|
+
extensions: [".properties"],
|
|
3571
|
+
parseEntries: parsePropertiesEntries,
|
|
3572
|
+
serializeEntries: serializePropertiesEntries,
|
|
3573
|
+
extractPlaceholders: extractPropertiesPlaceholders
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3083
3576
|
var AdapterRegistry = class {
|
|
3084
3577
|
adapters = [];
|
|
3085
3578
|
/**
|
|
@@ -3157,43 +3650,6 @@ function createVueI18nJsonAdapter() {
|
|
|
3157
3650
|
})
|
|
3158
3651
|
});
|
|
3159
3652
|
}
|
|
3160
|
-
function toEntries2(content, namespace, parseEntries) {
|
|
3161
|
-
try {
|
|
3162
|
-
return parseEntries(content, namespace);
|
|
3163
|
-
} catch (error) {
|
|
3164
|
-
rethrowStructured(error, "The file could not be parsed.");
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
function createFlatFileAdapter(options) {
|
|
3168
|
-
const {
|
|
3169
|
-
format,
|
|
3170
|
-
extensions,
|
|
3171
|
-
sniff,
|
|
3172
|
-
parseEntries,
|
|
3173
|
-
serializeEntries,
|
|
3174
|
-
extractPlaceholders,
|
|
3175
|
-
validateMessage,
|
|
3176
|
-
computeInvalidIcuKeys
|
|
3177
|
-
} = options;
|
|
3178
|
-
return {
|
|
3179
|
-
format,
|
|
3180
|
-
canHandle: buildCanHandle(extensions, sniff),
|
|
3181
|
-
extractPlaceholders,
|
|
3182
|
-
validateMessage: validateMessage ?? (() => true),
|
|
3183
|
-
async read(filePath, locale) {
|
|
3184
|
-
const content = await readFileContent(filePath);
|
|
3185
|
-
const namespace = namespaceOf(filePath);
|
|
3186
|
-
const entries = toEntries2(content, namespace, parseEntries);
|
|
3187
|
-
const resource = { locale, namespace, format, entries };
|
|
3188
|
-
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3189
|
-
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3190
|
-
},
|
|
3191
|
-
async write(resource, filePath) {
|
|
3192
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3193
|
-
await atomicWriteFile(filePath, data);
|
|
3194
|
-
}
|
|
3195
|
-
};
|
|
3196
|
-
}
|
|
3197
3653
|
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
3198
3654
|
function extractXliffPlaceholders(value) {
|
|
3199
3655
|
const result = [];
|
|
@@ -3504,7 +3960,7 @@ function createYamlAdapter() {
|
|
|
3504
3960
|
});
|
|
3505
3961
|
}
|
|
3506
3962
|
function createDefaultRegistry() {
|
|
3507
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
3963
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter()).register(createPropertiesAdapter());
|
|
3508
3964
|
}
|
|
3509
3965
|
|
|
3510
3966
|
// src/selection/select-adapter.ts
|
|
@@ -3625,6 +4081,28 @@ async function diff(input, deps = {}) {
|
|
|
3625
4081
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3626
4082
|
}
|
|
3627
4083
|
|
|
4084
|
+
// src/cache/fingerprint.ts
|
|
4085
|
+
function fingerprintModel(provider) {
|
|
4086
|
+
const options = provider.options;
|
|
4087
|
+
const model = options.model;
|
|
4088
|
+
return typeof model === "string" ? model : null;
|
|
4089
|
+
}
|
|
4090
|
+
function sortGlossary(glossary) {
|
|
4091
|
+
if (glossary === void 0) {
|
|
4092
|
+
return {};
|
|
4093
|
+
}
|
|
4094
|
+
return Object.fromEntries(Object.entries(glossary).sort(([a], [b]) => a < b ? -1 : 1));
|
|
4095
|
+
}
|
|
4096
|
+
function computeFingerprint(config) {
|
|
4097
|
+
const canonical = JSON.stringify({
|
|
4098
|
+
provider: config.provider.id,
|
|
4099
|
+
model: fingerprintModel(config.provider),
|
|
4100
|
+
tone: config.tone ?? null,
|
|
4101
|
+
glossary: sortGlossary(config.glossary)
|
|
4102
|
+
});
|
|
4103
|
+
return stableStringHash(canonical);
|
|
4104
|
+
}
|
|
4105
|
+
|
|
3628
4106
|
// src/flow/integrity-gate.ts
|
|
3629
4107
|
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3630
4108
|
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
@@ -3637,6 +4115,9 @@ function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
|
3637
4115
|
if (assessValueDegeneracy(sourceEntry.value, candidateValue).degenerate) {
|
|
3638
4116
|
return { accepted: false, reason: "degenerate" };
|
|
3639
4117
|
}
|
|
4118
|
+
if (sourceEntry.value.trim() !== "" && candidateValue.trim() === "") {
|
|
4119
|
+
return { accepted: false, reason: "empty" };
|
|
4120
|
+
}
|
|
3640
4121
|
return { accepted: true };
|
|
3641
4122
|
}
|
|
3642
4123
|
|
|
@@ -3675,6 +4156,12 @@ async function editEntry(input, deps = {}) {
|
|
|
3675
4156
|
mode: "merge",
|
|
3676
4157
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3677
4158
|
});
|
|
4159
|
+
await feedTranslationMemory(
|
|
4160
|
+
cwd,
|
|
4161
|
+
fs,
|
|
4162
|
+
computeFingerprint(config),
|
|
4163
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: input.value }]])
|
|
4164
|
+
);
|
|
3678
4165
|
return { accepted: true, value: input.value };
|
|
3679
4166
|
});
|
|
3680
4167
|
}
|
|
@@ -3892,13 +4379,19 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
3892
4379
|
mode: "merge",
|
|
3893
4380
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3894
4381
|
});
|
|
4382
|
+
await feedTranslationMemory(
|
|
4383
|
+
cwd,
|
|
4384
|
+
fs,
|
|
4385
|
+
computeFingerprint(config),
|
|
4386
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: value }]])
|
|
4387
|
+
);
|
|
3895
4388
|
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3896
4389
|
return { accepted: true, value, reviewReasons };
|
|
3897
4390
|
});
|
|
3898
4391
|
}
|
|
3899
4392
|
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3900
4393
|
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3901
|
-
var
|
|
4394
|
+
var CURRENT_VERSION3 = 1;
|
|
3902
4395
|
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3903
4396
|
var reviewReasonCodeSchema = z.enum([
|
|
3904
4397
|
"LENGTH_RATIO_OUTLIER",
|
|
@@ -3948,7 +4441,7 @@ function toRunStatusLocale(locale) {
|
|
|
3948
4441
|
}
|
|
3949
4442
|
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3950
4443
|
return {
|
|
3951
|
-
version:
|
|
4444
|
+
version: CURRENT_VERSION3,
|
|
3952
4445
|
generatedAt,
|
|
3953
4446
|
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3954
4447
|
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
@@ -3981,7 +4474,7 @@ async function readRunStatusFile(path, fs) {
|
|
|
3981
4474
|
return void 0;
|
|
3982
4475
|
}
|
|
3983
4476
|
const result = runStatusFileSchema.safeParse(parsed);
|
|
3984
|
-
if (!result.success || result.data.version !==
|
|
4477
|
+
if (!result.success || result.data.version !== CURRENT_VERSION3) {
|
|
3985
4478
|
return void 0;
|
|
3986
4479
|
}
|
|
3987
4480
|
return fromParsed(result.data);
|
|
@@ -4060,12 +4553,16 @@ function failureSummary(locale, error) {
|
|
|
4060
4553
|
orphaned: [],
|
|
4061
4554
|
pruned: [],
|
|
4062
4555
|
invalidIcuSource: [],
|
|
4556
|
+
cacheHits: [],
|
|
4063
4557
|
integrityMismatches: [],
|
|
4064
4558
|
providerFailures: [],
|
|
4065
4559
|
budgetWithheld: [],
|
|
4066
4560
|
generated: [],
|
|
4067
4561
|
notices: [],
|
|
4068
4562
|
needsReview: [],
|
|
4563
|
+
unfilled: [],
|
|
4564
|
+
malformedRows: [],
|
|
4565
|
+
duplicateKeys: [],
|
|
4069
4566
|
error: describeError(error)
|
|
4070
4567
|
};
|
|
4071
4568
|
}
|
|
@@ -4074,7 +4571,7 @@ function deriveLocaleStatus(parts) {
|
|
|
4074
4571
|
if (!withheld) {
|
|
4075
4572
|
return "succeeded";
|
|
4076
4573
|
}
|
|
4077
|
-
const accepted = parts.translated.length > 0 || parts.generated.length > 0;
|
|
4574
|
+
const accepted = parts.translated.length > 0 || parts.cacheHits.length > 0 || parts.generated.length > 0;
|
|
4078
4575
|
return accepted ? "partial" : "failed";
|
|
4079
4576
|
}
|
|
4080
4577
|
function partition(locales) {
|
|
@@ -4394,6 +4891,101 @@ function foldGenerationItem(item, result, adapter, accepted, withheld, providerF
|
|
|
4394
4891
|
}
|
|
4395
4892
|
|
|
4396
4893
|
// src/flow/locale-run.ts
|
|
4894
|
+
function partitionCacheHits(params, toTranslate) {
|
|
4895
|
+
const cache = params.cache;
|
|
4896
|
+
const hits = /* @__PURE__ */ new Map();
|
|
4897
|
+
if (cache === void 0) {
|
|
4898
|
+
return { hits, misses: toTranslate };
|
|
4899
|
+
}
|
|
4900
|
+
const misses = [];
|
|
4901
|
+
for (const key of toTranslate) {
|
|
4902
|
+
const source = params.source.entries.get(key);
|
|
4903
|
+
if (source === void 0) {
|
|
4904
|
+
continue;
|
|
4905
|
+
}
|
|
4906
|
+
const cached = lookupMemory(
|
|
4907
|
+
cache.snapshot,
|
|
4908
|
+
cache.fingerprint,
|
|
4909
|
+
params.targetLocale,
|
|
4910
|
+
contentHash(source)
|
|
4911
|
+
);
|
|
4912
|
+
if (cached !== void 0 && gateCandidateValue(source, cached, params.adapter).accepted) {
|
|
4913
|
+
hits.set(key, { value: cached, source });
|
|
4914
|
+
} else {
|
|
4915
|
+
misses.push(key);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
return { hits, misses };
|
|
4919
|
+
}
|
|
4920
|
+
function collectCacheAdditions(params, accepted, cacheHitKeys) {
|
|
4921
|
+
if (params.cache === void 0) {
|
|
4922
|
+
return [];
|
|
4923
|
+
}
|
|
4924
|
+
const additions = [];
|
|
4925
|
+
for (const [key, entry] of accepted) {
|
|
4926
|
+
if (!cacheHitKeys.has(key)) {
|
|
4927
|
+
additions.push({ contentHash: contentHash(entry.source), value: entry.value });
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
return additions;
|
|
4931
|
+
}
|
|
4932
|
+
function groupMissesByContent(params, misses) {
|
|
4933
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4934
|
+
for (const key of misses) {
|
|
4935
|
+
const source = params.source.entries.get(key);
|
|
4936
|
+
if (source === void 0) {
|
|
4937
|
+
continue;
|
|
4938
|
+
}
|
|
4939
|
+
const existing = byHash.get(contentHash(source));
|
|
4940
|
+
if (existing === void 0) {
|
|
4941
|
+
byHash.set(contentHash(source), { representative: key, duplicates: [] });
|
|
4942
|
+
} else {
|
|
4943
|
+
existing.duplicates.push(key);
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
return [...byHash.values()];
|
|
4947
|
+
}
|
|
4948
|
+
function fanOutContentDuplicates(params, groups, outcome) {
|
|
4949
|
+
for (const group of groups) {
|
|
4950
|
+
if (group.duplicates.length > 0) {
|
|
4951
|
+
applyGroupOutcome(params, group, outcome);
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
function applyGroupOutcome(params, group, outcome) {
|
|
4956
|
+
const acceptedRepresentative = outcome.accepted.get(group.representative);
|
|
4957
|
+
if (acceptedRepresentative !== void 0) {
|
|
4958
|
+
fanOutAccepted(params, group, acceptedRepresentative, outcome);
|
|
4959
|
+
return;
|
|
4960
|
+
}
|
|
4961
|
+
withheldBucketFor(group.representative, outcome).push(...group.duplicates);
|
|
4962
|
+
}
|
|
4963
|
+
function fanOutAccepted(params, group, acceptedRepresentative, outcome) {
|
|
4964
|
+
const flag = outcome.reviewFlags.get(group.representative);
|
|
4965
|
+
for (const key of group.duplicates) {
|
|
4966
|
+
const source = params.source.entries.get(key);
|
|
4967
|
+
if (source === void 0) {
|
|
4968
|
+
continue;
|
|
4969
|
+
}
|
|
4970
|
+
if (!gateCandidateValue(source, acceptedRepresentative.value, params.adapter).accepted) {
|
|
4971
|
+
outcome.integrityMismatches.push(key);
|
|
4972
|
+
continue;
|
|
4973
|
+
}
|
|
4974
|
+
outcome.accepted.set(key, { value: acceptedRepresentative.value, source });
|
|
4975
|
+
if (flag !== void 0) {
|
|
4976
|
+
outcome.reviewFlags.set(key, flag);
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
function withheldBucketFor(representative, outcome) {
|
|
4981
|
+
if (outcome.integrityMismatches.includes(representative)) {
|
|
4982
|
+
return outcome.integrityMismatches;
|
|
4983
|
+
}
|
|
4984
|
+
if (outcome.budgetWithheld.includes(representative)) {
|
|
4985
|
+
return outcome.budgetWithheld;
|
|
4986
|
+
}
|
|
4987
|
+
return outcome.providerFailures;
|
|
4988
|
+
}
|
|
4397
4989
|
function buildRequest3(params, entries) {
|
|
4398
4990
|
return {
|
|
4399
4991
|
sourceLocale: params.sourceLocale,
|
|
@@ -4436,6 +5028,7 @@ async function runLocale(params) {
|
|
|
4436
5028
|
orphaned,
|
|
4437
5029
|
invalidIcuSource,
|
|
4438
5030
|
translated: toTranslate,
|
|
5031
|
+
cacheHits: [],
|
|
4439
5032
|
generated: [],
|
|
4440
5033
|
integrityMismatches: [],
|
|
4441
5034
|
providerFailures: [],
|
|
@@ -4443,12 +5036,16 @@ async function runLocale(params) {
|
|
|
4443
5036
|
pruned,
|
|
4444
5037
|
notices: sdkNotices
|
|
4445
5038
|
}),
|
|
4446
|
-
lockEntries: {}
|
|
5039
|
+
lockEntries: {},
|
|
5040
|
+
cacheAdditions: []
|
|
4447
5041
|
};
|
|
4448
5042
|
}
|
|
4449
|
-
const
|
|
5043
|
+
const partition2 = partitionCacheHits(params, toTranslate);
|
|
5044
|
+
const cacheHitKeys = new Set(partition2.hits.keys());
|
|
5045
|
+
const missGroups = groupMissesByContent(params, partition2.misses);
|
|
5046
|
+
const entries = missGroups.map((group) => params.source.entries.get(group.representative)).filter((entry) => entry !== void 0);
|
|
4450
5047
|
const startedStopped = params.budget.stopped;
|
|
4451
|
-
const accepted =
|
|
5048
|
+
const accepted = new Map(partition2.hits);
|
|
4452
5049
|
const integrityMismatches = [];
|
|
4453
5050
|
const providerFailures = [];
|
|
4454
5051
|
const budgetWithheld = [];
|
|
@@ -4463,6 +5060,13 @@ async function runLocale(params) {
|
|
|
4463
5060
|
budgetWithheld,
|
|
4464
5061
|
reviewFlags
|
|
4465
5062
|
);
|
|
5063
|
+
fanOutContentDuplicates(params, missGroups, {
|
|
5064
|
+
accepted,
|
|
5065
|
+
integrityMismatches,
|
|
5066
|
+
providerFailures,
|
|
5067
|
+
budgetWithheld,
|
|
5068
|
+
reviewFlags
|
|
5069
|
+
});
|
|
4466
5070
|
const merged = new Map(target.entries);
|
|
4467
5071
|
for (const key of pruned) {
|
|
4468
5072
|
merged.delete(key);
|
|
@@ -4509,7 +5113,8 @@ async function runLocale(params) {
|
|
|
4509
5113
|
unchanged: diff2.unchanged,
|
|
4510
5114
|
orphaned,
|
|
4511
5115
|
invalidIcuSource,
|
|
4512
|
-
translated: [...accepted.keys()],
|
|
5116
|
+
translated: [...accepted.keys()].filter((key) => !cacheHitKeys.has(key)),
|
|
5117
|
+
cacheHits: [...cacheHitKeys].sort(),
|
|
4513
5118
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
4514
5119
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
4515
5120
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
@@ -4519,7 +5124,8 @@ async function runLocale(params) {
|
|
|
4519
5124
|
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
4520
5125
|
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
4521
5126
|
}),
|
|
4522
|
-
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
5127
|
+
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted),
|
|
5128
|
+
cacheAdditions: collectCacheAdditions(params, accepted, cacheHitKeys)
|
|
4523
5129
|
};
|
|
4524
5130
|
}
|
|
4525
5131
|
var NO_GENERATION_RESULT = {
|
|
@@ -4570,12 +5176,16 @@ function baseSummary(parts) {
|
|
|
4570
5176
|
orphaned: parts.orphaned,
|
|
4571
5177
|
pruned: parts.pruned,
|
|
4572
5178
|
invalidIcuSource: parts.invalidIcuSource,
|
|
5179
|
+
cacheHits: parts.cacheHits,
|
|
4573
5180
|
integrityMismatches: parts.integrityMismatches,
|
|
4574
5181
|
providerFailures: parts.providerFailures,
|
|
4575
5182
|
budgetWithheld: parts.budgetWithheld,
|
|
4576
5183
|
generated: parts.generated,
|
|
4577
5184
|
notices: parts.notices,
|
|
4578
5185
|
needsReview: parts.needsReview ?? [],
|
|
5186
|
+
unfilled: [],
|
|
5187
|
+
malformedRows: [],
|
|
5188
|
+
duplicateKeys: [],
|
|
4579
5189
|
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
4580
5190
|
};
|
|
4581
5191
|
}
|
|
@@ -4593,7 +5203,16 @@ async function translateAndCheck(provider, params, entries, accepted, integrityM
|
|
|
4593
5203
|
const notices = [];
|
|
4594
5204
|
const usage = createUsageAccumulator();
|
|
4595
5205
|
let tripped = false;
|
|
4596
|
-
|
|
5206
|
+
const batches = chunk(entries, params.maxBatchSize);
|
|
5207
|
+
let batchIndex = 0;
|
|
5208
|
+
for (const batch of batches) {
|
|
5209
|
+
batchIndex += 1;
|
|
5210
|
+
params.onProgress?.({
|
|
5211
|
+
type: "sub-batch",
|
|
5212
|
+
locale: params.targetLocale,
|
|
5213
|
+
batchIndex,
|
|
5214
|
+
totalBatches: batches.length
|
|
5215
|
+
});
|
|
4597
5216
|
if (params.budget.stopped) {
|
|
4598
5217
|
for (const entry of batch) {
|
|
4599
5218
|
budgetWithheld.push(entry.key);
|
|
@@ -4739,6 +5358,33 @@ async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
|
4739
5358
|
} catch {
|
|
4740
5359
|
}
|
|
4741
5360
|
}
|
|
5361
|
+
async function createRunCacheState(input, config, cwd, dryRun, fs) {
|
|
5362
|
+
if (dryRun || input.cache === false) {
|
|
5363
|
+
return void 0;
|
|
5364
|
+
}
|
|
5365
|
+
const { memory, writable } = await readTranslationMemory(cacheFilePath(cwd), fs);
|
|
5366
|
+
return { memory, writable, fingerprint: computeFingerprint(config), additions: /* @__PURE__ */ new Map() };
|
|
5367
|
+
}
|
|
5368
|
+
function withCacheNotices(summaries, cache) {
|
|
5369
|
+
if (cache === void 0 || cache.writable) {
|
|
5370
|
+
return [...summaries];
|
|
5371
|
+
}
|
|
5372
|
+
const notice = {
|
|
5373
|
+
code: "CACHE_VERSION_UNRECOGNIZED",
|
|
5374
|
+
message: `${CACHE_FILE_NAME} carries a version this build does not recognize, so it was written by a newer verbatra. It was left untouched and this run used no cache. Upgrade verbatra, or delete the file to rebuild it in this build's format.`
|
|
5375
|
+
};
|
|
5376
|
+
return summaries.map((summary) => ({ ...summary, notices: [...summary.notices, notice] }));
|
|
5377
|
+
}
|
|
5378
|
+
async function recordCacheAdditions(cwd, cache, fs) {
|
|
5379
|
+
if (cache === void 0 || cache.additions.size === 0 || !cache.writable) {
|
|
5380
|
+
return;
|
|
5381
|
+
}
|
|
5382
|
+
try {
|
|
5383
|
+
const merged = applyAdditions(cache.memory, cache.fingerprint, cache.additions);
|
|
5384
|
+
await writeTranslationMemory(cacheFilePath(cwd), merged, fs);
|
|
5385
|
+
} catch {
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
4742
5388
|
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4743
5389
|
return {
|
|
4744
5390
|
source: context.source.resource,
|
|
@@ -4757,7 +5403,9 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
4757
5403
|
generatePlurals: context.generatePlurals,
|
|
4758
5404
|
maxBatchSize: context.maxBatchSize,
|
|
4759
5405
|
fs: context.fs,
|
|
4760
|
-
budget: context.budget
|
|
5406
|
+
budget: context.budget,
|
|
5407
|
+
...context.cache !== void 0 ? { cache: { snapshot: context.cache.memory, fingerprint: context.cache.fingerprint } } : {},
|
|
5408
|
+
...context.onProgress !== void 0 ? { onProgress: context.onProgress } : {}
|
|
4761
5409
|
};
|
|
4762
5410
|
}
|
|
4763
5411
|
async function runDryLocale(context, targetLocale, lock) {
|
|
@@ -4781,6 +5429,9 @@ async function runLiveLocale(context, targetLocale) {
|
|
|
4781
5429
|
mode: "replace",
|
|
4782
5430
|
entries: result.lockEntries
|
|
4783
5431
|
});
|
|
5432
|
+
if (context.cache !== void 0 && result.cacheAdditions.length > 0) {
|
|
5433
|
+
context.cache.additions.set(targetLocale, additionsToRecord(result.cacheAdditions));
|
|
5434
|
+
}
|
|
4784
5435
|
return result.summary;
|
|
4785
5436
|
},
|
|
4786
5437
|
lockOptions
|
|
@@ -4796,27 +5447,98 @@ async function runOneLocale(targetLocale, run) {
|
|
|
4796
5447
|
return failureSummary(targetLocale, error);
|
|
4797
5448
|
}
|
|
4798
5449
|
}
|
|
4799
|
-
async function
|
|
5450
|
+
async function runLocaleAt(context, targetLocales, localeIndex, runOne, results) {
|
|
5451
|
+
const targetLocale = targetLocales[localeIndex];
|
|
5452
|
+
if (targetLocale === void 0) {
|
|
5453
|
+
return;
|
|
5454
|
+
}
|
|
5455
|
+
context.onProgress?.({
|
|
5456
|
+
type: "locale-started",
|
|
5457
|
+
locale: targetLocale,
|
|
5458
|
+
localeIndex,
|
|
5459
|
+
totalLocales: targetLocales.length
|
|
5460
|
+
});
|
|
5461
|
+
const summary = await runOneLocale(targetLocale, () => runOne(targetLocale));
|
|
5462
|
+
results[localeIndex] = summary;
|
|
5463
|
+
context.onProgress?.({
|
|
5464
|
+
type: "locale-finished",
|
|
5465
|
+
locale: targetLocale,
|
|
5466
|
+
translated: summary.translated.length,
|
|
5467
|
+
localeIndex,
|
|
5468
|
+
totalLocales: targetLocales.length
|
|
5469
|
+
});
|
|
5470
|
+
}
|
|
5471
|
+
async function runLocalesWithProgress(context, targetLocales, runOne, concurrency) {
|
|
5472
|
+
const totalLocales = targetLocales.length;
|
|
5473
|
+
const results = new Array(totalLocales);
|
|
5474
|
+
let nextIndex = 0;
|
|
5475
|
+
let abort;
|
|
5476
|
+
async function worker() {
|
|
5477
|
+
while (abort === void 0 && nextIndex < totalLocales) {
|
|
5478
|
+
const localeIndex = nextIndex;
|
|
5479
|
+
nextIndex += 1;
|
|
5480
|
+
try {
|
|
5481
|
+
await runLocaleAt(context, targetLocales, localeIndex, runOne, results);
|
|
5482
|
+
} catch (error) {
|
|
5483
|
+
abort ??= { reason: error };
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
const workerCount = Math.min(concurrency, totalLocales);
|
|
5488
|
+
const workers = [];
|
|
5489
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
5490
|
+
workers.push(worker());
|
|
5491
|
+
}
|
|
5492
|
+
await Promise.all(workers);
|
|
5493
|
+
if (abort !== void 0) {
|
|
5494
|
+
throw abort.reason;
|
|
5495
|
+
}
|
|
5496
|
+
return results.filter((summary) => summary !== void 0);
|
|
5497
|
+
}
|
|
5498
|
+
async function runAllLocalesDry(context, targetLocales, concurrency) {
|
|
4800
5499
|
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
5500
|
+
return runLocalesWithProgress(
|
|
5501
|
+
context,
|
|
5502
|
+
targetLocales,
|
|
5503
|
+
(targetLocale) => runDryLocale(context, targetLocale, lock),
|
|
5504
|
+
concurrency
|
|
5505
|
+
);
|
|
5506
|
+
}
|
|
5507
|
+
async function runAllLocalesLive(context, targetLocales, concurrency) {
|
|
5508
|
+
return runLocalesWithProgress(
|
|
5509
|
+
context,
|
|
5510
|
+
targetLocales,
|
|
5511
|
+
(targetLocale) => runLiveLocale(context, targetLocale),
|
|
5512
|
+
concurrency
|
|
5513
|
+
);
|
|
5514
|
+
}
|
|
5515
|
+
function resolveConcurrency(value) {
|
|
5516
|
+
if (value === void 0) {
|
|
5517
|
+
return 1;
|
|
5518
|
+
}
|
|
5519
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5520
|
+
throw new SdkError(
|
|
5521
|
+
"CONCURRENCY_INVALID",
|
|
5522
|
+
`The concurrency option must be an integer of at least 1, got ${value}.`
|
|
4805
5523
|
);
|
|
4806
5524
|
}
|
|
4807
|
-
return
|
|
5525
|
+
return value;
|
|
4808
5526
|
}
|
|
4809
|
-
|
|
4810
|
-
const
|
|
4811
|
-
|
|
4812
|
-
|
|
5527
|
+
function resolveRunConcurrency(value, dryRun, config) {
|
|
5528
|
+
const concurrency = resolveConcurrency(value);
|
|
5529
|
+
if (!dryRun && concurrency > 1 && config.maxTokens !== void 0) {
|
|
5530
|
+
throw new SdkError(
|
|
5531
|
+
"CONCURRENCY_BUDGET_CONFLICT",
|
|
5532
|
+
"A token budget (maxTokens) and concurrency greater than 1 cannot be combined on a live run: concurrent locales would overshoot the budget nondeterministically. Set concurrency to 1, remove maxTokens, or use --dry-run."
|
|
5533
|
+
);
|
|
4813
5534
|
}
|
|
4814
|
-
return
|
|
5535
|
+
return concurrency;
|
|
4815
5536
|
}
|
|
4816
5537
|
async function translate2(input, deps = {}) {
|
|
4817
5538
|
const config = input.config;
|
|
4818
5539
|
const cwd = input.cwd ?? process.cwd();
|
|
4819
5540
|
const dryRun = input.dryRun ?? false;
|
|
5541
|
+
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
4820
5542
|
const prune = input.prune ?? config.prune ?? false;
|
|
4821
5543
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
4822
5544
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
@@ -4828,6 +5550,7 @@ async function translate2(input, deps = {}) {
|
|
|
4828
5550
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4829
5551
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
4830
5552
|
const source = await readSource(config, cwd, fs, adapter);
|
|
5553
|
+
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
4831
5554
|
const context = {
|
|
4832
5555
|
source,
|
|
4833
5556
|
adapter,
|
|
@@ -4839,11 +5562,15 @@ async function translate2(input, deps = {}) {
|
|
|
4839
5562
|
maxBatchSize,
|
|
4840
5563
|
fs,
|
|
4841
5564
|
budget,
|
|
5565
|
+
cache,
|
|
4842
5566
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5567
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
4843
5568
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
4844
5569
|
};
|
|
4845
|
-
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
4846
|
-
|
|
5570
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales, concurrency) : await runAllLocalesLive(context, config.targetLocales, concurrency);
|
|
5571
|
+
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
5572
|
+
const locales = withCacheNotices(summaries, cache);
|
|
5573
|
+
const { succeeded, partial, failed } = partition(locales);
|
|
4847
5574
|
const usage = summaries.reduce(
|
|
4848
5575
|
(total, summary2) => combineUsage(total, summary2.usage),
|
|
4849
5576
|
void 0
|
|
@@ -4851,13 +5578,14 @@ async function translate2(input, deps = {}) {
|
|
|
4851
5578
|
const budgetSummary = toBudgetSummary(budget);
|
|
4852
5579
|
const summary = {
|
|
4853
5580
|
dryRun,
|
|
4854
|
-
locales
|
|
5581
|
+
locales,
|
|
4855
5582
|
succeeded,
|
|
4856
5583
|
partial,
|
|
4857
5584
|
failed,
|
|
4858
5585
|
...usage !== void 0 ? { usage } : {},
|
|
4859
5586
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4860
5587
|
};
|
|
5588
|
+
await recordCacheAdditions(cwd, cache, fs);
|
|
4861
5589
|
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4862
5590
|
return summary;
|
|
4863
5591
|
}
|
|
@@ -4873,8 +5601,12 @@ var INSTRUCTIONS_LINES = [
|
|
|
4873
5601
|
"How to use this workbook",
|
|
4874
5602
|
"",
|
|
4875
5603
|
"1. There is one sheet per language. Open the sheet named for the language you are translating.",
|
|
5604
|
+
" Do not rename, delete, or reorder the language tabs. verbatra matches each tab to a language by",
|
|
5605
|
+
" its exact name; a renamed or missing tab is reported and its translations are not imported.",
|
|
4876
5606
|
"2. Fill ONLY the 'Translation' column. Leave every other column unchanged.",
|
|
4877
|
-
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty
|
|
5607
|
+
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty",
|
|
5608
|
+
" string. A cell that contains only spaces is treated the same as an empty cell.",
|
|
5609
|
+
" To deliberately clear an existing value (set it to empty), type exactly [[CLEAR]] in the cell.",
|
|
4878
5610
|
"4. Keep placeholders exactly as they appear. A token such as {name} or {count} must stay verbatim,",
|
|
4879
5611
|
" in your translation, with the same spelling. Do not translate or remove it.",
|
|
4880
5612
|
"5. For ICU messages (for example {count, plural, one {# item} other {# items}}), keep the ICU",
|
|
@@ -5039,6 +5771,26 @@ function buildInstructionsSheet(workbook) {
|
|
|
5039
5771
|
}
|
|
5040
5772
|
sheet.getRow(1).font = { bold: true };
|
|
5041
5773
|
}
|
|
5774
|
+
var WORKBOOK_PROTECTION_XML = '<workbookProtection lockStructure="1" lockWindows="0"/>';
|
|
5775
|
+
function spliceWorkbookProtection(xml) {
|
|
5776
|
+
const anchor = xml.includes("<bookViews") ? "<bookViews" : "<sheets";
|
|
5777
|
+
return xml.replace(anchor, `${WORKBOOK_PROTECTION_XML}${anchor}`);
|
|
5778
|
+
}
|
|
5779
|
+
async function protectWorkbookStructure(bytes) {
|
|
5780
|
+
let zip;
|
|
5781
|
+
try {
|
|
5782
|
+
zip = await JSZip.loadAsync(bytes);
|
|
5783
|
+
} catch {
|
|
5784
|
+
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5785
|
+
}
|
|
5786
|
+
const workbookPart = zip.file("xl/workbook.xml");
|
|
5787
|
+
if (workbookPart === null) {
|
|
5788
|
+
return bytes;
|
|
5789
|
+
}
|
|
5790
|
+
const xml = await workbookPart.async("string");
|
|
5791
|
+
zip.file("xl/workbook.xml", spliceWorkbookProtection(xml));
|
|
5792
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
5793
|
+
}
|
|
5042
5794
|
async function buildWorkbook(model) {
|
|
5043
5795
|
assertNoWorksheetNameCollisions(model.sheets);
|
|
5044
5796
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -5046,13 +5798,15 @@ async function buildWorkbook(model) {
|
|
|
5046
5798
|
for (const sheet of model.sheets) {
|
|
5047
5799
|
await buildDataSheet(workbook, sheet);
|
|
5048
5800
|
}
|
|
5801
|
+
let serialized;
|
|
5049
5802
|
try {
|
|
5050
5803
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
5051
|
-
|
|
5052
|
-
return Uint8Array.prototype.slice.call(view);
|
|
5804
|
+
serialized = buffer;
|
|
5053
5805
|
} catch {
|
|
5054
5806
|
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5055
5807
|
}
|
|
5808
|
+
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5809
|
+
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
5056
5810
|
}
|
|
5057
5811
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
5058
5812
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
@@ -5172,26 +5926,24 @@ function assertHeader(sheet) {
|
|
|
5172
5926
|
);
|
|
5173
5927
|
}
|
|
5174
5928
|
}
|
|
5175
|
-
|
|
5929
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5930
|
+
function parseRow(row) {
|
|
5176
5931
|
const candidate = {
|
|
5177
5932
|
key: cellString(row.getCell(COLUMN.key)),
|
|
5178
5933
|
source: cellString(row.getCell(COLUMN.source)),
|
|
5179
5934
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
5180
5935
|
status: cellString(row.getCell(COLUMN.status)),
|
|
5181
5936
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
5182
|
-
translation: cellString(row.getCell(COLUMN.translation)),
|
|
5937
|
+
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
5183
5938
|
context: cellString(row.getCell(COLUMN.context)),
|
|
5184
5939
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
5185
5940
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
5186
5941
|
};
|
|
5187
5942
|
const result = rowSchema.safeParse(candidate);
|
|
5188
5943
|
if (!result.success) {
|
|
5189
|
-
|
|
5190
|
-
"WORKBOOK_INVALID",
|
|
5191
|
-
`The sheet "${sheet.name}" has a row with a missing key or an unrecognized status.`
|
|
5192
|
-
);
|
|
5944
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
5193
5945
|
}
|
|
5194
|
-
return result.data;
|
|
5946
|
+
return { ok: true, row: result.data };
|
|
5195
5947
|
}
|
|
5196
5948
|
function readDataSheet(sheet, limits) {
|
|
5197
5949
|
assertHeader(sheet);
|
|
@@ -5202,6 +5954,9 @@ function readDataSheet(sheet, limits) {
|
|
|
5202
5954
|
);
|
|
5203
5955
|
}
|
|
5204
5956
|
const rows = [];
|
|
5957
|
+
const malformed = [];
|
|
5958
|
+
const duplicates = [];
|
|
5959
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
5205
5960
|
for (let rowNumber = HEADER_ROW + 1; rowNumber <= sheet.rowCount; rowNumber += 1) {
|
|
5206
5961
|
const row = sheet.getRow(rowNumber);
|
|
5207
5962
|
if (row.cellCount > limits.maxCellsPerRow) {
|
|
@@ -5213,9 +5968,19 @@ function readDataSheet(sheet, limits) {
|
|
|
5213
5968
|
if (cellString(row.getCell(COLUMN.key)) === "") {
|
|
5214
5969
|
continue;
|
|
5215
5970
|
}
|
|
5216
|
-
|
|
5971
|
+
const outcome = parseRow(row);
|
|
5972
|
+
if (!outcome.ok) {
|
|
5973
|
+
malformed.push({ locale: sheet.name, row: rowNumber, column: outcome.column });
|
|
5974
|
+
continue;
|
|
5975
|
+
}
|
|
5976
|
+
if (seenKeys.has(outcome.row.key)) {
|
|
5977
|
+
duplicates.push({ locale: sheet.name, key: outcome.row.key, row: rowNumber });
|
|
5978
|
+
continue;
|
|
5979
|
+
}
|
|
5980
|
+
seenKeys.add(outcome.row.key);
|
|
5981
|
+
rows.push(outcome.row);
|
|
5217
5982
|
}
|
|
5218
|
-
return { locale: sheet.name, rows };
|
|
5983
|
+
return { sheet: { locale: sheet.name, rows }, malformed, duplicates };
|
|
5219
5984
|
}
|
|
5220
5985
|
async function loadWorkbook(bytes) {
|
|
5221
5986
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -5238,13 +6003,18 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
5238
6003
|
);
|
|
5239
6004
|
}
|
|
5240
6005
|
const sheets = [];
|
|
6006
|
+
const malformedRows = [];
|
|
6007
|
+
const duplicateKeys = [];
|
|
5241
6008
|
for (const sheet of workbook.worksheets) {
|
|
5242
6009
|
if (sheet.name === INSTRUCTIONS_SHEET_NAME) {
|
|
5243
6010
|
continue;
|
|
5244
6011
|
}
|
|
5245
|
-
|
|
6012
|
+
const read = readDataSheet(sheet, limits);
|
|
6013
|
+
sheets.push(read.sheet);
|
|
6014
|
+
malformedRows.push(...read.malformed);
|
|
6015
|
+
duplicateKeys.push(...read.duplicates);
|
|
5246
6016
|
}
|
|
5247
|
-
return { sheets };
|
|
6017
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
5248
6018
|
}
|
|
5249
6019
|
|
|
5250
6020
|
// src/flow/workbook/export-workbook.ts
|
|
@@ -5345,6 +6115,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
5345
6115
|
}
|
|
5346
6116
|
|
|
5347
6117
|
// src/flow/workbook/import-locale.ts
|
|
6118
|
+
var CLEAR_SENTINEL = "[[CLEAR]]";
|
|
5348
6119
|
var UnknownKeyError = class extends Error {
|
|
5349
6120
|
key;
|
|
5350
6121
|
constructor(key) {
|
|
@@ -5373,9 +6144,20 @@ function trackBlankDrift(row, params, buckets) {
|
|
|
5373
6144
|
buckets.blankDrifted.add(row.key);
|
|
5374
6145
|
}
|
|
5375
6146
|
}
|
|
5376
|
-
function
|
|
6147
|
+
function classifyClear(row, sourceEntry, buckets) {
|
|
6148
|
+
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
6149
|
+
buckets.mismatches.push(row.key);
|
|
6150
|
+
buckets.withheld.add(row.key);
|
|
6151
|
+
return;
|
|
6152
|
+
}
|
|
6153
|
+
buckets.accepted.set(row.key, { value: "", source: sourceEntry, cleared: true });
|
|
6154
|
+
}
|
|
6155
|
+
function classifyRows(params, buckets, liveCandidates) {
|
|
5377
6156
|
for (const row of params.sheet.rows) {
|
|
5378
6157
|
if (row.translation === "") {
|
|
6158
|
+
if (liveCandidates.has(row.key)) {
|
|
6159
|
+
buckets.unfilled.push(row.key);
|
|
6160
|
+
}
|
|
5379
6161
|
trackBlankDrift(row, params, buckets);
|
|
5380
6162
|
continue;
|
|
5381
6163
|
}
|
|
@@ -5386,9 +6168,17 @@ function classifyRows(params, buckets) {
|
|
|
5386
6168
|
if (sourceEntry === void 0) {
|
|
5387
6169
|
continue;
|
|
5388
6170
|
}
|
|
6171
|
+
if (row.translation === CLEAR_SENTINEL) {
|
|
6172
|
+
classifyClear(row, sourceEntry, buckets);
|
|
6173
|
+
continue;
|
|
6174
|
+
}
|
|
5389
6175
|
const reason = judge(row, sourceEntry, params.adapter);
|
|
5390
6176
|
if (reason === void 0) {
|
|
5391
|
-
buckets.accepted.set(row.key, {
|
|
6177
|
+
buckets.accepted.set(row.key, {
|
|
6178
|
+
value: row.translation,
|
|
6179
|
+
source: sourceEntry,
|
|
6180
|
+
cleared: false
|
|
6181
|
+
});
|
|
5392
6182
|
} else {
|
|
5393
6183
|
buckets.mismatches.push(row.key);
|
|
5394
6184
|
buckets.withheld.add(row.key);
|
|
@@ -5407,9 +6197,10 @@ function importLocale(params) {
|
|
|
5407
6197
|
accepted: /* @__PURE__ */ new Map(),
|
|
5408
6198
|
mismatches: [],
|
|
5409
6199
|
withheld: /* @__PURE__ */ new Set(),
|
|
5410
|
-
blankDrifted: /* @__PURE__ */ new Set()
|
|
6200
|
+
blankDrifted: /* @__PURE__ */ new Set(),
|
|
6201
|
+
unfilled: []
|
|
5411
6202
|
};
|
|
5412
|
-
classifyRows(params, buckets);
|
|
6203
|
+
classifyRows(params, buckets, /* @__PURE__ */ new Set([...diff2.missing, ...diff2.changed]));
|
|
5413
6204
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
5414
6205
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
5415
6206
|
const translated = [...buckets.accepted.keys()].sort();
|
|
@@ -5418,6 +6209,7 @@ function importLocale(params) {
|
|
|
5418
6209
|
locale: params.sheet.locale,
|
|
5419
6210
|
status: deriveLocaleStatus({
|
|
5420
6211
|
translated,
|
|
6212
|
+
cacheHits: [],
|
|
5421
6213
|
generated: [],
|
|
5422
6214
|
integrityMismatches,
|
|
5423
6215
|
providerFailures: [],
|
|
@@ -5428,12 +6220,16 @@ function importLocale(params) {
|
|
|
5428
6220
|
orphaned: diff2.orphaned,
|
|
5429
6221
|
pruned: [],
|
|
5430
6222
|
invalidIcuSource,
|
|
6223
|
+
cacheHits: [],
|
|
5431
6224
|
integrityMismatches,
|
|
5432
6225
|
providerFailures: [],
|
|
5433
6226
|
budgetWithheld: [],
|
|
5434
6227
|
generated: [],
|
|
5435
6228
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
5436
|
-
needsReview: []
|
|
6229
|
+
needsReview: [],
|
|
6230
|
+
unfilled: [...new Set(buckets.unfilled)].sort(),
|
|
6231
|
+
malformedRows: params.malformedRows,
|
|
6232
|
+
duplicateKeys: params.duplicateKeys
|
|
5437
6233
|
};
|
|
5438
6234
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
5439
6235
|
}
|
|
@@ -5460,6 +6256,21 @@ function mergeAccepted(target, accepted) {
|
|
|
5460
6256
|
}
|
|
5461
6257
|
return merged;
|
|
5462
6258
|
}
|
|
6259
|
+
function sheetCacheAdditions(accepted) {
|
|
6260
|
+
const record = {};
|
|
6261
|
+
for (const [, { value, source, cleared }] of accepted) {
|
|
6262
|
+
if (!cleared) {
|
|
6263
|
+
record[contentHash(source)] = value;
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
return record;
|
|
6267
|
+
}
|
|
6268
|
+
function collectSheetAdditions(byLocale, locale, additions) {
|
|
6269
|
+
if (Object.keys(additions).length === 0) {
|
|
6270
|
+
return;
|
|
6271
|
+
}
|
|
6272
|
+
byLocale.set(locale, { ...byLocale.get(locale), ...additions });
|
|
6273
|
+
}
|
|
5463
6274
|
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
5464
6275
|
const entries = {};
|
|
5465
6276
|
for (const key of merged.keys()) {
|
|
@@ -5476,11 +6287,20 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
5476
6287
|
}
|
|
5477
6288
|
return entries;
|
|
5478
6289
|
}
|
|
6290
|
+
var MissingSheetError = class extends Error {
|
|
6291
|
+
code = "WORKBOOK_SHEET_MISSING";
|
|
6292
|
+
constructor(locale) {
|
|
6293
|
+
super(
|
|
6294
|
+
`The workbook has no sheet (tab) for the configured target locale "${locale}". The tab may have been renamed, deleted, or reordered out of the workbook.`
|
|
6295
|
+
);
|
|
6296
|
+
this.name = "MissingSheetError";
|
|
6297
|
+
}
|
|
6298
|
+
};
|
|
5479
6299
|
async function runSheet(ctx, sheet, lock) {
|
|
5480
6300
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
5481
6301
|
throw new SdkError(
|
|
5482
6302
|
"CONFIG_INVALID",
|
|
5483
|
-
`The workbook has a sheet
|
|
6303
|
+
`The workbook has a sheet named "${sheet.locale}", which is not a configured target locale. It may be a renamed, added, or reordered tab; leave every language tab named exactly as exported.`
|
|
5484
6304
|
);
|
|
5485
6305
|
}
|
|
5486
6306
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -5491,10 +6311,12 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5491
6311
|
target,
|
|
5492
6312
|
baseline,
|
|
5493
6313
|
adapter: ctx.adapter,
|
|
5494
|
-
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys
|
|
6314
|
+
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6315
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6316
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
5495
6317
|
});
|
|
5496
6318
|
if (ctx.dryRun) {
|
|
5497
|
-
return { summary, lockEntries: {} };
|
|
6319
|
+
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
5498
6320
|
}
|
|
5499
6321
|
const merged = mergeAccepted(target, accepted);
|
|
5500
6322
|
if (accepted.size > 0) {
|
|
@@ -5509,7 +6331,11 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5509
6331
|
path
|
|
5510
6332
|
);
|
|
5511
6333
|
}
|
|
5512
|
-
return {
|
|
6334
|
+
return {
|
|
6335
|
+
summary,
|
|
6336
|
+
lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted),
|
|
6337
|
+
cacheAdditions: sheetCacheAdditions(accepted)
|
|
6338
|
+
};
|
|
5513
6339
|
}
|
|
5514
6340
|
async function importWorkbook(input, deps = {}) {
|
|
5515
6341
|
const config = input.config;
|
|
@@ -5534,9 +6360,12 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5534
6360
|
fs,
|
|
5535
6361
|
source: source.resource,
|
|
5536
6362
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
5537
|
-
dryRun
|
|
6363
|
+
dryRun,
|
|
6364
|
+
malformedRows: data.malformedRows,
|
|
6365
|
+
duplicateKeys: data.duplicateKeys
|
|
5538
6366
|
};
|
|
5539
6367
|
const summaries = [];
|
|
6368
|
+
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
5540
6369
|
for (const sheet of data.sheets) {
|
|
5541
6370
|
try {
|
|
5542
6371
|
let summary;
|
|
@@ -5549,6 +6378,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5549
6378
|
mode: "replace",
|
|
5550
6379
|
entries: result.lockEntries
|
|
5551
6380
|
});
|
|
6381
|
+
collectSheetAdditions(cacheAdditions, sheet.locale, result.cacheAdditions);
|
|
5552
6382
|
return result.summary;
|
|
5553
6383
|
});
|
|
5554
6384
|
}
|
|
@@ -5557,6 +6387,15 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5557
6387
|
summaries.push(failureSummary(sheet.locale, error));
|
|
5558
6388
|
}
|
|
5559
6389
|
}
|
|
6390
|
+
const presentLocales = new Set(data.sheets.map((sheet) => sheet.locale));
|
|
6391
|
+
for (const locale of config.targetLocales) {
|
|
6392
|
+
if (!presentLocales.has(locale)) {
|
|
6393
|
+
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
if (!dryRun) {
|
|
6397
|
+
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6398
|
+
}
|
|
5560
6399
|
const { succeeded, partial, failed } = partition(summaries);
|
|
5561
6400
|
return { dryRun, locales: summaries, succeeded, partial, failed };
|
|
5562
6401
|
}
|
|
@@ -5608,6 +6447,7 @@ async function watch(input, deps = {}) {
|
|
|
5608
6447
|
const cwd = input.cwd ?? process.cwd();
|
|
5609
6448
|
const debounceMs = input.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
5610
6449
|
const fs = deps.fs ?? defaultFs;
|
|
6450
|
+
resolveRunConcurrency(input.concurrency, false, input.config);
|
|
5611
6451
|
const sourcePath = localeFilePath(cwd, input.config.files.pattern, input.config.sourceLocale);
|
|
5612
6452
|
if (!await fs.fileExists(sourcePath)) {
|
|
5613
6453
|
throw new SdkError(
|
|
@@ -5620,7 +6460,10 @@ async function watch(input, deps = {}) {
|
|
|
5620
6460
|
config: input.config,
|
|
5621
6461
|
cwd,
|
|
5622
6462
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5623
|
-
...input.
|
|
6463
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6464
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
6465
|
+
...input.concurrency !== void 0 ? { concurrency: input.concurrency } : {},
|
|
6466
|
+
...input.cache !== void 0 ? { cache: input.cache } : {}
|
|
5624
6467
|
};
|
|
5625
6468
|
let state = "idle";
|
|
5626
6469
|
let pending = false;
|
|
@@ -5688,6 +6531,6 @@ async function watch(input, deps = {}) {
|
|
|
5688
6531
|
return { stop };
|
|
5689
6532
|
}
|
|
5690
6533
|
|
|
5691
|
-
export { DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
6534
|
+
export { CACHE_FILE_NAME, DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
5692
6535
|
//# sourceMappingURL=index.js.map
|
|
5693
6536
|
//# sourceMappingURL=index.js.map
|