@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.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var fs = require('fs');
|
|
4
3
|
var path = require('path');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
var fs = require('fs');
|
|
5
6
|
var cosmiconfig = require('cosmiconfig');
|
|
6
7
|
var cosmiconfigTypescriptLoader = require('cosmiconfig-typescript-loader');
|
|
7
8
|
var crypto = require('crypto');
|
|
8
9
|
var promises = require('fs/promises');
|
|
9
|
-
var zod = require('zod');
|
|
10
10
|
var Anthropic = require('@anthropic-ai/sdk');
|
|
11
11
|
var deepl = require('deepl-node');
|
|
12
12
|
var module$1 = require('module');
|
|
@@ -17,8 +17,8 @@ var icuMessageformatParser = require('@formatjs/icu-messageformat-parser');
|
|
|
17
17
|
var xmldom = require('@xmldom/xmldom');
|
|
18
18
|
var yaml = require('yaml');
|
|
19
19
|
var ExcelJS = require('exceljs');
|
|
20
|
-
var stream = require('stream');
|
|
21
20
|
var JSZip = require('jszip');
|
|
21
|
+
var stream = require('stream');
|
|
22
22
|
var chokidar = require('chokidar');
|
|
23
23
|
|
|
24
24
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -49,6 +49,103 @@ var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
|
|
|
49
49
|
var ExcelJS__default = /*#__PURE__*/_interopDefault(ExcelJS);
|
|
50
50
|
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
51
51
|
|
|
52
|
+
// src/cache/translation-memory.ts
|
|
53
|
+
var CACHE_FILE_NAME = "verbatra.cache.json";
|
|
54
|
+
var CURRENT_VERSION = 1;
|
|
55
|
+
var EMPTY_MEMORY = { version: CURRENT_VERSION, entries: {} };
|
|
56
|
+
var MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
|
|
57
|
+
var translationMemorySchema = zod.z.object({
|
|
58
|
+
version: zod.z.number().int().positive(),
|
|
59
|
+
entries: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.string())))
|
|
60
|
+
});
|
|
61
|
+
function cacheFilePath(cwd) {
|
|
62
|
+
return path.resolve(cwd, CACHE_FILE_NAME);
|
|
63
|
+
}
|
|
64
|
+
var UNUSABLE = { memory: EMPTY_MEMORY, writable: true };
|
|
65
|
+
async function readTranslationMemory(path, fs) {
|
|
66
|
+
let read;
|
|
67
|
+
try {
|
|
68
|
+
read = await fs.readFileBounded(path, MAX_CACHE_FILE_BYTES);
|
|
69
|
+
} catch {
|
|
70
|
+
return UNUSABLE;
|
|
71
|
+
}
|
|
72
|
+
if (read.kind !== "ok") {
|
|
73
|
+
return UNUSABLE;
|
|
74
|
+
}
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(read.content);
|
|
78
|
+
} catch {
|
|
79
|
+
return UNUSABLE;
|
|
80
|
+
}
|
|
81
|
+
const result = translationMemorySchema.safeParse(parsed);
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
return UNUSABLE;
|
|
84
|
+
}
|
|
85
|
+
if (result.data.version !== CURRENT_VERSION) {
|
|
86
|
+
return { memory: EMPTY_MEMORY, writable: false };
|
|
87
|
+
}
|
|
88
|
+
return { memory: result.data, writable: true };
|
|
89
|
+
}
|
|
90
|
+
function lookupMemory(memory, fingerprint, locale, contentHash2) {
|
|
91
|
+
return memory.entries[fingerprint]?.[locale]?.[contentHash2];
|
|
92
|
+
}
|
|
93
|
+
function applyAdditions(base, fingerprint, additionsByLocale) {
|
|
94
|
+
if (additionsByLocale.size === 0) {
|
|
95
|
+
return base;
|
|
96
|
+
}
|
|
97
|
+
const fingerprintEntries = {};
|
|
98
|
+
for (const [locale, hashes] of Object.entries(base.entries[fingerprint] ?? {})) {
|
|
99
|
+
fingerprintEntries[locale] = { ...hashes };
|
|
100
|
+
}
|
|
101
|
+
for (const [locale, hashes] of additionsByLocale) {
|
|
102
|
+
fingerprintEntries[locale] = { ...fingerprintEntries[locale], ...hashes };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
version: base.version,
|
|
106
|
+
entries: { ...base.entries, [fingerprint]: fingerprintEntries }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function additionsToRecord(additions) {
|
|
110
|
+
const record = {};
|
|
111
|
+
for (const addition of additions) {
|
|
112
|
+
record[addition.contentHash] = addition.value;
|
|
113
|
+
}
|
|
114
|
+
return record;
|
|
115
|
+
}
|
|
116
|
+
function sortEntries(record) {
|
|
117
|
+
return Object.entries(record).sort(([a], [b]) => a < b ? -1 : 1);
|
|
118
|
+
}
|
|
119
|
+
function serialize(memory) {
|
|
120
|
+
const entries = {};
|
|
121
|
+
for (const [fingerprint, locales] of sortEntries(memory.entries)) {
|
|
122
|
+
const localeMap = {};
|
|
123
|
+
for (const [locale, hashes] of sortEntries(locales)) {
|
|
124
|
+
localeMap[locale] = Object.fromEntries(sortEntries(hashes));
|
|
125
|
+
}
|
|
126
|
+
entries[fingerprint] = localeMap;
|
|
127
|
+
}
|
|
128
|
+
return `${JSON.stringify({ version: memory.version, entries }, null, 2)}
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
async function writeTranslationMemory(path, memory, fs) {
|
|
132
|
+
await fs.writeFile(path, serialize(memory));
|
|
133
|
+
}
|
|
134
|
+
async function feedTranslationMemory(cwd, fs, fingerprint, additionsByLocale) {
|
|
135
|
+
if (additionsByLocale.size === 0) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const path = cacheFilePath(cwd);
|
|
140
|
+
const { memory, writable } = await readTranslationMemory(path, fs);
|
|
141
|
+
if (!writable) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await writeTranslationMemory(path, applyAdditions(memory, fingerprint, additionsByLocale), fs);
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
52
149
|
// src/config/define-config.ts
|
|
53
150
|
function defineConfig(config) {
|
|
54
151
|
return config;
|
|
@@ -242,7 +339,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
242
339
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
243
340
|
var FNV_PRIME = 1099511628211n;
|
|
244
341
|
var U64_MASK = (1n << 64n) - 1n;
|
|
245
|
-
function
|
|
342
|
+
function stableStringHash(input) {
|
|
246
343
|
let hash = FNV_OFFSET_BASIS;
|
|
247
344
|
for (let index = 0; index < input.length; index += 1) {
|
|
248
345
|
hash ^= BigInt(input.charCodeAt(index));
|
|
@@ -263,7 +360,7 @@ function canonicalize(entry) {
|
|
|
263
360
|
]);
|
|
264
361
|
}
|
|
265
362
|
function contentHash(entry) {
|
|
266
|
-
return
|
|
363
|
+
return stableStringHash(canonicalize(entry));
|
|
267
364
|
}
|
|
268
365
|
function sorted(keys) {
|
|
269
366
|
return [...keys].sort();
|
|
@@ -308,7 +405,8 @@ var SUPPORTED_FORMATS = [
|
|
|
308
405
|
"ngx-translate-json",
|
|
309
406
|
"xliff",
|
|
310
407
|
"yaml",
|
|
311
|
-
"arb"
|
|
408
|
+
"arb",
|
|
409
|
+
"properties"
|
|
312
410
|
];
|
|
313
411
|
var supportedFormatSchema = zod.z.enum(SUPPORTED_FORMATS);
|
|
314
412
|
var translationEntrySchema = zod.z.object({
|
|
@@ -1250,14 +1348,14 @@ function throwAbort(signal) {
|
|
|
1250
1348
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1251
1349
|
}
|
|
1252
1350
|
function delay(ms, signal) {
|
|
1253
|
-
return new Promise((
|
|
1351
|
+
return new Promise((resolve10) => {
|
|
1254
1352
|
const onAbort = () => {
|
|
1255
1353
|
clearTimeout(timer);
|
|
1256
|
-
|
|
1354
|
+
resolve10();
|
|
1257
1355
|
};
|
|
1258
1356
|
const timer = setTimeout(() => {
|
|
1259
1357
|
signal?.removeEventListener("abort", onAbort);
|
|
1260
|
-
|
|
1358
|
+
resolve10();
|
|
1261
1359
|
}, ms);
|
|
1262
1360
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1263
1361
|
});
|
|
@@ -1995,8 +2093,8 @@ async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
|
1995
2093
|
|
|
1996
2094
|
// src/lock/lock-file.ts
|
|
1997
2095
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1998
|
-
var
|
|
1999
|
-
var EMPTY_LOCK = { version:
|
|
2096
|
+
var CURRENT_VERSION2 = 1;
|
|
2097
|
+
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
2000
2098
|
var MAX_LOCK_FILE_BYTES = 16 * 1024 * 1024;
|
|
2001
2099
|
var lockFileSchema = zod.z.object({
|
|
2002
2100
|
version: zod.z.number().int().positive(),
|
|
@@ -2025,10 +2123,10 @@ function parseLockFileRead(read, path) {
|
|
|
2025
2123
|
if (!result.success) {
|
|
2026
2124
|
throw new SdkError("LOCK_FILE_INVALID", `The lock-file at ${path} has an unexpected shape.`);
|
|
2027
2125
|
}
|
|
2028
|
-
if (result.data.version !==
|
|
2126
|
+
if (result.data.version !== CURRENT_VERSION2) {
|
|
2029
2127
|
throw new SdkError(
|
|
2030
2128
|
"LOCK_FILE_INVALID",
|
|
2031
|
-
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${
|
|
2129
|
+
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${CURRENT_VERSION2}.`
|
|
2032
2130
|
);
|
|
2033
2131
|
}
|
|
2034
2132
|
return result.data;
|
|
@@ -2818,7 +2916,7 @@ function createTreeFileAdapter(options) {
|
|
|
2818
2916
|
extensions,
|
|
2819
2917
|
sniff,
|
|
2820
2918
|
parse: parse3,
|
|
2821
|
-
serialize,
|
|
2919
|
+
serialize: serialize2,
|
|
2822
2920
|
deriveEntry,
|
|
2823
2921
|
extractPlaceholders,
|
|
2824
2922
|
computeInvalidIcuKeys,
|
|
@@ -2853,7 +2951,7 @@ function createTreeFileAdapter(options) {
|
|
|
2853
2951
|
},
|
|
2854
2952
|
async write(resource, filePath) {
|
|
2855
2953
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
2856
|
-
await atomicWriteFile(filePath,
|
|
2954
|
+
await atomicWriteFile(filePath, serialize2(tree));
|
|
2857
2955
|
}
|
|
2858
2956
|
};
|
|
2859
2957
|
}
|
|
@@ -3110,6 +3208,401 @@ function createNgxTranslateJsonAdapter() {
|
|
|
3110
3208
|
keyMode: "path-notation"
|
|
3111
3209
|
});
|
|
3112
3210
|
}
|
|
3211
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
3212
|
+
try {
|
|
3213
|
+
return parseEntries(content, namespace);
|
|
3214
|
+
} catch (error) {
|
|
3215
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
function createFlatFileAdapter(options) {
|
|
3219
|
+
const {
|
|
3220
|
+
format,
|
|
3221
|
+
extensions,
|
|
3222
|
+
sniff,
|
|
3223
|
+
parseEntries,
|
|
3224
|
+
serializeEntries,
|
|
3225
|
+
extractPlaceholders,
|
|
3226
|
+
validateMessage,
|
|
3227
|
+
computeInvalidIcuKeys
|
|
3228
|
+
} = options;
|
|
3229
|
+
return {
|
|
3230
|
+
format,
|
|
3231
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
3232
|
+
extractPlaceholders,
|
|
3233
|
+
validateMessage: validateMessage ?? (() => true),
|
|
3234
|
+
async read(filePath, locale) {
|
|
3235
|
+
const content = await readFileContent(filePath);
|
|
3236
|
+
const namespace = namespaceOf(filePath);
|
|
3237
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
3238
|
+
const resource = { locale, namespace, format, entries };
|
|
3239
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3240
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3241
|
+
},
|
|
3242
|
+
async write(resource, filePath) {
|
|
3243
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
3244
|
+
await atomicWriteFile(filePath, data);
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordinal", "choice"]);
|
|
3249
|
+
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3250
|
+
function matchingBraces(value) {
|
|
3251
|
+
const close = new Int32Array(value.length).fill(-1);
|
|
3252
|
+
const open3 = [];
|
|
3253
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
3254
|
+
const char = value[i];
|
|
3255
|
+
if (char === "{") {
|
|
3256
|
+
open3.push(i);
|
|
3257
|
+
} else if (char === "}") {
|
|
3258
|
+
const start = open3.pop();
|
|
3259
|
+
if (start !== void 0) {
|
|
3260
|
+
close[start] = i;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
return close;
|
|
3265
|
+
}
|
|
3266
|
+
function findNameEnd(value, from, limit) {
|
|
3267
|
+
for (let i = from; i < limit; i += 1) {
|
|
3268
|
+
const char = value[i];
|
|
3269
|
+
if (char === ",") {
|
|
3270
|
+
return { comma: i, hasBrace: false };
|
|
3271
|
+
}
|
|
3272
|
+
if (char === "{" || char === "}") {
|
|
3273
|
+
return { comma: -1, hasBrace: true };
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
return { comma: -1, hasBrace: false };
|
|
3277
|
+
}
|
|
3278
|
+
function parseArgumentAt(value, open3, close) {
|
|
3279
|
+
const { comma, hasBrace } = findNameEnd(value, open3 + 1, close);
|
|
3280
|
+
if (hasBrace) {
|
|
3281
|
+
return null;
|
|
3282
|
+
}
|
|
3283
|
+
const nameEnd = comma === -1 ? close : comma;
|
|
3284
|
+
const name = value.slice(open3 + 1, nameEnd).trim();
|
|
3285
|
+
if (!ARGUMENT_NAME.test(name)) {
|
|
3286
|
+
return null;
|
|
3287
|
+
}
|
|
3288
|
+
if (comma === -1) {
|
|
3289
|
+
return { name, type: null, styleStart: -1 };
|
|
3290
|
+
}
|
|
3291
|
+
const secondComma = value.indexOf(",", comma + 1);
|
|
3292
|
+
if (secondComma === -1 || secondComma >= close) {
|
|
3293
|
+
return { name, type: value.slice(comma + 1, close).trim(), styleStart: -1 };
|
|
3294
|
+
}
|
|
3295
|
+
return { name, type: value.slice(comma + 1, secondComma).trim(), styleStart: secondComma + 1 };
|
|
3296
|
+
}
|
|
3297
|
+
function canonicalToken(value, arg, close) {
|
|
3298
|
+
if (arg.type === null) {
|
|
3299
|
+
return `{${arg.name}}`;
|
|
3300
|
+
}
|
|
3301
|
+
if (arg.styleStart === -1) {
|
|
3302
|
+
return `{${arg.name},${arg.type}}`;
|
|
3303
|
+
}
|
|
3304
|
+
return `{${arg.name},${arg.type},${value.slice(arg.styleStart, close).trim()}}`;
|
|
3305
|
+
}
|
|
3306
|
+
function emitArgument(value, close, arg, closeIndex, out) {
|
|
3307
|
+
if (arg.type !== null && SUBMESSAGE_TYPES.has(arg.type)) {
|
|
3308
|
+
out.push(`{${arg.name},${arg.type}}`);
|
|
3309
|
+
if (arg.styleStart !== -1) {
|
|
3310
|
+
scanRange(value, close, arg.styleStart, closeIndex, false, out);
|
|
3311
|
+
}
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
out.push(canonicalToken(value, arg, closeIndex));
|
|
3315
|
+
}
|
|
3316
|
+
function scanRange(value, close, start, end, topLevel, out) {
|
|
3317
|
+
let i = start;
|
|
3318
|
+
while (i < end) {
|
|
3319
|
+
if (value[i] !== "{") {
|
|
3320
|
+
i += 1;
|
|
3321
|
+
continue;
|
|
3322
|
+
}
|
|
3323
|
+
if (topLevel && value[i + 1] === "{") {
|
|
3324
|
+
i += 2;
|
|
3325
|
+
continue;
|
|
3326
|
+
}
|
|
3327
|
+
const closeIndex = close[i] ?? -1;
|
|
3328
|
+
if (closeIndex === -1 || closeIndex >= end) {
|
|
3329
|
+
i += 1;
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
const arg = parseArgumentAt(value, i, closeIndex);
|
|
3333
|
+
if (arg === null) {
|
|
3334
|
+
i += 1;
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
emitArgument(value, close, arg, closeIndex, out);
|
|
3338
|
+
i = closeIndex + 1;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
function extractPropertiesPlaceholders(value) {
|
|
3342
|
+
const out = [];
|
|
3343
|
+
scanRange(value, matchingBraces(value), 0, value.length, true, out);
|
|
3344
|
+
return out;
|
|
3345
|
+
}
|
|
3346
|
+
var UNICODE_ESCAPE = /^[0-9a-fA-F]{4}$/;
|
|
3347
|
+
var LEADING_WHITESPACE = /^[ \t\f]+/;
|
|
3348
|
+
var TRAILING_TERMINATOR = /(?:\r\n|\r|\n)$/;
|
|
3349
|
+
function detectLineTerminator(content) {
|
|
3350
|
+
if (content.includes("\r\n")) {
|
|
3351
|
+
return "\r\n";
|
|
3352
|
+
}
|
|
3353
|
+
return content.includes("\r") ? "\r" : "\n";
|
|
3354
|
+
}
|
|
3355
|
+
function isPropertiesWhitespace(char) {
|
|
3356
|
+
return char === " " || char === " " || char === "\f";
|
|
3357
|
+
}
|
|
3358
|
+
function splitPhysicalLines(content) {
|
|
3359
|
+
if (content === "") {
|
|
3360
|
+
return [];
|
|
3361
|
+
}
|
|
3362
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
3363
|
+
if (TRAILING_TERMINATOR.test(content)) {
|
|
3364
|
+
lines.pop();
|
|
3365
|
+
}
|
|
3366
|
+
return lines;
|
|
3367
|
+
}
|
|
3368
|
+
function countTrailingBackslashes(line) {
|
|
3369
|
+
let count = 0;
|
|
3370
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) {
|
|
3371
|
+
count += 1;
|
|
3372
|
+
}
|
|
3373
|
+
return count;
|
|
3374
|
+
}
|
|
3375
|
+
function isContinued(line) {
|
|
3376
|
+
return countTrailingBackslashes(line) % 2 === 1;
|
|
3377
|
+
}
|
|
3378
|
+
function isBlankLine(line) {
|
|
3379
|
+
return line.trim() === "";
|
|
3380
|
+
}
|
|
3381
|
+
function isCommentLine(line) {
|
|
3382
|
+
const trimmed = line.replace(LEADING_WHITESPACE, "");
|
|
3383
|
+
return trimmed.startsWith("#") || trimmed.startsWith("!");
|
|
3384
|
+
}
|
|
3385
|
+
function joinContinuation(lines, start) {
|
|
3386
|
+
const pieces = [];
|
|
3387
|
+
let index = start;
|
|
3388
|
+
let current = lines[start] ?? "";
|
|
3389
|
+
while (isContinued(current) && index + 1 < lines.length) {
|
|
3390
|
+
pieces.push(current.slice(0, -1));
|
|
3391
|
+
index += 1;
|
|
3392
|
+
current = (lines[index] ?? "").replace(LEADING_WHITESPACE, "");
|
|
3393
|
+
}
|
|
3394
|
+
if (isContinued(current)) {
|
|
3395
|
+
current = current.slice(0, -1);
|
|
3396
|
+
}
|
|
3397
|
+
pieces.push(current);
|
|
3398
|
+
return { logical: pieces.join(""), nextIndex: index + 1 };
|
|
3399
|
+
}
|
|
3400
|
+
function decodeSimpleEscape(char) {
|
|
3401
|
+
switch (char) {
|
|
3402
|
+
case "t":
|
|
3403
|
+
return " ";
|
|
3404
|
+
case "n":
|
|
3405
|
+
return "\n";
|
|
3406
|
+
case "r":
|
|
3407
|
+
return "\r";
|
|
3408
|
+
case "f":
|
|
3409
|
+
return "\f";
|
|
3410
|
+
default:
|
|
3411
|
+
return char;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
function decodeUnicodeEscape(raw, at) {
|
|
3415
|
+
const hex = raw.slice(at, at + 4);
|
|
3416
|
+
if (!UNICODE_ESCAPE.test(hex)) {
|
|
3417
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file has a malformed unicode escape.");
|
|
3418
|
+
}
|
|
3419
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
3420
|
+
}
|
|
3421
|
+
function decodeEscapes(raw) {
|
|
3422
|
+
let out = "";
|
|
3423
|
+
let i = 0;
|
|
3424
|
+
while (i < raw.length) {
|
|
3425
|
+
const char = raw[i];
|
|
3426
|
+
if (char !== "\\") {
|
|
3427
|
+
out += char;
|
|
3428
|
+
i += 1;
|
|
3429
|
+
continue;
|
|
3430
|
+
}
|
|
3431
|
+
const next = raw[i + 1];
|
|
3432
|
+
if (next === void 0) {
|
|
3433
|
+
break;
|
|
3434
|
+
}
|
|
3435
|
+
if (next === "u") {
|
|
3436
|
+
out += decodeUnicodeEscape(raw, i + 2);
|
|
3437
|
+
i += 6;
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
out += decodeSimpleEscape(next);
|
|
3441
|
+
i += 2;
|
|
3442
|
+
}
|
|
3443
|
+
return out;
|
|
3444
|
+
}
|
|
3445
|
+
function keyEndIndex(logical, from) {
|
|
3446
|
+
let i = from;
|
|
3447
|
+
while (i < logical.length) {
|
|
3448
|
+
const char = logical[i];
|
|
3449
|
+
if (char === "\\") {
|
|
3450
|
+
i += 2;
|
|
3451
|
+
continue;
|
|
3452
|
+
}
|
|
3453
|
+
if (char === void 0 || isPropertiesWhitespace(char) || char === "=" || char === ":") {
|
|
3454
|
+
break;
|
|
3455
|
+
}
|
|
3456
|
+
i += 1;
|
|
3457
|
+
}
|
|
3458
|
+
return i;
|
|
3459
|
+
}
|
|
3460
|
+
function skipWhitespace(logical, from) {
|
|
3461
|
+
let i = from;
|
|
3462
|
+
while (i < logical.length) {
|
|
3463
|
+
const char = logical[i];
|
|
3464
|
+
if (char === void 0 || !isPropertiesWhitespace(char)) {
|
|
3465
|
+
break;
|
|
3466
|
+
}
|
|
3467
|
+
i += 1;
|
|
3468
|
+
}
|
|
3469
|
+
return i;
|
|
3470
|
+
}
|
|
3471
|
+
function parseEntryLine(logical) {
|
|
3472
|
+
const keyStart = skipWhitespace(logical, 0);
|
|
3473
|
+
const keyEnd = keyEndIndex(logical, keyStart);
|
|
3474
|
+
const afterKey = skipWhitespace(logical, keyEnd);
|
|
3475
|
+
const separator = logical[afterKey];
|
|
3476
|
+
const valueStart = separator === "=" || separator === ":" ? skipWhitespace(logical, afterKey + 1) : afterKey;
|
|
3477
|
+
return {
|
|
3478
|
+
key: decodeEscapes(logical.slice(keyStart, keyEnd)),
|
|
3479
|
+
value: decodeEscapes(logical.slice(valueStart))
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
function parseItems(content) {
|
|
3483
|
+
const lines = splitPhysicalLines(content);
|
|
3484
|
+
const items = [];
|
|
3485
|
+
let i = 0;
|
|
3486
|
+
while (i < lines.length) {
|
|
3487
|
+
const line = lines[i] ?? "";
|
|
3488
|
+
if (isBlankLine(line) || isCommentLine(line)) {
|
|
3489
|
+
items.push({ kind: "raw", text: line });
|
|
3490
|
+
i += 1;
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
const { logical, nextIndex } = joinContinuation(lines, i);
|
|
3494
|
+
items.push({ kind: "entry", ...parseEntryLine(logical) });
|
|
3495
|
+
i = nextIndex;
|
|
3496
|
+
}
|
|
3497
|
+
return items;
|
|
3498
|
+
}
|
|
3499
|
+
function parsePropertiesEntries(content, namespace) {
|
|
3500
|
+
const map = /* @__PURE__ */ new Map();
|
|
3501
|
+
for (const item of parseItems(content)) {
|
|
3502
|
+
if (item.kind === "entry") {
|
|
3503
|
+
map.set(item.key, {
|
|
3504
|
+
key: item.key,
|
|
3505
|
+
namespace,
|
|
3506
|
+
value: item.value,
|
|
3507
|
+
placeholders: extractPropertiesPlaceholders(item.value),
|
|
3508
|
+
isPlural: false
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
return map;
|
|
3513
|
+
}
|
|
3514
|
+
function unicodeEscape(code) {
|
|
3515
|
+
return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3516
|
+
}
|
|
3517
|
+
function escapeChar(char, isFirst, isKey) {
|
|
3518
|
+
switch (char) {
|
|
3519
|
+
case "\\":
|
|
3520
|
+
return "\\\\";
|
|
3521
|
+
case " ":
|
|
3522
|
+
return "\\t";
|
|
3523
|
+
case "\n":
|
|
3524
|
+
return "\\n";
|
|
3525
|
+
case "\r":
|
|
3526
|
+
return "\\r";
|
|
3527
|
+
case "\f":
|
|
3528
|
+
return "\\f";
|
|
3529
|
+
case " ":
|
|
3530
|
+
return isKey || isFirst ? "\\ " : " ";
|
|
3531
|
+
}
|
|
3532
|
+
if (isKey && (char === "=" || char === ":" || char === "#" || char === "!")) {
|
|
3533
|
+
return `\\${char}`;
|
|
3534
|
+
}
|
|
3535
|
+
const code = char.charCodeAt(0);
|
|
3536
|
+
return code < 32 || code > 126 ? unicodeEscape(code) : char;
|
|
3537
|
+
}
|
|
3538
|
+
function escapeString(input, isKey) {
|
|
3539
|
+
let out = "";
|
|
3540
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
3541
|
+
const char = input[i];
|
|
3542
|
+
if (char !== void 0) {
|
|
3543
|
+
out += escapeChar(char, i === 0, isKey);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
return out;
|
|
3547
|
+
}
|
|
3548
|
+
function formatEntry(key, value) {
|
|
3549
|
+
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3550
|
+
}
|
|
3551
|
+
function isFileNotFound(error) {
|
|
3552
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3553
|
+
}
|
|
3554
|
+
async function readStructure(filePath) {
|
|
3555
|
+
let outcome;
|
|
3556
|
+
try {
|
|
3557
|
+
outcome = await readBounded2(filePath);
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
if (isFileNotFound(error)) {
|
|
3560
|
+
return { items: [], terminator: "\n" };
|
|
3561
|
+
}
|
|
3562
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination file could not be read.");
|
|
3563
|
+
}
|
|
3564
|
+
if (outcome.kind === "not-a-file") {
|
|
3565
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
3566
|
+
}
|
|
3567
|
+
if (outcome.kind === "too-large") {
|
|
3568
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
3569
|
+
}
|
|
3570
|
+
return {
|
|
3571
|
+
items: parseItems(outcome.content),
|
|
3572
|
+
terminator: detectLineTerminator(outcome.content)
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
async function serializePropertiesEntries(entries, filePath) {
|
|
3576
|
+
const { items, terminator } = await readStructure(filePath);
|
|
3577
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3578
|
+
const lines = [];
|
|
3579
|
+
for (const item of items) {
|
|
3580
|
+
if (item.kind === "raw") {
|
|
3581
|
+
lines.push(item.text);
|
|
3582
|
+
continue;
|
|
3583
|
+
}
|
|
3584
|
+
const entry = entries.get(item.key);
|
|
3585
|
+
if (entry !== void 0 && !emitted.has(item.key)) {
|
|
3586
|
+
lines.push(formatEntry(item.key, entry.value));
|
|
3587
|
+
emitted.add(item.key);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
for (const [key, entry] of entries) {
|
|
3591
|
+
if (!emitted.has(key)) {
|
|
3592
|
+
lines.push(formatEntry(key, entry.value));
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return lines.length === 0 ? "" : `${lines.join(terminator)}${terminator}`;
|
|
3596
|
+
}
|
|
3597
|
+
function createPropertiesAdapter() {
|
|
3598
|
+
return createFlatFileAdapter({
|
|
3599
|
+
format: "properties",
|
|
3600
|
+
extensions: [".properties"],
|
|
3601
|
+
parseEntries: parsePropertiesEntries,
|
|
3602
|
+
serializeEntries: serializePropertiesEntries,
|
|
3603
|
+
extractPlaceholders: extractPropertiesPlaceholders
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3113
3606
|
var AdapterRegistry = class {
|
|
3114
3607
|
adapters = [];
|
|
3115
3608
|
/**
|
|
@@ -3187,43 +3680,6 @@ function createVueI18nJsonAdapter() {
|
|
|
3187
3680
|
})
|
|
3188
3681
|
});
|
|
3189
3682
|
}
|
|
3190
|
-
function toEntries2(content, namespace, parseEntries) {
|
|
3191
|
-
try {
|
|
3192
|
-
return parseEntries(content, namespace);
|
|
3193
|
-
} catch (error) {
|
|
3194
|
-
rethrowStructured(error, "The file could not be parsed.");
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
function createFlatFileAdapter(options) {
|
|
3198
|
-
const {
|
|
3199
|
-
format,
|
|
3200
|
-
extensions,
|
|
3201
|
-
sniff,
|
|
3202
|
-
parseEntries,
|
|
3203
|
-
serializeEntries,
|
|
3204
|
-
extractPlaceholders,
|
|
3205
|
-
validateMessage,
|
|
3206
|
-
computeInvalidIcuKeys
|
|
3207
|
-
} = options;
|
|
3208
|
-
return {
|
|
3209
|
-
format,
|
|
3210
|
-
canHandle: buildCanHandle(extensions, sniff),
|
|
3211
|
-
extractPlaceholders,
|
|
3212
|
-
validateMessage: validateMessage ?? (() => true),
|
|
3213
|
-
async read(filePath, locale) {
|
|
3214
|
-
const content = await readFileContent(filePath);
|
|
3215
|
-
const namespace = namespaceOf(filePath);
|
|
3216
|
-
const entries = toEntries2(content, namespace, parseEntries);
|
|
3217
|
-
const resource = { locale, namespace, format, entries };
|
|
3218
|
-
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3219
|
-
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3220
|
-
},
|
|
3221
|
-
async write(resource, filePath) {
|
|
3222
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3223
|
-
await atomicWriteFile(filePath, data);
|
|
3224
|
-
}
|
|
3225
|
-
};
|
|
3226
|
-
}
|
|
3227
3683
|
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
3228
3684
|
function extractXliffPlaceholders(value) {
|
|
3229
3685
|
const result = [];
|
|
@@ -3534,7 +3990,7 @@ function createYamlAdapter() {
|
|
|
3534
3990
|
});
|
|
3535
3991
|
}
|
|
3536
3992
|
function createDefaultRegistry() {
|
|
3537
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
3993
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter()).register(createPropertiesAdapter());
|
|
3538
3994
|
}
|
|
3539
3995
|
|
|
3540
3996
|
// src/selection/select-adapter.ts
|
|
@@ -3655,6 +4111,28 @@ async function diff(input, deps = {}) {
|
|
|
3655
4111
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3656
4112
|
}
|
|
3657
4113
|
|
|
4114
|
+
// src/cache/fingerprint.ts
|
|
4115
|
+
function fingerprintModel(provider) {
|
|
4116
|
+
const options = provider.options;
|
|
4117
|
+
const model = options.model;
|
|
4118
|
+
return typeof model === "string" ? model : null;
|
|
4119
|
+
}
|
|
4120
|
+
function sortGlossary(glossary) {
|
|
4121
|
+
if (glossary === void 0) {
|
|
4122
|
+
return {};
|
|
4123
|
+
}
|
|
4124
|
+
return Object.fromEntries(Object.entries(glossary).sort(([a], [b]) => a < b ? -1 : 1));
|
|
4125
|
+
}
|
|
4126
|
+
function computeFingerprint(config) {
|
|
4127
|
+
const canonical = JSON.stringify({
|
|
4128
|
+
provider: config.provider.id,
|
|
4129
|
+
model: fingerprintModel(config.provider),
|
|
4130
|
+
tone: config.tone ?? null,
|
|
4131
|
+
glossary: sortGlossary(config.glossary)
|
|
4132
|
+
});
|
|
4133
|
+
return stableStringHash(canonical);
|
|
4134
|
+
}
|
|
4135
|
+
|
|
3658
4136
|
// src/flow/integrity-gate.ts
|
|
3659
4137
|
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3660
4138
|
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
@@ -3667,6 +4145,9 @@ function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
|
3667
4145
|
if (assessValueDegeneracy(sourceEntry.value, candidateValue).degenerate) {
|
|
3668
4146
|
return { accepted: false, reason: "degenerate" };
|
|
3669
4147
|
}
|
|
4148
|
+
if (sourceEntry.value.trim() !== "" && candidateValue.trim() === "") {
|
|
4149
|
+
return { accepted: false, reason: "empty" };
|
|
4150
|
+
}
|
|
3670
4151
|
return { accepted: true };
|
|
3671
4152
|
}
|
|
3672
4153
|
|
|
@@ -3705,6 +4186,12 @@ async function editEntry(input, deps = {}) {
|
|
|
3705
4186
|
mode: "merge",
|
|
3706
4187
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3707
4188
|
});
|
|
4189
|
+
await feedTranslationMemory(
|
|
4190
|
+
cwd,
|
|
4191
|
+
fs,
|
|
4192
|
+
computeFingerprint(config),
|
|
4193
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: input.value }]])
|
|
4194
|
+
);
|
|
3708
4195
|
return { accepted: true, value: input.value };
|
|
3709
4196
|
});
|
|
3710
4197
|
}
|
|
@@ -3922,13 +4409,19 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
3922
4409
|
mode: "merge",
|
|
3923
4410
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3924
4411
|
});
|
|
4412
|
+
await feedTranslationMemory(
|
|
4413
|
+
cwd,
|
|
4414
|
+
fs,
|
|
4415
|
+
computeFingerprint(config),
|
|
4416
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: value }]])
|
|
4417
|
+
);
|
|
3925
4418
|
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3926
4419
|
return { accepted: true, value, reviewReasons };
|
|
3927
4420
|
});
|
|
3928
4421
|
}
|
|
3929
4422
|
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3930
4423
|
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3931
|
-
var
|
|
4424
|
+
var CURRENT_VERSION3 = 1;
|
|
3932
4425
|
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3933
4426
|
var reviewReasonCodeSchema = zod.z.enum([
|
|
3934
4427
|
"LENGTH_RATIO_OUTLIER",
|
|
@@ -3978,7 +4471,7 @@ function toRunStatusLocale(locale) {
|
|
|
3978
4471
|
}
|
|
3979
4472
|
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3980
4473
|
return {
|
|
3981
|
-
version:
|
|
4474
|
+
version: CURRENT_VERSION3,
|
|
3982
4475
|
generatedAt,
|
|
3983
4476
|
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3984
4477
|
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
@@ -4011,7 +4504,7 @@ async function readRunStatusFile(path, fs) {
|
|
|
4011
4504
|
return void 0;
|
|
4012
4505
|
}
|
|
4013
4506
|
const result = runStatusFileSchema.safeParse(parsed);
|
|
4014
|
-
if (!result.success || result.data.version !==
|
|
4507
|
+
if (!result.success || result.data.version !== CURRENT_VERSION3) {
|
|
4015
4508
|
return void 0;
|
|
4016
4509
|
}
|
|
4017
4510
|
return fromParsed(result.data);
|
|
@@ -4090,12 +4583,16 @@ function failureSummary(locale, error) {
|
|
|
4090
4583
|
orphaned: [],
|
|
4091
4584
|
pruned: [],
|
|
4092
4585
|
invalidIcuSource: [],
|
|
4586
|
+
cacheHits: [],
|
|
4093
4587
|
integrityMismatches: [],
|
|
4094
4588
|
providerFailures: [],
|
|
4095
4589
|
budgetWithheld: [],
|
|
4096
4590
|
generated: [],
|
|
4097
4591
|
notices: [],
|
|
4098
4592
|
needsReview: [],
|
|
4593
|
+
unfilled: [],
|
|
4594
|
+
malformedRows: [],
|
|
4595
|
+
duplicateKeys: [],
|
|
4099
4596
|
error: describeError(error)
|
|
4100
4597
|
};
|
|
4101
4598
|
}
|
|
@@ -4104,7 +4601,7 @@ function deriveLocaleStatus(parts) {
|
|
|
4104
4601
|
if (!withheld) {
|
|
4105
4602
|
return "succeeded";
|
|
4106
4603
|
}
|
|
4107
|
-
const accepted = parts.translated.length > 0 || parts.generated.length > 0;
|
|
4604
|
+
const accepted = parts.translated.length > 0 || parts.cacheHits.length > 0 || parts.generated.length > 0;
|
|
4108
4605
|
return accepted ? "partial" : "failed";
|
|
4109
4606
|
}
|
|
4110
4607
|
function partition(locales) {
|
|
@@ -4424,6 +4921,101 @@ function foldGenerationItem(item, result, adapter, accepted, withheld, providerF
|
|
|
4424
4921
|
}
|
|
4425
4922
|
|
|
4426
4923
|
// src/flow/locale-run.ts
|
|
4924
|
+
function partitionCacheHits(params, toTranslate) {
|
|
4925
|
+
const cache = params.cache;
|
|
4926
|
+
const hits = /* @__PURE__ */ new Map();
|
|
4927
|
+
if (cache === void 0) {
|
|
4928
|
+
return { hits, misses: toTranslate };
|
|
4929
|
+
}
|
|
4930
|
+
const misses = [];
|
|
4931
|
+
for (const key of toTranslate) {
|
|
4932
|
+
const source = params.source.entries.get(key);
|
|
4933
|
+
if (source === void 0) {
|
|
4934
|
+
continue;
|
|
4935
|
+
}
|
|
4936
|
+
const cached = lookupMemory(
|
|
4937
|
+
cache.snapshot,
|
|
4938
|
+
cache.fingerprint,
|
|
4939
|
+
params.targetLocale,
|
|
4940
|
+
contentHash(source)
|
|
4941
|
+
);
|
|
4942
|
+
if (cached !== void 0 && gateCandidateValue(source, cached, params.adapter).accepted) {
|
|
4943
|
+
hits.set(key, { value: cached, source });
|
|
4944
|
+
} else {
|
|
4945
|
+
misses.push(key);
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
return { hits, misses };
|
|
4949
|
+
}
|
|
4950
|
+
function collectCacheAdditions(params, accepted, cacheHitKeys) {
|
|
4951
|
+
if (params.cache === void 0) {
|
|
4952
|
+
return [];
|
|
4953
|
+
}
|
|
4954
|
+
const additions = [];
|
|
4955
|
+
for (const [key, entry] of accepted) {
|
|
4956
|
+
if (!cacheHitKeys.has(key)) {
|
|
4957
|
+
additions.push({ contentHash: contentHash(entry.source), value: entry.value });
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
return additions;
|
|
4961
|
+
}
|
|
4962
|
+
function groupMissesByContent(params, misses) {
|
|
4963
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4964
|
+
for (const key of misses) {
|
|
4965
|
+
const source = params.source.entries.get(key);
|
|
4966
|
+
if (source === void 0) {
|
|
4967
|
+
continue;
|
|
4968
|
+
}
|
|
4969
|
+
const existing = byHash.get(contentHash(source));
|
|
4970
|
+
if (existing === void 0) {
|
|
4971
|
+
byHash.set(contentHash(source), { representative: key, duplicates: [] });
|
|
4972
|
+
} else {
|
|
4973
|
+
existing.duplicates.push(key);
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
return [...byHash.values()];
|
|
4977
|
+
}
|
|
4978
|
+
function fanOutContentDuplicates(params, groups, outcome) {
|
|
4979
|
+
for (const group of groups) {
|
|
4980
|
+
if (group.duplicates.length > 0) {
|
|
4981
|
+
applyGroupOutcome(params, group, outcome);
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
function applyGroupOutcome(params, group, outcome) {
|
|
4986
|
+
const acceptedRepresentative = outcome.accepted.get(group.representative);
|
|
4987
|
+
if (acceptedRepresentative !== void 0) {
|
|
4988
|
+
fanOutAccepted(params, group, acceptedRepresentative, outcome);
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
withheldBucketFor(group.representative, outcome).push(...group.duplicates);
|
|
4992
|
+
}
|
|
4993
|
+
function fanOutAccepted(params, group, acceptedRepresentative, outcome) {
|
|
4994
|
+
const flag = outcome.reviewFlags.get(group.representative);
|
|
4995
|
+
for (const key of group.duplicates) {
|
|
4996
|
+
const source = params.source.entries.get(key);
|
|
4997
|
+
if (source === void 0) {
|
|
4998
|
+
continue;
|
|
4999
|
+
}
|
|
5000
|
+
if (!gateCandidateValue(source, acceptedRepresentative.value, params.adapter).accepted) {
|
|
5001
|
+
outcome.integrityMismatches.push(key);
|
|
5002
|
+
continue;
|
|
5003
|
+
}
|
|
5004
|
+
outcome.accepted.set(key, { value: acceptedRepresentative.value, source });
|
|
5005
|
+
if (flag !== void 0) {
|
|
5006
|
+
outcome.reviewFlags.set(key, flag);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
5010
|
+
function withheldBucketFor(representative, outcome) {
|
|
5011
|
+
if (outcome.integrityMismatches.includes(representative)) {
|
|
5012
|
+
return outcome.integrityMismatches;
|
|
5013
|
+
}
|
|
5014
|
+
if (outcome.budgetWithheld.includes(representative)) {
|
|
5015
|
+
return outcome.budgetWithheld;
|
|
5016
|
+
}
|
|
5017
|
+
return outcome.providerFailures;
|
|
5018
|
+
}
|
|
4427
5019
|
function buildRequest3(params, entries) {
|
|
4428
5020
|
return {
|
|
4429
5021
|
sourceLocale: params.sourceLocale,
|
|
@@ -4466,6 +5058,7 @@ async function runLocale(params) {
|
|
|
4466
5058
|
orphaned,
|
|
4467
5059
|
invalidIcuSource,
|
|
4468
5060
|
translated: toTranslate,
|
|
5061
|
+
cacheHits: [],
|
|
4469
5062
|
generated: [],
|
|
4470
5063
|
integrityMismatches: [],
|
|
4471
5064
|
providerFailures: [],
|
|
@@ -4473,12 +5066,16 @@ async function runLocale(params) {
|
|
|
4473
5066
|
pruned,
|
|
4474
5067
|
notices: sdkNotices
|
|
4475
5068
|
}),
|
|
4476
|
-
lockEntries: {}
|
|
5069
|
+
lockEntries: {},
|
|
5070
|
+
cacheAdditions: []
|
|
4477
5071
|
};
|
|
4478
5072
|
}
|
|
4479
|
-
const
|
|
5073
|
+
const partition2 = partitionCacheHits(params, toTranslate);
|
|
5074
|
+
const cacheHitKeys = new Set(partition2.hits.keys());
|
|
5075
|
+
const missGroups = groupMissesByContent(params, partition2.misses);
|
|
5076
|
+
const entries = missGroups.map((group) => params.source.entries.get(group.representative)).filter((entry) => entry !== void 0);
|
|
4480
5077
|
const startedStopped = params.budget.stopped;
|
|
4481
|
-
const accepted =
|
|
5078
|
+
const accepted = new Map(partition2.hits);
|
|
4482
5079
|
const integrityMismatches = [];
|
|
4483
5080
|
const providerFailures = [];
|
|
4484
5081
|
const budgetWithheld = [];
|
|
@@ -4493,6 +5090,13 @@ async function runLocale(params) {
|
|
|
4493
5090
|
budgetWithheld,
|
|
4494
5091
|
reviewFlags
|
|
4495
5092
|
);
|
|
5093
|
+
fanOutContentDuplicates(params, missGroups, {
|
|
5094
|
+
accepted,
|
|
5095
|
+
integrityMismatches,
|
|
5096
|
+
providerFailures,
|
|
5097
|
+
budgetWithheld,
|
|
5098
|
+
reviewFlags
|
|
5099
|
+
});
|
|
4496
5100
|
const merged = new Map(target.entries);
|
|
4497
5101
|
for (const key of pruned) {
|
|
4498
5102
|
merged.delete(key);
|
|
@@ -4539,7 +5143,8 @@ async function runLocale(params) {
|
|
|
4539
5143
|
unchanged: diff2.unchanged,
|
|
4540
5144
|
orphaned,
|
|
4541
5145
|
invalidIcuSource,
|
|
4542
|
-
translated: [...accepted.keys()],
|
|
5146
|
+
translated: [...accepted.keys()].filter((key) => !cacheHitKeys.has(key)),
|
|
5147
|
+
cacheHits: [...cacheHitKeys].sort(),
|
|
4543
5148
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
4544
5149
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
4545
5150
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
@@ -4549,7 +5154,8 @@ async function runLocale(params) {
|
|
|
4549
5154
|
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
4550
5155
|
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
4551
5156
|
}),
|
|
4552
|
-
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
5157
|
+
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted),
|
|
5158
|
+
cacheAdditions: collectCacheAdditions(params, accepted, cacheHitKeys)
|
|
4553
5159
|
};
|
|
4554
5160
|
}
|
|
4555
5161
|
var NO_GENERATION_RESULT = {
|
|
@@ -4600,12 +5206,16 @@ function baseSummary(parts) {
|
|
|
4600
5206
|
orphaned: parts.orphaned,
|
|
4601
5207
|
pruned: parts.pruned,
|
|
4602
5208
|
invalidIcuSource: parts.invalidIcuSource,
|
|
5209
|
+
cacheHits: parts.cacheHits,
|
|
4603
5210
|
integrityMismatches: parts.integrityMismatches,
|
|
4604
5211
|
providerFailures: parts.providerFailures,
|
|
4605
5212
|
budgetWithheld: parts.budgetWithheld,
|
|
4606
5213
|
generated: parts.generated,
|
|
4607
5214
|
notices: parts.notices,
|
|
4608
5215
|
needsReview: parts.needsReview ?? [],
|
|
5216
|
+
unfilled: [],
|
|
5217
|
+
malformedRows: [],
|
|
5218
|
+
duplicateKeys: [],
|
|
4609
5219
|
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
4610
5220
|
};
|
|
4611
5221
|
}
|
|
@@ -4623,7 +5233,16 @@ async function translateAndCheck(provider, params, entries, accepted, integrityM
|
|
|
4623
5233
|
const notices = [];
|
|
4624
5234
|
const usage = createUsageAccumulator();
|
|
4625
5235
|
let tripped = false;
|
|
4626
|
-
|
|
5236
|
+
const batches = chunk(entries, params.maxBatchSize);
|
|
5237
|
+
let batchIndex = 0;
|
|
5238
|
+
for (const batch of batches) {
|
|
5239
|
+
batchIndex += 1;
|
|
5240
|
+
params.onProgress?.({
|
|
5241
|
+
type: "sub-batch",
|
|
5242
|
+
locale: params.targetLocale,
|
|
5243
|
+
batchIndex,
|
|
5244
|
+
totalBatches: batches.length
|
|
5245
|
+
});
|
|
4627
5246
|
if (params.budget.stopped) {
|
|
4628
5247
|
for (const entry of batch) {
|
|
4629
5248
|
budgetWithheld.push(entry.key);
|
|
@@ -4769,6 +5388,33 @@ async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
|
4769
5388
|
} catch {
|
|
4770
5389
|
}
|
|
4771
5390
|
}
|
|
5391
|
+
async function createRunCacheState(input, config, cwd, dryRun, fs) {
|
|
5392
|
+
if (dryRun || input.cache === false) {
|
|
5393
|
+
return void 0;
|
|
5394
|
+
}
|
|
5395
|
+
const { memory, writable } = await readTranslationMemory(cacheFilePath(cwd), fs);
|
|
5396
|
+
return { memory, writable, fingerprint: computeFingerprint(config), additions: /* @__PURE__ */ new Map() };
|
|
5397
|
+
}
|
|
5398
|
+
function withCacheNotices(summaries, cache) {
|
|
5399
|
+
if (cache === void 0 || cache.writable) {
|
|
5400
|
+
return [...summaries];
|
|
5401
|
+
}
|
|
5402
|
+
const notice = {
|
|
5403
|
+
code: "CACHE_VERSION_UNRECOGNIZED",
|
|
5404
|
+
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.`
|
|
5405
|
+
};
|
|
5406
|
+
return summaries.map((summary) => ({ ...summary, notices: [...summary.notices, notice] }));
|
|
5407
|
+
}
|
|
5408
|
+
async function recordCacheAdditions(cwd, cache, fs) {
|
|
5409
|
+
if (cache === void 0 || cache.additions.size === 0 || !cache.writable) {
|
|
5410
|
+
return;
|
|
5411
|
+
}
|
|
5412
|
+
try {
|
|
5413
|
+
const merged = applyAdditions(cache.memory, cache.fingerprint, cache.additions);
|
|
5414
|
+
await writeTranslationMemory(cacheFilePath(cwd), merged, fs);
|
|
5415
|
+
} catch {
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
4772
5418
|
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4773
5419
|
return {
|
|
4774
5420
|
source: context.source.resource,
|
|
@@ -4787,7 +5433,9 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
4787
5433
|
generatePlurals: context.generatePlurals,
|
|
4788
5434
|
maxBatchSize: context.maxBatchSize,
|
|
4789
5435
|
fs: context.fs,
|
|
4790
|
-
budget: context.budget
|
|
5436
|
+
budget: context.budget,
|
|
5437
|
+
...context.cache !== void 0 ? { cache: { snapshot: context.cache.memory, fingerprint: context.cache.fingerprint } } : {},
|
|
5438
|
+
...context.onProgress !== void 0 ? { onProgress: context.onProgress } : {}
|
|
4791
5439
|
};
|
|
4792
5440
|
}
|
|
4793
5441
|
async function runDryLocale(context, targetLocale, lock) {
|
|
@@ -4811,6 +5459,9 @@ async function runLiveLocale(context, targetLocale) {
|
|
|
4811
5459
|
mode: "replace",
|
|
4812
5460
|
entries: result.lockEntries
|
|
4813
5461
|
});
|
|
5462
|
+
if (context.cache !== void 0 && result.cacheAdditions.length > 0) {
|
|
5463
|
+
context.cache.additions.set(targetLocale, additionsToRecord(result.cacheAdditions));
|
|
5464
|
+
}
|
|
4814
5465
|
return result.summary;
|
|
4815
5466
|
},
|
|
4816
5467
|
lockOptions
|
|
@@ -4826,27 +5477,98 @@ async function runOneLocale(targetLocale, run) {
|
|
|
4826
5477
|
return failureSummary(targetLocale, error);
|
|
4827
5478
|
}
|
|
4828
5479
|
}
|
|
4829
|
-
async function
|
|
5480
|
+
async function runLocaleAt(context, targetLocales, localeIndex, runOne, results) {
|
|
5481
|
+
const targetLocale = targetLocales[localeIndex];
|
|
5482
|
+
if (targetLocale === void 0) {
|
|
5483
|
+
return;
|
|
5484
|
+
}
|
|
5485
|
+
context.onProgress?.({
|
|
5486
|
+
type: "locale-started",
|
|
5487
|
+
locale: targetLocale,
|
|
5488
|
+
localeIndex,
|
|
5489
|
+
totalLocales: targetLocales.length
|
|
5490
|
+
});
|
|
5491
|
+
const summary = await runOneLocale(targetLocale, () => runOne(targetLocale));
|
|
5492
|
+
results[localeIndex] = summary;
|
|
5493
|
+
context.onProgress?.({
|
|
5494
|
+
type: "locale-finished",
|
|
5495
|
+
locale: targetLocale,
|
|
5496
|
+
translated: summary.translated.length,
|
|
5497
|
+
localeIndex,
|
|
5498
|
+
totalLocales: targetLocales.length
|
|
5499
|
+
});
|
|
5500
|
+
}
|
|
5501
|
+
async function runLocalesWithProgress(context, targetLocales, runOne, concurrency) {
|
|
5502
|
+
const totalLocales = targetLocales.length;
|
|
5503
|
+
const results = new Array(totalLocales);
|
|
5504
|
+
let nextIndex = 0;
|
|
5505
|
+
let abort;
|
|
5506
|
+
async function worker() {
|
|
5507
|
+
while (abort === void 0 && nextIndex < totalLocales) {
|
|
5508
|
+
const localeIndex = nextIndex;
|
|
5509
|
+
nextIndex += 1;
|
|
5510
|
+
try {
|
|
5511
|
+
await runLocaleAt(context, targetLocales, localeIndex, runOne, results);
|
|
5512
|
+
} catch (error) {
|
|
5513
|
+
abort ??= { reason: error };
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
const workerCount = Math.min(concurrency, totalLocales);
|
|
5518
|
+
const workers = [];
|
|
5519
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
5520
|
+
workers.push(worker());
|
|
5521
|
+
}
|
|
5522
|
+
await Promise.all(workers);
|
|
5523
|
+
if (abort !== void 0) {
|
|
5524
|
+
throw abort.reason;
|
|
5525
|
+
}
|
|
5526
|
+
return results.filter((summary) => summary !== void 0);
|
|
5527
|
+
}
|
|
5528
|
+
async function runAllLocalesDry(context, targetLocales, concurrency) {
|
|
4830
5529
|
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
5530
|
+
return runLocalesWithProgress(
|
|
5531
|
+
context,
|
|
5532
|
+
targetLocales,
|
|
5533
|
+
(targetLocale) => runDryLocale(context, targetLocale, lock),
|
|
5534
|
+
concurrency
|
|
5535
|
+
);
|
|
5536
|
+
}
|
|
5537
|
+
async function runAllLocalesLive(context, targetLocales, concurrency) {
|
|
5538
|
+
return runLocalesWithProgress(
|
|
5539
|
+
context,
|
|
5540
|
+
targetLocales,
|
|
5541
|
+
(targetLocale) => runLiveLocale(context, targetLocale),
|
|
5542
|
+
concurrency
|
|
5543
|
+
);
|
|
5544
|
+
}
|
|
5545
|
+
function resolveConcurrency(value) {
|
|
5546
|
+
if (value === void 0) {
|
|
5547
|
+
return 1;
|
|
5548
|
+
}
|
|
5549
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5550
|
+
throw new SdkError(
|
|
5551
|
+
"CONCURRENCY_INVALID",
|
|
5552
|
+
`The concurrency option must be an integer of at least 1, got ${value}.`
|
|
4835
5553
|
);
|
|
4836
5554
|
}
|
|
4837
|
-
return
|
|
5555
|
+
return value;
|
|
4838
5556
|
}
|
|
4839
|
-
|
|
4840
|
-
const
|
|
4841
|
-
|
|
4842
|
-
|
|
5557
|
+
function resolveRunConcurrency(value, dryRun, config) {
|
|
5558
|
+
const concurrency = resolveConcurrency(value);
|
|
5559
|
+
if (!dryRun && concurrency > 1 && config.maxTokens !== void 0) {
|
|
5560
|
+
throw new SdkError(
|
|
5561
|
+
"CONCURRENCY_BUDGET_CONFLICT",
|
|
5562
|
+
"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."
|
|
5563
|
+
);
|
|
4843
5564
|
}
|
|
4844
|
-
return
|
|
5565
|
+
return concurrency;
|
|
4845
5566
|
}
|
|
4846
5567
|
async function translate2(input, deps = {}) {
|
|
4847
5568
|
const config = input.config;
|
|
4848
5569
|
const cwd = input.cwd ?? process.cwd();
|
|
4849
5570
|
const dryRun = input.dryRun ?? false;
|
|
5571
|
+
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
4850
5572
|
const prune = input.prune ?? config.prune ?? false;
|
|
4851
5573
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
4852
5574
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
@@ -4858,6 +5580,7 @@ async function translate2(input, deps = {}) {
|
|
|
4858
5580
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4859
5581
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
4860
5582
|
const source = await readSource(config, cwd, fs, adapter);
|
|
5583
|
+
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
4861
5584
|
const context = {
|
|
4862
5585
|
source,
|
|
4863
5586
|
adapter,
|
|
@@ -4869,11 +5592,15 @@ async function translate2(input, deps = {}) {
|
|
|
4869
5592
|
maxBatchSize,
|
|
4870
5593
|
fs,
|
|
4871
5594
|
budget,
|
|
5595
|
+
cache,
|
|
4872
5596
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5597
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
4873
5598
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
4874
5599
|
};
|
|
4875
|
-
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
4876
|
-
|
|
5600
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales, concurrency) : await runAllLocalesLive(context, config.targetLocales, concurrency);
|
|
5601
|
+
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
5602
|
+
const locales = withCacheNotices(summaries, cache);
|
|
5603
|
+
const { succeeded, partial, failed } = partition(locales);
|
|
4877
5604
|
const usage = summaries.reduce(
|
|
4878
5605
|
(total, summary2) => combineUsage(total, summary2.usage),
|
|
4879
5606
|
void 0
|
|
@@ -4881,13 +5608,14 @@ async function translate2(input, deps = {}) {
|
|
|
4881
5608
|
const budgetSummary = toBudgetSummary(budget);
|
|
4882
5609
|
const summary = {
|
|
4883
5610
|
dryRun,
|
|
4884
|
-
locales
|
|
5611
|
+
locales,
|
|
4885
5612
|
succeeded,
|
|
4886
5613
|
partial,
|
|
4887
5614
|
failed,
|
|
4888
5615
|
...usage !== void 0 ? { usage } : {},
|
|
4889
5616
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4890
5617
|
};
|
|
5618
|
+
await recordCacheAdditions(cwd, cache, fs);
|
|
4891
5619
|
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4892
5620
|
return summary;
|
|
4893
5621
|
}
|
|
@@ -4903,8 +5631,12 @@ var INSTRUCTIONS_LINES = [
|
|
|
4903
5631
|
"How to use this workbook",
|
|
4904
5632
|
"",
|
|
4905
5633
|
"1. There is one sheet per language. Open the sheet named for the language you are translating.",
|
|
5634
|
+
" Do not rename, delete, or reorder the language tabs. verbatra matches each tab to a language by",
|
|
5635
|
+
" its exact name; a renamed or missing tab is reported and its translations are not imported.",
|
|
4906
5636
|
"2. Fill ONLY the 'Translation' column. Leave every other column unchanged.",
|
|
4907
|
-
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty
|
|
5637
|
+
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty",
|
|
5638
|
+
" string. A cell that contains only spaces is treated the same as an empty cell.",
|
|
5639
|
+
" To deliberately clear an existing value (set it to empty), type exactly [[CLEAR]] in the cell.",
|
|
4908
5640
|
"4. Keep placeholders exactly as they appear. A token such as {name} or {count} must stay verbatim,",
|
|
4909
5641
|
" in your translation, with the same spelling. Do not translate or remove it.",
|
|
4910
5642
|
"5. For ICU messages (for example {count, plural, one {# item} other {# items}}), keep the ICU",
|
|
@@ -5069,6 +5801,26 @@ function buildInstructionsSheet(workbook) {
|
|
|
5069
5801
|
}
|
|
5070
5802
|
sheet.getRow(1).font = { bold: true };
|
|
5071
5803
|
}
|
|
5804
|
+
var WORKBOOK_PROTECTION_XML = '<workbookProtection lockStructure="1" lockWindows="0"/>';
|
|
5805
|
+
function spliceWorkbookProtection(xml) {
|
|
5806
|
+
const anchor = xml.includes("<bookViews") ? "<bookViews" : "<sheets";
|
|
5807
|
+
return xml.replace(anchor, `${WORKBOOK_PROTECTION_XML}${anchor}`);
|
|
5808
|
+
}
|
|
5809
|
+
async function protectWorkbookStructure(bytes) {
|
|
5810
|
+
let zip;
|
|
5811
|
+
try {
|
|
5812
|
+
zip = await JSZip__default.default.loadAsync(bytes);
|
|
5813
|
+
} catch {
|
|
5814
|
+
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5815
|
+
}
|
|
5816
|
+
const workbookPart = zip.file("xl/workbook.xml");
|
|
5817
|
+
if (workbookPart === null) {
|
|
5818
|
+
return bytes;
|
|
5819
|
+
}
|
|
5820
|
+
const xml = await workbookPart.async("string");
|
|
5821
|
+
zip.file("xl/workbook.xml", spliceWorkbookProtection(xml));
|
|
5822
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
5823
|
+
}
|
|
5072
5824
|
async function buildWorkbook(model) {
|
|
5073
5825
|
assertNoWorksheetNameCollisions(model.sheets);
|
|
5074
5826
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
@@ -5076,13 +5828,15 @@ async function buildWorkbook(model) {
|
|
|
5076
5828
|
for (const sheet of model.sheets) {
|
|
5077
5829
|
await buildDataSheet(workbook, sheet);
|
|
5078
5830
|
}
|
|
5831
|
+
let serialized;
|
|
5079
5832
|
try {
|
|
5080
5833
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
5081
|
-
|
|
5082
|
-
return Uint8Array.prototype.slice.call(view);
|
|
5834
|
+
serialized = buffer;
|
|
5083
5835
|
} catch {
|
|
5084
5836
|
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5085
5837
|
}
|
|
5838
|
+
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5839
|
+
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
5086
5840
|
}
|
|
5087
5841
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
5088
5842
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
@@ -5202,26 +5956,24 @@ function assertHeader(sheet) {
|
|
|
5202
5956
|
);
|
|
5203
5957
|
}
|
|
5204
5958
|
}
|
|
5205
|
-
|
|
5959
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5960
|
+
function parseRow(row) {
|
|
5206
5961
|
const candidate = {
|
|
5207
5962
|
key: cellString(row.getCell(COLUMN.key)),
|
|
5208
5963
|
source: cellString(row.getCell(COLUMN.source)),
|
|
5209
5964
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
5210
5965
|
status: cellString(row.getCell(COLUMN.status)),
|
|
5211
5966
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
5212
|
-
translation: cellString(row.getCell(COLUMN.translation)),
|
|
5967
|
+
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
5213
5968
|
context: cellString(row.getCell(COLUMN.context)),
|
|
5214
5969
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
5215
5970
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
5216
5971
|
};
|
|
5217
5972
|
const result = rowSchema.safeParse(candidate);
|
|
5218
5973
|
if (!result.success) {
|
|
5219
|
-
|
|
5220
|
-
"WORKBOOK_INVALID",
|
|
5221
|
-
`The sheet "${sheet.name}" has a row with a missing key or an unrecognized status.`
|
|
5222
|
-
);
|
|
5974
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
5223
5975
|
}
|
|
5224
|
-
return result.data;
|
|
5976
|
+
return { ok: true, row: result.data };
|
|
5225
5977
|
}
|
|
5226
5978
|
function readDataSheet(sheet, limits) {
|
|
5227
5979
|
assertHeader(sheet);
|
|
@@ -5232,6 +5984,9 @@ function readDataSheet(sheet, limits) {
|
|
|
5232
5984
|
);
|
|
5233
5985
|
}
|
|
5234
5986
|
const rows = [];
|
|
5987
|
+
const malformed = [];
|
|
5988
|
+
const duplicates = [];
|
|
5989
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
5235
5990
|
for (let rowNumber = HEADER_ROW + 1; rowNumber <= sheet.rowCount; rowNumber += 1) {
|
|
5236
5991
|
const row = sheet.getRow(rowNumber);
|
|
5237
5992
|
if (row.cellCount > limits.maxCellsPerRow) {
|
|
@@ -5243,9 +5998,19 @@ function readDataSheet(sheet, limits) {
|
|
|
5243
5998
|
if (cellString(row.getCell(COLUMN.key)) === "") {
|
|
5244
5999
|
continue;
|
|
5245
6000
|
}
|
|
5246
|
-
|
|
6001
|
+
const outcome = parseRow(row);
|
|
6002
|
+
if (!outcome.ok) {
|
|
6003
|
+
malformed.push({ locale: sheet.name, row: rowNumber, column: outcome.column });
|
|
6004
|
+
continue;
|
|
6005
|
+
}
|
|
6006
|
+
if (seenKeys.has(outcome.row.key)) {
|
|
6007
|
+
duplicates.push({ locale: sheet.name, key: outcome.row.key, row: rowNumber });
|
|
6008
|
+
continue;
|
|
6009
|
+
}
|
|
6010
|
+
seenKeys.add(outcome.row.key);
|
|
6011
|
+
rows.push(outcome.row);
|
|
5247
6012
|
}
|
|
5248
|
-
return { locale: sheet.name, rows };
|
|
6013
|
+
return { sheet: { locale: sheet.name, rows }, malformed, duplicates };
|
|
5249
6014
|
}
|
|
5250
6015
|
async function loadWorkbook(bytes) {
|
|
5251
6016
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
@@ -5268,13 +6033,18 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
5268
6033
|
);
|
|
5269
6034
|
}
|
|
5270
6035
|
const sheets = [];
|
|
6036
|
+
const malformedRows = [];
|
|
6037
|
+
const duplicateKeys = [];
|
|
5271
6038
|
for (const sheet of workbook.worksheets) {
|
|
5272
6039
|
if (sheet.name === INSTRUCTIONS_SHEET_NAME) {
|
|
5273
6040
|
continue;
|
|
5274
6041
|
}
|
|
5275
|
-
|
|
6042
|
+
const read = readDataSheet(sheet, limits);
|
|
6043
|
+
sheets.push(read.sheet);
|
|
6044
|
+
malformedRows.push(...read.malformed);
|
|
6045
|
+
duplicateKeys.push(...read.duplicates);
|
|
5276
6046
|
}
|
|
5277
|
-
return { sheets };
|
|
6047
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
5278
6048
|
}
|
|
5279
6049
|
|
|
5280
6050
|
// src/flow/workbook/export-workbook.ts
|
|
@@ -5375,6 +6145,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
5375
6145
|
}
|
|
5376
6146
|
|
|
5377
6147
|
// src/flow/workbook/import-locale.ts
|
|
6148
|
+
var CLEAR_SENTINEL = "[[CLEAR]]";
|
|
5378
6149
|
var UnknownKeyError = class extends Error {
|
|
5379
6150
|
key;
|
|
5380
6151
|
constructor(key) {
|
|
@@ -5403,9 +6174,20 @@ function trackBlankDrift(row, params, buckets) {
|
|
|
5403
6174
|
buckets.blankDrifted.add(row.key);
|
|
5404
6175
|
}
|
|
5405
6176
|
}
|
|
5406
|
-
function
|
|
6177
|
+
function classifyClear(row, sourceEntry, buckets) {
|
|
6178
|
+
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
6179
|
+
buckets.mismatches.push(row.key);
|
|
6180
|
+
buckets.withheld.add(row.key);
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
buckets.accepted.set(row.key, { value: "", source: sourceEntry, cleared: true });
|
|
6184
|
+
}
|
|
6185
|
+
function classifyRows(params, buckets, liveCandidates) {
|
|
5407
6186
|
for (const row of params.sheet.rows) {
|
|
5408
6187
|
if (row.translation === "") {
|
|
6188
|
+
if (liveCandidates.has(row.key)) {
|
|
6189
|
+
buckets.unfilled.push(row.key);
|
|
6190
|
+
}
|
|
5409
6191
|
trackBlankDrift(row, params, buckets);
|
|
5410
6192
|
continue;
|
|
5411
6193
|
}
|
|
@@ -5416,9 +6198,17 @@ function classifyRows(params, buckets) {
|
|
|
5416
6198
|
if (sourceEntry === void 0) {
|
|
5417
6199
|
continue;
|
|
5418
6200
|
}
|
|
6201
|
+
if (row.translation === CLEAR_SENTINEL) {
|
|
6202
|
+
classifyClear(row, sourceEntry, buckets);
|
|
6203
|
+
continue;
|
|
6204
|
+
}
|
|
5419
6205
|
const reason = judge(row, sourceEntry, params.adapter);
|
|
5420
6206
|
if (reason === void 0) {
|
|
5421
|
-
buckets.accepted.set(row.key, {
|
|
6207
|
+
buckets.accepted.set(row.key, {
|
|
6208
|
+
value: row.translation,
|
|
6209
|
+
source: sourceEntry,
|
|
6210
|
+
cleared: false
|
|
6211
|
+
});
|
|
5422
6212
|
} else {
|
|
5423
6213
|
buckets.mismatches.push(row.key);
|
|
5424
6214
|
buckets.withheld.add(row.key);
|
|
@@ -5437,9 +6227,10 @@ function importLocale(params) {
|
|
|
5437
6227
|
accepted: /* @__PURE__ */ new Map(),
|
|
5438
6228
|
mismatches: [],
|
|
5439
6229
|
withheld: /* @__PURE__ */ new Set(),
|
|
5440
|
-
blankDrifted: /* @__PURE__ */ new Set()
|
|
6230
|
+
blankDrifted: /* @__PURE__ */ new Set(),
|
|
6231
|
+
unfilled: []
|
|
5441
6232
|
};
|
|
5442
|
-
classifyRows(params, buckets);
|
|
6233
|
+
classifyRows(params, buckets, /* @__PURE__ */ new Set([...diff2.missing, ...diff2.changed]));
|
|
5443
6234
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
5444
6235
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
5445
6236
|
const translated = [...buckets.accepted.keys()].sort();
|
|
@@ -5448,6 +6239,7 @@ function importLocale(params) {
|
|
|
5448
6239
|
locale: params.sheet.locale,
|
|
5449
6240
|
status: deriveLocaleStatus({
|
|
5450
6241
|
translated,
|
|
6242
|
+
cacheHits: [],
|
|
5451
6243
|
generated: [],
|
|
5452
6244
|
integrityMismatches,
|
|
5453
6245
|
providerFailures: [],
|
|
@@ -5458,12 +6250,16 @@ function importLocale(params) {
|
|
|
5458
6250
|
orphaned: diff2.orphaned,
|
|
5459
6251
|
pruned: [],
|
|
5460
6252
|
invalidIcuSource,
|
|
6253
|
+
cacheHits: [],
|
|
5461
6254
|
integrityMismatches,
|
|
5462
6255
|
providerFailures: [],
|
|
5463
6256
|
budgetWithheld: [],
|
|
5464
6257
|
generated: [],
|
|
5465
6258
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
5466
|
-
needsReview: []
|
|
6259
|
+
needsReview: [],
|
|
6260
|
+
unfilled: [...new Set(buckets.unfilled)].sort(),
|
|
6261
|
+
malformedRows: params.malformedRows,
|
|
6262
|
+
duplicateKeys: params.duplicateKeys
|
|
5467
6263
|
};
|
|
5468
6264
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
5469
6265
|
}
|
|
@@ -5490,6 +6286,21 @@ function mergeAccepted(target, accepted) {
|
|
|
5490
6286
|
}
|
|
5491
6287
|
return merged;
|
|
5492
6288
|
}
|
|
6289
|
+
function sheetCacheAdditions(accepted) {
|
|
6290
|
+
const record = {};
|
|
6291
|
+
for (const [, { value, source, cleared }] of accepted) {
|
|
6292
|
+
if (!cleared) {
|
|
6293
|
+
record[contentHash(source)] = value;
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
return record;
|
|
6297
|
+
}
|
|
6298
|
+
function collectSheetAdditions(byLocale, locale, additions) {
|
|
6299
|
+
if (Object.keys(additions).length === 0) {
|
|
6300
|
+
return;
|
|
6301
|
+
}
|
|
6302
|
+
byLocale.set(locale, { ...byLocale.get(locale), ...additions });
|
|
6303
|
+
}
|
|
5493
6304
|
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
5494
6305
|
const entries = {};
|
|
5495
6306
|
for (const key of merged.keys()) {
|
|
@@ -5506,11 +6317,20 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
5506
6317
|
}
|
|
5507
6318
|
return entries;
|
|
5508
6319
|
}
|
|
6320
|
+
var MissingSheetError = class extends Error {
|
|
6321
|
+
code = "WORKBOOK_SHEET_MISSING";
|
|
6322
|
+
constructor(locale) {
|
|
6323
|
+
super(
|
|
6324
|
+
`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.`
|
|
6325
|
+
);
|
|
6326
|
+
this.name = "MissingSheetError";
|
|
6327
|
+
}
|
|
6328
|
+
};
|
|
5509
6329
|
async function runSheet(ctx, sheet, lock) {
|
|
5510
6330
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
5511
6331
|
throw new SdkError(
|
|
5512
6332
|
"CONFIG_INVALID",
|
|
5513
|
-
`The workbook has a sheet
|
|
6333
|
+
`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.`
|
|
5514
6334
|
);
|
|
5515
6335
|
}
|
|
5516
6336
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -5521,10 +6341,12 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5521
6341
|
target,
|
|
5522
6342
|
baseline,
|
|
5523
6343
|
adapter: ctx.adapter,
|
|
5524
|
-
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys
|
|
6344
|
+
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6345
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6346
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
5525
6347
|
});
|
|
5526
6348
|
if (ctx.dryRun) {
|
|
5527
|
-
return { summary, lockEntries: {} };
|
|
6349
|
+
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
5528
6350
|
}
|
|
5529
6351
|
const merged = mergeAccepted(target, accepted);
|
|
5530
6352
|
if (accepted.size > 0) {
|
|
@@ -5539,7 +6361,11 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5539
6361
|
path
|
|
5540
6362
|
);
|
|
5541
6363
|
}
|
|
5542
|
-
return {
|
|
6364
|
+
return {
|
|
6365
|
+
summary,
|
|
6366
|
+
lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted),
|
|
6367
|
+
cacheAdditions: sheetCacheAdditions(accepted)
|
|
6368
|
+
};
|
|
5543
6369
|
}
|
|
5544
6370
|
async function importWorkbook(input, deps = {}) {
|
|
5545
6371
|
const config = input.config;
|
|
@@ -5564,9 +6390,12 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5564
6390
|
fs,
|
|
5565
6391
|
source: source.resource,
|
|
5566
6392
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
5567
|
-
dryRun
|
|
6393
|
+
dryRun,
|
|
6394
|
+
malformedRows: data.malformedRows,
|
|
6395
|
+
duplicateKeys: data.duplicateKeys
|
|
5568
6396
|
};
|
|
5569
6397
|
const summaries = [];
|
|
6398
|
+
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
5570
6399
|
for (const sheet of data.sheets) {
|
|
5571
6400
|
try {
|
|
5572
6401
|
let summary;
|
|
@@ -5579,6 +6408,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5579
6408
|
mode: "replace",
|
|
5580
6409
|
entries: result.lockEntries
|
|
5581
6410
|
});
|
|
6411
|
+
collectSheetAdditions(cacheAdditions, sheet.locale, result.cacheAdditions);
|
|
5582
6412
|
return result.summary;
|
|
5583
6413
|
});
|
|
5584
6414
|
}
|
|
@@ -5587,6 +6417,15 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5587
6417
|
summaries.push(failureSummary(sheet.locale, error));
|
|
5588
6418
|
}
|
|
5589
6419
|
}
|
|
6420
|
+
const presentLocales = new Set(data.sheets.map((sheet) => sheet.locale));
|
|
6421
|
+
for (const locale of config.targetLocales) {
|
|
6422
|
+
if (!presentLocales.has(locale)) {
|
|
6423
|
+
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
if (!dryRun) {
|
|
6427
|
+
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6428
|
+
}
|
|
5590
6429
|
const { succeeded, partial, failed } = partition(summaries);
|
|
5591
6430
|
return { dryRun, locales: summaries, succeeded, partial, failed };
|
|
5592
6431
|
}
|
|
@@ -5638,6 +6477,7 @@ async function watch(input, deps = {}) {
|
|
|
5638
6477
|
const cwd = input.cwd ?? process.cwd();
|
|
5639
6478
|
const debounceMs = input.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
5640
6479
|
const fs = deps.fs ?? defaultFs;
|
|
6480
|
+
resolveRunConcurrency(input.concurrency, false, input.config);
|
|
5641
6481
|
const sourcePath = localeFilePath(cwd, input.config.files.pattern, input.config.sourceLocale);
|
|
5642
6482
|
if (!await fs.fileExists(sourcePath)) {
|
|
5643
6483
|
throw new SdkError(
|
|
@@ -5650,7 +6490,10 @@ async function watch(input, deps = {}) {
|
|
|
5650
6490
|
config: input.config,
|
|
5651
6491
|
cwd,
|
|
5652
6492
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5653
|
-
...input.
|
|
6493
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6494
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
6495
|
+
...input.concurrency !== void 0 ? { concurrency: input.concurrency } : {},
|
|
6496
|
+
...input.cache !== void 0 ? { cache: input.cache } : {}
|
|
5654
6497
|
};
|
|
5655
6498
|
let state = "idle";
|
|
5656
6499
|
let pending = false;
|
|
@@ -5718,6 +6561,7 @@ async function watch(input, deps = {}) {
|
|
|
5718
6561
|
return { stop };
|
|
5719
6562
|
}
|
|
5720
6563
|
|
|
6564
|
+
exports.CACHE_FILE_NAME = CACHE_FILE_NAME;
|
|
5721
6565
|
exports.DEFAULT_WORKBOOK_PATH = DEFAULT_WORKBOOK_PATH;
|
|
5722
6566
|
exports.LOCK_FILE_NAME = LOCK_FILE_NAME;
|
|
5723
6567
|
exports.SdkError = SdkError;
|