@verbatra/sdk 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +895 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -57
- package/dist/index.d.ts +262 -57
- package/dist/index.js +895 -99
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,96 @@ 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
|
+
async function readTranslationMemory(path, fs) {
|
|
65
|
+
let read;
|
|
66
|
+
try {
|
|
67
|
+
read = await fs.readFileBounded(path, MAX_CACHE_FILE_BYTES);
|
|
68
|
+
} catch {
|
|
69
|
+
return EMPTY_MEMORY;
|
|
70
|
+
}
|
|
71
|
+
if (read.kind !== "ok") {
|
|
72
|
+
return EMPTY_MEMORY;
|
|
73
|
+
}
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(read.content);
|
|
77
|
+
} catch {
|
|
78
|
+
return EMPTY_MEMORY;
|
|
79
|
+
}
|
|
80
|
+
const result = translationMemorySchema.safeParse(parsed);
|
|
81
|
+
if (!result.success || result.data.version !== CURRENT_VERSION) {
|
|
82
|
+
return EMPTY_MEMORY;
|
|
83
|
+
}
|
|
84
|
+
return result.data;
|
|
85
|
+
}
|
|
86
|
+
function lookupMemory(memory, fingerprint, locale, contentHash2) {
|
|
87
|
+
return memory.entries[fingerprint]?.[locale]?.[contentHash2];
|
|
88
|
+
}
|
|
89
|
+
function applyAdditions(base, fingerprint, additionsByLocale) {
|
|
90
|
+
if (additionsByLocale.size === 0) {
|
|
91
|
+
return base;
|
|
92
|
+
}
|
|
93
|
+
const fingerprintEntries = {};
|
|
94
|
+
for (const [locale, hashes] of Object.entries(base.entries[fingerprint] ?? {})) {
|
|
95
|
+
fingerprintEntries[locale] = { ...hashes };
|
|
96
|
+
}
|
|
97
|
+
for (const [locale, hashes] of additionsByLocale) {
|
|
98
|
+
fingerprintEntries[locale] = { ...fingerprintEntries[locale], ...hashes };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
version: base.version,
|
|
102
|
+
entries: { ...base.entries, [fingerprint]: fingerprintEntries }
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function additionsToRecord(additions) {
|
|
106
|
+
const record = {};
|
|
107
|
+
for (const addition of additions) {
|
|
108
|
+
record[addition.contentHash] = addition.value;
|
|
109
|
+
}
|
|
110
|
+
return record;
|
|
111
|
+
}
|
|
112
|
+
function sortEntries(record) {
|
|
113
|
+
return Object.entries(record).sort(([a], [b]) => a < b ? -1 : 1);
|
|
114
|
+
}
|
|
115
|
+
function serialize(memory) {
|
|
116
|
+
const entries = {};
|
|
117
|
+
for (const [fingerprint, locales] of sortEntries(memory.entries)) {
|
|
118
|
+
const localeMap = {};
|
|
119
|
+
for (const [locale, hashes] of sortEntries(locales)) {
|
|
120
|
+
localeMap[locale] = Object.fromEntries(sortEntries(hashes));
|
|
121
|
+
}
|
|
122
|
+
entries[fingerprint] = localeMap;
|
|
123
|
+
}
|
|
124
|
+
return `${JSON.stringify({ version: memory.version, entries }, null, 2)}
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
async function writeTranslationMemory(path, memory, fs) {
|
|
128
|
+
await fs.writeFile(path, serialize(memory));
|
|
129
|
+
}
|
|
130
|
+
async function feedTranslationMemory(cwd, fs, fingerprint, additionsByLocale) {
|
|
131
|
+
if (additionsByLocale.size === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const path = cacheFilePath(cwd);
|
|
136
|
+
const memory = await readTranslationMemory(path, fs);
|
|
137
|
+
await writeTranslationMemory(path, applyAdditions(memory, fingerprint, additionsByLocale), fs);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
52
142
|
// src/config/define-config.ts
|
|
53
143
|
function defineConfig(config) {
|
|
54
144
|
return config;
|
|
@@ -242,7 +332,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
242
332
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
243
333
|
var FNV_PRIME = 1099511628211n;
|
|
244
334
|
var U64_MASK = (1n << 64n) - 1n;
|
|
245
|
-
function
|
|
335
|
+
function stableStringHash(input) {
|
|
246
336
|
let hash = FNV_OFFSET_BASIS;
|
|
247
337
|
for (let index = 0; index < input.length; index += 1) {
|
|
248
338
|
hash ^= BigInt(input.charCodeAt(index));
|
|
@@ -263,7 +353,7 @@ function canonicalize(entry) {
|
|
|
263
353
|
]);
|
|
264
354
|
}
|
|
265
355
|
function contentHash(entry) {
|
|
266
|
-
return
|
|
356
|
+
return stableStringHash(canonicalize(entry));
|
|
267
357
|
}
|
|
268
358
|
function sorted(keys) {
|
|
269
359
|
return [...keys].sort();
|
|
@@ -308,7 +398,8 @@ var SUPPORTED_FORMATS = [
|
|
|
308
398
|
"ngx-translate-json",
|
|
309
399
|
"xliff",
|
|
310
400
|
"yaml",
|
|
311
|
-
"arb"
|
|
401
|
+
"arb",
|
|
402
|
+
"properties"
|
|
312
403
|
];
|
|
313
404
|
var supportedFormatSchema = zod.z.enum(SUPPORTED_FORMATS);
|
|
314
405
|
var translationEntrySchema = zod.z.object({
|
|
@@ -1250,14 +1341,14 @@ function throwAbort(signal) {
|
|
|
1250
1341
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1251
1342
|
}
|
|
1252
1343
|
function delay(ms, signal) {
|
|
1253
|
-
return new Promise((
|
|
1344
|
+
return new Promise((resolve10) => {
|
|
1254
1345
|
const onAbort = () => {
|
|
1255
1346
|
clearTimeout(timer);
|
|
1256
|
-
|
|
1347
|
+
resolve10();
|
|
1257
1348
|
};
|
|
1258
1349
|
const timer = setTimeout(() => {
|
|
1259
1350
|
signal?.removeEventListener("abort", onAbort);
|
|
1260
|
-
|
|
1351
|
+
resolve10();
|
|
1261
1352
|
}, ms);
|
|
1262
1353
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1263
1354
|
});
|
|
@@ -1995,8 +2086,8 @@ async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
|
1995
2086
|
|
|
1996
2087
|
// src/lock/lock-file.ts
|
|
1997
2088
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1998
|
-
var
|
|
1999
|
-
var EMPTY_LOCK = { version:
|
|
2089
|
+
var CURRENT_VERSION2 = 1;
|
|
2090
|
+
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
2000
2091
|
var MAX_LOCK_FILE_BYTES = 16 * 1024 * 1024;
|
|
2001
2092
|
var lockFileSchema = zod.z.object({
|
|
2002
2093
|
version: zod.z.number().int().positive(),
|
|
@@ -2025,10 +2116,10 @@ function parseLockFileRead(read, path) {
|
|
|
2025
2116
|
if (!result.success) {
|
|
2026
2117
|
throw new SdkError("LOCK_FILE_INVALID", `The lock-file at ${path} has an unexpected shape.`);
|
|
2027
2118
|
}
|
|
2028
|
-
if (result.data.version !==
|
|
2119
|
+
if (result.data.version !== CURRENT_VERSION2) {
|
|
2029
2120
|
throw new SdkError(
|
|
2030
2121
|
"LOCK_FILE_INVALID",
|
|
2031
|
-
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${
|
|
2122
|
+
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${CURRENT_VERSION2}.`
|
|
2032
2123
|
);
|
|
2033
2124
|
}
|
|
2034
2125
|
return result.data;
|
|
@@ -2818,7 +2909,7 @@ function createTreeFileAdapter(options) {
|
|
|
2818
2909
|
extensions,
|
|
2819
2910
|
sniff,
|
|
2820
2911
|
parse: parse3,
|
|
2821
|
-
serialize,
|
|
2912
|
+
serialize: serialize2,
|
|
2822
2913
|
deriveEntry,
|
|
2823
2914
|
extractPlaceholders,
|
|
2824
2915
|
computeInvalidIcuKeys,
|
|
@@ -2853,7 +2944,7 @@ function createTreeFileAdapter(options) {
|
|
|
2853
2944
|
},
|
|
2854
2945
|
async write(resource, filePath) {
|
|
2855
2946
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
2856
|
-
await atomicWriteFile(filePath,
|
|
2947
|
+
await atomicWriteFile(filePath, serialize2(tree));
|
|
2857
2948
|
}
|
|
2858
2949
|
};
|
|
2859
2950
|
}
|
|
@@ -3110,6 +3201,393 @@ function createNgxTranslateJsonAdapter() {
|
|
|
3110
3201
|
keyMode: "path-notation"
|
|
3111
3202
|
});
|
|
3112
3203
|
}
|
|
3204
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
3205
|
+
try {
|
|
3206
|
+
return parseEntries(content, namespace);
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
function createFlatFileAdapter(options) {
|
|
3212
|
+
const {
|
|
3213
|
+
format,
|
|
3214
|
+
extensions,
|
|
3215
|
+
sniff,
|
|
3216
|
+
parseEntries,
|
|
3217
|
+
serializeEntries,
|
|
3218
|
+
extractPlaceholders,
|
|
3219
|
+
validateMessage,
|
|
3220
|
+
computeInvalidIcuKeys
|
|
3221
|
+
} = options;
|
|
3222
|
+
return {
|
|
3223
|
+
format,
|
|
3224
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
3225
|
+
extractPlaceholders,
|
|
3226
|
+
validateMessage: validateMessage ?? (() => true),
|
|
3227
|
+
async read(filePath, locale) {
|
|
3228
|
+
const content = await readFileContent(filePath);
|
|
3229
|
+
const namespace = namespaceOf(filePath);
|
|
3230
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
3231
|
+
const resource = { locale, namespace, format, entries };
|
|
3232
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3233
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3234
|
+
},
|
|
3235
|
+
async write(resource, filePath) {
|
|
3236
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
3237
|
+
await atomicWriteFile(filePath, data);
|
|
3238
|
+
}
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordinal", "choice"]);
|
|
3242
|
+
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3243
|
+
function matchingBraces(value) {
|
|
3244
|
+
const close = new Int32Array(value.length).fill(-1);
|
|
3245
|
+
const open3 = [];
|
|
3246
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
3247
|
+
const char = value[i];
|
|
3248
|
+
if (char === "{") {
|
|
3249
|
+
open3.push(i);
|
|
3250
|
+
} else if (char === "}") {
|
|
3251
|
+
const start = open3.pop();
|
|
3252
|
+
if (start !== void 0) {
|
|
3253
|
+
close[start] = i;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
return close;
|
|
3258
|
+
}
|
|
3259
|
+
function findNameEnd(value, from, limit) {
|
|
3260
|
+
for (let i = from; i < limit; i += 1) {
|
|
3261
|
+
const char = value[i];
|
|
3262
|
+
if (char === ",") {
|
|
3263
|
+
return { comma: i, hasBrace: false };
|
|
3264
|
+
}
|
|
3265
|
+
if (char === "{" || char === "}") {
|
|
3266
|
+
return { comma: -1, hasBrace: true };
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
return { comma: -1, hasBrace: false };
|
|
3270
|
+
}
|
|
3271
|
+
function parseArgumentAt(value, open3, close) {
|
|
3272
|
+
const { comma, hasBrace } = findNameEnd(value, open3 + 1, close);
|
|
3273
|
+
if (hasBrace) {
|
|
3274
|
+
return null;
|
|
3275
|
+
}
|
|
3276
|
+
const nameEnd = comma === -1 ? close : comma;
|
|
3277
|
+
const name = value.slice(open3 + 1, nameEnd).trim();
|
|
3278
|
+
if (!ARGUMENT_NAME.test(name)) {
|
|
3279
|
+
return null;
|
|
3280
|
+
}
|
|
3281
|
+
if (comma === -1) {
|
|
3282
|
+
return { name, type: null, styleStart: -1 };
|
|
3283
|
+
}
|
|
3284
|
+
const secondComma = value.indexOf(",", comma + 1);
|
|
3285
|
+
if (secondComma === -1 || secondComma >= close) {
|
|
3286
|
+
return { name, type: value.slice(comma + 1, close).trim(), styleStart: -1 };
|
|
3287
|
+
}
|
|
3288
|
+
return { name, type: value.slice(comma + 1, secondComma).trim(), styleStart: secondComma + 1 };
|
|
3289
|
+
}
|
|
3290
|
+
function canonicalToken(value, arg, close) {
|
|
3291
|
+
if (arg.type === null) {
|
|
3292
|
+
return `{${arg.name}}`;
|
|
3293
|
+
}
|
|
3294
|
+
if (arg.styleStart === -1) {
|
|
3295
|
+
return `{${arg.name},${arg.type}}`;
|
|
3296
|
+
}
|
|
3297
|
+
return `{${arg.name},${arg.type},${value.slice(arg.styleStart, close).trim()}}`;
|
|
3298
|
+
}
|
|
3299
|
+
function emitArgument(value, close, arg, closeIndex, out) {
|
|
3300
|
+
if (arg.type !== null && SUBMESSAGE_TYPES.has(arg.type)) {
|
|
3301
|
+
out.push(`{${arg.name},${arg.type}}`);
|
|
3302
|
+
if (arg.styleStart !== -1) {
|
|
3303
|
+
scanRange(value, close, arg.styleStart, closeIndex, false, out);
|
|
3304
|
+
}
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
out.push(canonicalToken(value, arg, closeIndex));
|
|
3308
|
+
}
|
|
3309
|
+
function scanRange(value, close, start, end, topLevel, out) {
|
|
3310
|
+
let i = start;
|
|
3311
|
+
while (i < end) {
|
|
3312
|
+
if (value[i] !== "{") {
|
|
3313
|
+
i += 1;
|
|
3314
|
+
continue;
|
|
3315
|
+
}
|
|
3316
|
+
if (topLevel && value[i + 1] === "{") {
|
|
3317
|
+
i += 2;
|
|
3318
|
+
continue;
|
|
3319
|
+
}
|
|
3320
|
+
const closeIndex = close[i] ?? -1;
|
|
3321
|
+
if (closeIndex === -1 || closeIndex >= end) {
|
|
3322
|
+
i += 1;
|
|
3323
|
+
continue;
|
|
3324
|
+
}
|
|
3325
|
+
const arg = parseArgumentAt(value, i, closeIndex);
|
|
3326
|
+
if (arg === null) {
|
|
3327
|
+
i += 1;
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
emitArgument(value, close, arg, closeIndex, out);
|
|
3331
|
+
i = closeIndex + 1;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function extractPropertiesPlaceholders(value) {
|
|
3335
|
+
const out = [];
|
|
3336
|
+
scanRange(value, matchingBraces(value), 0, value.length, true, out);
|
|
3337
|
+
return out;
|
|
3338
|
+
}
|
|
3339
|
+
var UNICODE_ESCAPE = /^[0-9a-fA-F]{4}$/;
|
|
3340
|
+
var LEADING_WHITESPACE = /^[ \t\f]+/;
|
|
3341
|
+
var TRAILING_TERMINATOR = /(?:\r\n|\r|\n)$/;
|
|
3342
|
+
function isPropertiesWhitespace(char) {
|
|
3343
|
+
return char === " " || char === " " || char === "\f";
|
|
3344
|
+
}
|
|
3345
|
+
function splitPhysicalLines(content) {
|
|
3346
|
+
if (content === "") {
|
|
3347
|
+
return [];
|
|
3348
|
+
}
|
|
3349
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
3350
|
+
if (TRAILING_TERMINATOR.test(content)) {
|
|
3351
|
+
lines.pop();
|
|
3352
|
+
}
|
|
3353
|
+
return lines;
|
|
3354
|
+
}
|
|
3355
|
+
function countTrailingBackslashes(line) {
|
|
3356
|
+
let count = 0;
|
|
3357
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) {
|
|
3358
|
+
count += 1;
|
|
3359
|
+
}
|
|
3360
|
+
return count;
|
|
3361
|
+
}
|
|
3362
|
+
function isContinued(line) {
|
|
3363
|
+
return countTrailingBackslashes(line) % 2 === 1;
|
|
3364
|
+
}
|
|
3365
|
+
function isBlankLine(line) {
|
|
3366
|
+
return line.trim() === "";
|
|
3367
|
+
}
|
|
3368
|
+
function isCommentLine(line) {
|
|
3369
|
+
const trimmed = line.replace(LEADING_WHITESPACE, "");
|
|
3370
|
+
return trimmed.startsWith("#") || trimmed.startsWith("!");
|
|
3371
|
+
}
|
|
3372
|
+
function joinContinuation(lines, start) {
|
|
3373
|
+
const pieces = [];
|
|
3374
|
+
let index = start;
|
|
3375
|
+
let current = lines[start] ?? "";
|
|
3376
|
+
while (isContinued(current) && index + 1 < lines.length) {
|
|
3377
|
+
pieces.push(current.slice(0, -1));
|
|
3378
|
+
index += 1;
|
|
3379
|
+
current = (lines[index] ?? "").replace(LEADING_WHITESPACE, "");
|
|
3380
|
+
}
|
|
3381
|
+
if (isContinued(current)) {
|
|
3382
|
+
current = current.slice(0, -1);
|
|
3383
|
+
}
|
|
3384
|
+
pieces.push(current);
|
|
3385
|
+
return { logical: pieces.join(""), nextIndex: index + 1 };
|
|
3386
|
+
}
|
|
3387
|
+
function decodeSimpleEscape(char) {
|
|
3388
|
+
switch (char) {
|
|
3389
|
+
case "t":
|
|
3390
|
+
return " ";
|
|
3391
|
+
case "n":
|
|
3392
|
+
return "\n";
|
|
3393
|
+
case "r":
|
|
3394
|
+
return "\r";
|
|
3395
|
+
case "f":
|
|
3396
|
+
return "\f";
|
|
3397
|
+
default:
|
|
3398
|
+
return char;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
function decodeUnicodeEscape(raw, at) {
|
|
3402
|
+
const hex = raw.slice(at, at + 4);
|
|
3403
|
+
if (!UNICODE_ESCAPE.test(hex)) {
|
|
3404
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file has a malformed unicode escape.");
|
|
3405
|
+
}
|
|
3406
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
3407
|
+
}
|
|
3408
|
+
function decodeEscapes(raw) {
|
|
3409
|
+
let out = "";
|
|
3410
|
+
let i = 0;
|
|
3411
|
+
while (i < raw.length) {
|
|
3412
|
+
const char = raw[i];
|
|
3413
|
+
if (char !== "\\") {
|
|
3414
|
+
out += char;
|
|
3415
|
+
i += 1;
|
|
3416
|
+
continue;
|
|
3417
|
+
}
|
|
3418
|
+
const next = raw[i + 1];
|
|
3419
|
+
if (next === void 0) {
|
|
3420
|
+
break;
|
|
3421
|
+
}
|
|
3422
|
+
if (next === "u") {
|
|
3423
|
+
out += decodeUnicodeEscape(raw, i + 2);
|
|
3424
|
+
i += 6;
|
|
3425
|
+
continue;
|
|
3426
|
+
}
|
|
3427
|
+
out += decodeSimpleEscape(next);
|
|
3428
|
+
i += 2;
|
|
3429
|
+
}
|
|
3430
|
+
return out;
|
|
3431
|
+
}
|
|
3432
|
+
function keyEndIndex(logical, from) {
|
|
3433
|
+
let i = from;
|
|
3434
|
+
while (i < logical.length) {
|
|
3435
|
+
const char = logical[i];
|
|
3436
|
+
if (char === "\\") {
|
|
3437
|
+
i += 2;
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
if (char === void 0 || isPropertiesWhitespace(char) || char === "=" || char === ":") {
|
|
3441
|
+
break;
|
|
3442
|
+
}
|
|
3443
|
+
i += 1;
|
|
3444
|
+
}
|
|
3445
|
+
return i;
|
|
3446
|
+
}
|
|
3447
|
+
function skipWhitespace(logical, from) {
|
|
3448
|
+
let i = from;
|
|
3449
|
+
while (i < logical.length) {
|
|
3450
|
+
const char = logical[i];
|
|
3451
|
+
if (char === void 0 || !isPropertiesWhitespace(char)) {
|
|
3452
|
+
break;
|
|
3453
|
+
}
|
|
3454
|
+
i += 1;
|
|
3455
|
+
}
|
|
3456
|
+
return i;
|
|
3457
|
+
}
|
|
3458
|
+
function parseEntryLine(logical) {
|
|
3459
|
+
const keyStart = skipWhitespace(logical, 0);
|
|
3460
|
+
const keyEnd = keyEndIndex(logical, keyStart);
|
|
3461
|
+
const afterKey = skipWhitespace(logical, keyEnd);
|
|
3462
|
+
const separator = logical[afterKey];
|
|
3463
|
+
const valueStart = separator === "=" || separator === ":" ? skipWhitespace(logical, afterKey + 1) : afterKey;
|
|
3464
|
+
return {
|
|
3465
|
+
key: decodeEscapes(logical.slice(keyStart, keyEnd)),
|
|
3466
|
+
value: decodeEscapes(logical.slice(valueStart))
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
function parseItems(content) {
|
|
3470
|
+
const lines = splitPhysicalLines(content);
|
|
3471
|
+
const items = [];
|
|
3472
|
+
let i = 0;
|
|
3473
|
+
while (i < lines.length) {
|
|
3474
|
+
const line = lines[i] ?? "";
|
|
3475
|
+
if (isBlankLine(line) || isCommentLine(line)) {
|
|
3476
|
+
items.push({ kind: "raw", text: line });
|
|
3477
|
+
i += 1;
|
|
3478
|
+
continue;
|
|
3479
|
+
}
|
|
3480
|
+
const { logical, nextIndex } = joinContinuation(lines, i);
|
|
3481
|
+
items.push({ kind: "entry", ...parseEntryLine(logical) });
|
|
3482
|
+
i = nextIndex;
|
|
3483
|
+
}
|
|
3484
|
+
return items;
|
|
3485
|
+
}
|
|
3486
|
+
function parsePropertiesEntries(content, namespace) {
|
|
3487
|
+
const map = /* @__PURE__ */ new Map();
|
|
3488
|
+
for (const item of parseItems(content)) {
|
|
3489
|
+
if (item.kind === "entry") {
|
|
3490
|
+
map.set(item.key, {
|
|
3491
|
+
key: item.key,
|
|
3492
|
+
namespace,
|
|
3493
|
+
value: item.value,
|
|
3494
|
+
placeholders: extractPropertiesPlaceholders(item.value),
|
|
3495
|
+
isPlural: false
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
return map;
|
|
3500
|
+
}
|
|
3501
|
+
function unicodeEscape(code) {
|
|
3502
|
+
return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3503
|
+
}
|
|
3504
|
+
function escapeChar(char, isFirst, isKey) {
|
|
3505
|
+
switch (char) {
|
|
3506
|
+
case "\\":
|
|
3507
|
+
return "\\\\";
|
|
3508
|
+
case " ":
|
|
3509
|
+
return "\\t";
|
|
3510
|
+
case "\n":
|
|
3511
|
+
return "\\n";
|
|
3512
|
+
case "\r":
|
|
3513
|
+
return "\\r";
|
|
3514
|
+
case "\f":
|
|
3515
|
+
return "\\f";
|
|
3516
|
+
case " ":
|
|
3517
|
+
return isKey || isFirst ? "\\ " : " ";
|
|
3518
|
+
}
|
|
3519
|
+
if (isKey && (char === "=" || char === ":" || char === "#" || char === "!")) {
|
|
3520
|
+
return `\\${char}`;
|
|
3521
|
+
}
|
|
3522
|
+
const code = char.charCodeAt(0);
|
|
3523
|
+
return code < 32 || code > 126 ? unicodeEscape(code) : char;
|
|
3524
|
+
}
|
|
3525
|
+
function escapeString(input, isKey) {
|
|
3526
|
+
let out = "";
|
|
3527
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
3528
|
+
const char = input[i];
|
|
3529
|
+
if (char !== void 0) {
|
|
3530
|
+
out += escapeChar(char, i === 0, isKey);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return out;
|
|
3534
|
+
}
|
|
3535
|
+
function formatEntry(key, value) {
|
|
3536
|
+
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3537
|
+
}
|
|
3538
|
+
function isFileNotFound(error) {
|
|
3539
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3540
|
+
}
|
|
3541
|
+
async function readStructure(filePath) {
|
|
3542
|
+
let outcome;
|
|
3543
|
+
try {
|
|
3544
|
+
outcome = await readBounded2(filePath);
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
if (isFileNotFound(error)) {
|
|
3547
|
+
return [];
|
|
3548
|
+
}
|
|
3549
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination file could not be read.");
|
|
3550
|
+
}
|
|
3551
|
+
if (outcome.kind === "not-a-file") {
|
|
3552
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
3553
|
+
}
|
|
3554
|
+
if (outcome.kind === "too-large") {
|
|
3555
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
3556
|
+
}
|
|
3557
|
+
return parseItems(outcome.content);
|
|
3558
|
+
}
|
|
3559
|
+
async function serializePropertiesEntries(entries, filePath) {
|
|
3560
|
+
const structure = await readStructure(filePath);
|
|
3561
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3562
|
+
const lines = [];
|
|
3563
|
+
for (const item of structure) {
|
|
3564
|
+
if (item.kind === "raw") {
|
|
3565
|
+
lines.push(item.text);
|
|
3566
|
+
continue;
|
|
3567
|
+
}
|
|
3568
|
+
const entry = entries.get(item.key);
|
|
3569
|
+
if (entry !== void 0 && !emitted.has(item.key)) {
|
|
3570
|
+
lines.push(formatEntry(item.key, entry.value));
|
|
3571
|
+
emitted.add(item.key);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
for (const [key, entry] of entries) {
|
|
3575
|
+
if (!emitted.has(key)) {
|
|
3576
|
+
lines.push(formatEntry(key, entry.value));
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
return lines.length === 0 ? "" : `${lines.join("\n")}
|
|
3580
|
+
`;
|
|
3581
|
+
}
|
|
3582
|
+
function createPropertiesAdapter() {
|
|
3583
|
+
return createFlatFileAdapter({
|
|
3584
|
+
format: "properties",
|
|
3585
|
+
extensions: [".properties"],
|
|
3586
|
+
parseEntries: parsePropertiesEntries,
|
|
3587
|
+
serializeEntries: serializePropertiesEntries,
|
|
3588
|
+
extractPlaceholders: extractPropertiesPlaceholders
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3113
3591
|
var AdapterRegistry = class {
|
|
3114
3592
|
adapters = [];
|
|
3115
3593
|
/**
|
|
@@ -3187,43 +3665,6 @@ function createVueI18nJsonAdapter() {
|
|
|
3187
3665
|
})
|
|
3188
3666
|
});
|
|
3189
3667
|
}
|
|
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
3668
|
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
3228
3669
|
function extractXliffPlaceholders(value) {
|
|
3229
3670
|
const result = [];
|
|
@@ -3534,7 +3975,7 @@ function createYamlAdapter() {
|
|
|
3534
3975
|
});
|
|
3535
3976
|
}
|
|
3536
3977
|
function createDefaultRegistry() {
|
|
3537
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
3978
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter()).register(createPropertiesAdapter());
|
|
3538
3979
|
}
|
|
3539
3980
|
|
|
3540
3981
|
// src/selection/select-adapter.ts
|
|
@@ -3655,6 +4096,28 @@ async function diff(input, deps = {}) {
|
|
|
3655
4096
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3656
4097
|
}
|
|
3657
4098
|
|
|
4099
|
+
// src/cache/fingerprint.ts
|
|
4100
|
+
function fingerprintModel(provider) {
|
|
4101
|
+
const options = provider.options;
|
|
4102
|
+
const model = options.model;
|
|
4103
|
+
return typeof model === "string" ? model : null;
|
|
4104
|
+
}
|
|
4105
|
+
function sortGlossary(glossary) {
|
|
4106
|
+
if (glossary === void 0) {
|
|
4107
|
+
return {};
|
|
4108
|
+
}
|
|
4109
|
+
return Object.fromEntries(Object.entries(glossary).sort(([a], [b]) => a < b ? -1 : 1));
|
|
4110
|
+
}
|
|
4111
|
+
function computeFingerprint(config) {
|
|
4112
|
+
const canonical = JSON.stringify({
|
|
4113
|
+
provider: config.provider.id,
|
|
4114
|
+
model: fingerprintModel(config.provider),
|
|
4115
|
+
tone: config.tone ?? null,
|
|
4116
|
+
glossary: sortGlossary(config.glossary)
|
|
4117
|
+
});
|
|
4118
|
+
return stableStringHash(canonical);
|
|
4119
|
+
}
|
|
4120
|
+
|
|
3658
4121
|
// src/flow/integrity-gate.ts
|
|
3659
4122
|
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3660
4123
|
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
@@ -3705,6 +4168,12 @@ async function editEntry(input, deps = {}) {
|
|
|
3705
4168
|
mode: "merge",
|
|
3706
4169
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3707
4170
|
});
|
|
4171
|
+
await feedTranslationMemory(
|
|
4172
|
+
cwd,
|
|
4173
|
+
fs,
|
|
4174
|
+
computeFingerprint(config),
|
|
4175
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: input.value }]])
|
|
4176
|
+
);
|
|
3708
4177
|
return { accepted: true, value: input.value };
|
|
3709
4178
|
});
|
|
3710
4179
|
}
|
|
@@ -3922,13 +4391,19 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
3922
4391
|
mode: "merge",
|
|
3923
4392
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3924
4393
|
});
|
|
4394
|
+
await feedTranslationMemory(
|
|
4395
|
+
cwd,
|
|
4396
|
+
fs,
|
|
4397
|
+
computeFingerprint(config),
|
|
4398
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: value }]])
|
|
4399
|
+
);
|
|
3925
4400
|
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3926
4401
|
return { accepted: true, value, reviewReasons };
|
|
3927
4402
|
});
|
|
3928
4403
|
}
|
|
3929
4404
|
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3930
4405
|
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3931
|
-
var
|
|
4406
|
+
var CURRENT_VERSION3 = 1;
|
|
3932
4407
|
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3933
4408
|
var reviewReasonCodeSchema = zod.z.enum([
|
|
3934
4409
|
"LENGTH_RATIO_OUTLIER",
|
|
@@ -3978,7 +4453,7 @@ function toRunStatusLocale(locale) {
|
|
|
3978
4453
|
}
|
|
3979
4454
|
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3980
4455
|
return {
|
|
3981
|
-
version:
|
|
4456
|
+
version: CURRENT_VERSION3,
|
|
3982
4457
|
generatedAt,
|
|
3983
4458
|
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3984
4459
|
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
@@ -4011,7 +4486,7 @@ async function readRunStatusFile(path, fs) {
|
|
|
4011
4486
|
return void 0;
|
|
4012
4487
|
}
|
|
4013
4488
|
const result = runStatusFileSchema.safeParse(parsed);
|
|
4014
|
-
if (!result.success || result.data.version !==
|
|
4489
|
+
if (!result.success || result.data.version !== CURRENT_VERSION3) {
|
|
4015
4490
|
return void 0;
|
|
4016
4491
|
}
|
|
4017
4492
|
return fromParsed(result.data);
|
|
@@ -4090,12 +4565,16 @@ function failureSummary(locale, error) {
|
|
|
4090
4565
|
orphaned: [],
|
|
4091
4566
|
pruned: [],
|
|
4092
4567
|
invalidIcuSource: [],
|
|
4568
|
+
cacheHits: [],
|
|
4093
4569
|
integrityMismatches: [],
|
|
4094
4570
|
providerFailures: [],
|
|
4095
4571
|
budgetWithheld: [],
|
|
4096
4572
|
generated: [],
|
|
4097
4573
|
notices: [],
|
|
4098
4574
|
needsReview: [],
|
|
4575
|
+
unfilled: [],
|
|
4576
|
+
malformedRows: [],
|
|
4577
|
+
duplicateKeys: [],
|
|
4099
4578
|
error: describeError(error)
|
|
4100
4579
|
};
|
|
4101
4580
|
}
|
|
@@ -4104,7 +4583,7 @@ function deriveLocaleStatus(parts) {
|
|
|
4104
4583
|
if (!withheld) {
|
|
4105
4584
|
return "succeeded";
|
|
4106
4585
|
}
|
|
4107
|
-
const accepted = parts.translated.length > 0 || parts.generated.length > 0;
|
|
4586
|
+
const accepted = parts.translated.length > 0 || parts.cacheHits.length > 0 || parts.generated.length > 0;
|
|
4108
4587
|
return accepted ? "partial" : "failed";
|
|
4109
4588
|
}
|
|
4110
4589
|
function partition(locales) {
|
|
@@ -4424,6 +4903,97 @@ function foldGenerationItem(item, result, adapter, accepted, withheld, providerF
|
|
|
4424
4903
|
}
|
|
4425
4904
|
|
|
4426
4905
|
// src/flow/locale-run.ts
|
|
4906
|
+
function partitionCacheHits(params, toTranslate) {
|
|
4907
|
+
const cache = params.cache;
|
|
4908
|
+
const hits = /* @__PURE__ */ new Map();
|
|
4909
|
+
if (cache === void 0) {
|
|
4910
|
+
return { hits, misses: toTranslate };
|
|
4911
|
+
}
|
|
4912
|
+
const misses = [];
|
|
4913
|
+
for (const key of toTranslate) {
|
|
4914
|
+
const source = params.source.entries.get(key);
|
|
4915
|
+
if (source === void 0) {
|
|
4916
|
+
continue;
|
|
4917
|
+
}
|
|
4918
|
+
const cached = lookupMemory(
|
|
4919
|
+
cache.snapshot,
|
|
4920
|
+
cache.fingerprint,
|
|
4921
|
+
params.targetLocale,
|
|
4922
|
+
contentHash(source)
|
|
4923
|
+
);
|
|
4924
|
+
if (cached !== void 0 && gateCandidateValue(source, cached, params.adapter).accepted) {
|
|
4925
|
+
hits.set(key, { value: cached, source });
|
|
4926
|
+
} else {
|
|
4927
|
+
misses.push(key);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
return { hits, misses };
|
|
4931
|
+
}
|
|
4932
|
+
function collectCacheAdditions(params, accepted, cacheHitKeys) {
|
|
4933
|
+
if (params.cache === void 0) {
|
|
4934
|
+
return [];
|
|
4935
|
+
}
|
|
4936
|
+
const additions = [];
|
|
4937
|
+
for (const [key, entry] of accepted) {
|
|
4938
|
+
if (!cacheHitKeys.has(key)) {
|
|
4939
|
+
additions.push({ contentHash: contentHash(entry.source), value: entry.value });
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
return additions;
|
|
4943
|
+
}
|
|
4944
|
+
function groupMissesByContent(params, misses) {
|
|
4945
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4946
|
+
for (const key of misses) {
|
|
4947
|
+
const source = params.source.entries.get(key);
|
|
4948
|
+
if (source === void 0) {
|
|
4949
|
+
continue;
|
|
4950
|
+
}
|
|
4951
|
+
const existing = byHash.get(contentHash(source));
|
|
4952
|
+
if (existing === void 0) {
|
|
4953
|
+
byHash.set(contentHash(source), { representative: key, duplicates: [] });
|
|
4954
|
+
} else {
|
|
4955
|
+
existing.duplicates.push(key);
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
return [...byHash.values()];
|
|
4959
|
+
}
|
|
4960
|
+
function fanOutContentDuplicates(params, groups, outcome) {
|
|
4961
|
+
for (const group of groups) {
|
|
4962
|
+
if (group.duplicates.length > 0) {
|
|
4963
|
+
applyGroupOutcome(params, group, outcome);
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
function applyGroupOutcome(params, group, outcome) {
|
|
4968
|
+
const acceptedRepresentative = outcome.accepted.get(group.representative);
|
|
4969
|
+
if (acceptedRepresentative !== void 0) {
|
|
4970
|
+
fanOutAccepted(params, group, acceptedRepresentative, outcome);
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
withheldBucketFor(group.representative, outcome).push(...group.duplicates);
|
|
4974
|
+
}
|
|
4975
|
+
function fanOutAccepted(params, group, acceptedRepresentative, outcome) {
|
|
4976
|
+
const flag = outcome.reviewFlags.get(group.representative);
|
|
4977
|
+
for (const key of group.duplicates) {
|
|
4978
|
+
const source = params.source.entries.get(key);
|
|
4979
|
+
if (source === void 0) {
|
|
4980
|
+
continue;
|
|
4981
|
+
}
|
|
4982
|
+
outcome.accepted.set(key, { value: acceptedRepresentative.value, source });
|
|
4983
|
+
if (flag !== void 0) {
|
|
4984
|
+
outcome.reviewFlags.set(key, flag);
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
function withheldBucketFor(representative, outcome) {
|
|
4989
|
+
if (outcome.integrityMismatches.includes(representative)) {
|
|
4990
|
+
return outcome.integrityMismatches;
|
|
4991
|
+
}
|
|
4992
|
+
if (outcome.budgetWithheld.includes(representative)) {
|
|
4993
|
+
return outcome.budgetWithheld;
|
|
4994
|
+
}
|
|
4995
|
+
return outcome.providerFailures;
|
|
4996
|
+
}
|
|
4427
4997
|
function buildRequest3(params, entries) {
|
|
4428
4998
|
return {
|
|
4429
4999
|
sourceLocale: params.sourceLocale,
|
|
@@ -4466,6 +5036,7 @@ async function runLocale(params) {
|
|
|
4466
5036
|
orphaned,
|
|
4467
5037
|
invalidIcuSource,
|
|
4468
5038
|
translated: toTranslate,
|
|
5039
|
+
cacheHits: [],
|
|
4469
5040
|
generated: [],
|
|
4470
5041
|
integrityMismatches: [],
|
|
4471
5042
|
providerFailures: [],
|
|
@@ -4473,12 +5044,16 @@ async function runLocale(params) {
|
|
|
4473
5044
|
pruned,
|
|
4474
5045
|
notices: sdkNotices
|
|
4475
5046
|
}),
|
|
4476
|
-
lockEntries: {}
|
|
5047
|
+
lockEntries: {},
|
|
5048
|
+
cacheAdditions: []
|
|
4477
5049
|
};
|
|
4478
5050
|
}
|
|
4479
|
-
const
|
|
5051
|
+
const partition2 = partitionCacheHits(params, toTranslate);
|
|
5052
|
+
const cacheHitKeys = new Set(partition2.hits.keys());
|
|
5053
|
+
const missGroups = groupMissesByContent(params, partition2.misses);
|
|
5054
|
+
const entries = missGroups.map((group) => params.source.entries.get(group.representative)).filter((entry) => entry !== void 0);
|
|
4480
5055
|
const startedStopped = params.budget.stopped;
|
|
4481
|
-
const accepted =
|
|
5056
|
+
const accepted = new Map(partition2.hits);
|
|
4482
5057
|
const integrityMismatches = [];
|
|
4483
5058
|
const providerFailures = [];
|
|
4484
5059
|
const budgetWithheld = [];
|
|
@@ -4493,6 +5068,13 @@ async function runLocale(params) {
|
|
|
4493
5068
|
budgetWithheld,
|
|
4494
5069
|
reviewFlags
|
|
4495
5070
|
);
|
|
5071
|
+
fanOutContentDuplicates(params, missGroups, {
|
|
5072
|
+
accepted,
|
|
5073
|
+
integrityMismatches,
|
|
5074
|
+
providerFailures,
|
|
5075
|
+
budgetWithheld,
|
|
5076
|
+
reviewFlags
|
|
5077
|
+
});
|
|
4496
5078
|
const merged = new Map(target.entries);
|
|
4497
5079
|
for (const key of pruned) {
|
|
4498
5080
|
merged.delete(key);
|
|
@@ -4539,7 +5121,8 @@ async function runLocale(params) {
|
|
|
4539
5121
|
unchanged: diff2.unchanged,
|
|
4540
5122
|
orphaned,
|
|
4541
5123
|
invalidIcuSource,
|
|
4542
|
-
translated: [...accepted.keys()],
|
|
5124
|
+
translated: [...accepted.keys()].filter((key) => !cacheHitKeys.has(key)),
|
|
5125
|
+
cacheHits: [...cacheHitKeys].sort(),
|
|
4543
5126
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
4544
5127
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
4545
5128
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
@@ -4549,7 +5132,8 @@ async function runLocale(params) {
|
|
|
4549
5132
|
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
4550
5133
|
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
4551
5134
|
}),
|
|
4552
|
-
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
5135
|
+
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted),
|
|
5136
|
+
cacheAdditions: collectCacheAdditions(params, accepted, cacheHitKeys)
|
|
4553
5137
|
};
|
|
4554
5138
|
}
|
|
4555
5139
|
var NO_GENERATION_RESULT = {
|
|
@@ -4600,12 +5184,16 @@ function baseSummary(parts) {
|
|
|
4600
5184
|
orphaned: parts.orphaned,
|
|
4601
5185
|
pruned: parts.pruned,
|
|
4602
5186
|
invalidIcuSource: parts.invalidIcuSource,
|
|
5187
|
+
cacheHits: parts.cacheHits,
|
|
4603
5188
|
integrityMismatches: parts.integrityMismatches,
|
|
4604
5189
|
providerFailures: parts.providerFailures,
|
|
4605
5190
|
budgetWithheld: parts.budgetWithheld,
|
|
4606
5191
|
generated: parts.generated,
|
|
4607
5192
|
notices: parts.notices,
|
|
4608
5193
|
needsReview: parts.needsReview ?? [],
|
|
5194
|
+
unfilled: [],
|
|
5195
|
+
malformedRows: [],
|
|
5196
|
+
duplicateKeys: [],
|
|
4609
5197
|
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
4610
5198
|
};
|
|
4611
5199
|
}
|
|
@@ -4623,7 +5211,16 @@ async function translateAndCheck(provider, params, entries, accepted, integrityM
|
|
|
4623
5211
|
const notices = [];
|
|
4624
5212
|
const usage = createUsageAccumulator();
|
|
4625
5213
|
let tripped = false;
|
|
4626
|
-
|
|
5214
|
+
const batches = chunk(entries, params.maxBatchSize);
|
|
5215
|
+
let batchIndex = 0;
|
|
5216
|
+
for (const batch of batches) {
|
|
5217
|
+
batchIndex += 1;
|
|
5218
|
+
params.onProgress?.({
|
|
5219
|
+
type: "sub-batch",
|
|
5220
|
+
locale: params.targetLocale,
|
|
5221
|
+
batchIndex,
|
|
5222
|
+
totalBatches: batches.length
|
|
5223
|
+
});
|
|
4627
5224
|
if (params.budget.stopped) {
|
|
4628
5225
|
for (const entry of batch) {
|
|
4629
5226
|
budgetWithheld.push(entry.key);
|
|
@@ -4769,6 +5366,26 @@ async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
|
4769
5366
|
} catch {
|
|
4770
5367
|
}
|
|
4771
5368
|
}
|
|
5369
|
+
async function createRunCacheState(input, config, cwd, dryRun, fs) {
|
|
5370
|
+
if (dryRun || input.cache === false) {
|
|
5371
|
+
return void 0;
|
|
5372
|
+
}
|
|
5373
|
+
return {
|
|
5374
|
+
memory: await readTranslationMemory(cacheFilePath(cwd), fs),
|
|
5375
|
+
fingerprint: computeFingerprint(config),
|
|
5376
|
+
additions: /* @__PURE__ */ new Map()
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
async function recordCacheAdditions(cwd, cache, fs) {
|
|
5380
|
+
if (cache === void 0 || cache.additions.size === 0) {
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
try {
|
|
5384
|
+
const merged = applyAdditions(cache.memory, cache.fingerprint, cache.additions);
|
|
5385
|
+
await writeTranslationMemory(cacheFilePath(cwd), merged, fs);
|
|
5386
|
+
} catch {
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
4772
5389
|
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4773
5390
|
return {
|
|
4774
5391
|
source: context.source.resource,
|
|
@@ -4787,7 +5404,9 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
4787
5404
|
generatePlurals: context.generatePlurals,
|
|
4788
5405
|
maxBatchSize: context.maxBatchSize,
|
|
4789
5406
|
fs: context.fs,
|
|
4790
|
-
budget: context.budget
|
|
5407
|
+
budget: context.budget,
|
|
5408
|
+
...context.cache !== void 0 ? { cache: { snapshot: context.cache.memory, fingerprint: context.cache.fingerprint } } : {},
|
|
5409
|
+
...context.onProgress !== void 0 ? { onProgress: context.onProgress } : {}
|
|
4791
5410
|
};
|
|
4792
5411
|
}
|
|
4793
5412
|
async function runDryLocale(context, targetLocale, lock) {
|
|
@@ -4811,6 +5430,9 @@ async function runLiveLocale(context, targetLocale) {
|
|
|
4811
5430
|
mode: "replace",
|
|
4812
5431
|
entries: result.lockEntries
|
|
4813
5432
|
});
|
|
5433
|
+
if (context.cache !== void 0 && result.cacheAdditions.length > 0) {
|
|
5434
|
+
context.cache.additions.set(targetLocale, additionsToRecord(result.cacheAdditions));
|
|
5435
|
+
}
|
|
4814
5436
|
return result.summary;
|
|
4815
5437
|
},
|
|
4816
5438
|
lockOptions
|
|
@@ -4826,27 +5448,88 @@ async function runOneLocale(targetLocale, run) {
|
|
|
4826
5448
|
return failureSummary(targetLocale, error);
|
|
4827
5449
|
}
|
|
4828
5450
|
}
|
|
4829
|
-
async function
|
|
5451
|
+
async function runLocaleAt(context, targetLocales, localeIndex, runOne, results) {
|
|
5452
|
+
const targetLocale = targetLocales[localeIndex];
|
|
5453
|
+
if (targetLocale === void 0) {
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5456
|
+
context.onProgress?.({
|
|
5457
|
+
type: "locale-started",
|
|
5458
|
+
locale: targetLocale,
|
|
5459
|
+
localeIndex,
|
|
5460
|
+
totalLocales: targetLocales.length
|
|
5461
|
+
});
|
|
5462
|
+
const summary = await runOneLocale(targetLocale, () => runOne(targetLocale));
|
|
5463
|
+
results[localeIndex] = summary;
|
|
5464
|
+
context.onProgress?.({
|
|
5465
|
+
type: "locale-finished",
|
|
5466
|
+
locale: targetLocale,
|
|
5467
|
+
translated: summary.translated.length
|
|
5468
|
+
});
|
|
5469
|
+
}
|
|
5470
|
+
async function runLocalesWithProgress(context, targetLocales, runOne, concurrency) {
|
|
5471
|
+
const totalLocales = targetLocales.length;
|
|
5472
|
+
const results = new Array(totalLocales);
|
|
5473
|
+
let nextIndex = 0;
|
|
5474
|
+
async function worker() {
|
|
5475
|
+
while (nextIndex < totalLocales) {
|
|
5476
|
+
const localeIndex = nextIndex;
|
|
5477
|
+
nextIndex += 1;
|
|
5478
|
+
await runLocaleAt(context, targetLocales, localeIndex, runOne, results);
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
const workerCount = Math.min(concurrency, totalLocales);
|
|
5482
|
+
const workers = [];
|
|
5483
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
5484
|
+
workers.push(worker());
|
|
5485
|
+
}
|
|
5486
|
+
await Promise.all(workers);
|
|
5487
|
+
return results.filter((summary) => summary !== void 0);
|
|
5488
|
+
}
|
|
5489
|
+
async function runAllLocalesDry(context, targetLocales, concurrency) {
|
|
4830
5490
|
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
5491
|
+
return runLocalesWithProgress(
|
|
5492
|
+
context,
|
|
5493
|
+
targetLocales,
|
|
5494
|
+
(targetLocale) => runDryLocale(context, targetLocale, lock),
|
|
5495
|
+
concurrency
|
|
5496
|
+
);
|
|
5497
|
+
}
|
|
5498
|
+
async function runAllLocalesLive(context, targetLocales, concurrency) {
|
|
5499
|
+
return runLocalesWithProgress(
|
|
5500
|
+
context,
|
|
5501
|
+
targetLocales,
|
|
5502
|
+
(targetLocale) => runLiveLocale(context, targetLocale),
|
|
5503
|
+
concurrency
|
|
5504
|
+
);
|
|
5505
|
+
}
|
|
5506
|
+
function resolveConcurrency(value) {
|
|
5507
|
+
if (value === void 0) {
|
|
5508
|
+
return 1;
|
|
5509
|
+
}
|
|
5510
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5511
|
+
throw new SdkError(
|
|
5512
|
+
"CONCURRENCY_INVALID",
|
|
5513
|
+
`The concurrency option must be an integer of at least 1, got ${value}.`
|
|
4835
5514
|
);
|
|
4836
5515
|
}
|
|
4837
|
-
return
|
|
5516
|
+
return value;
|
|
4838
5517
|
}
|
|
4839
|
-
|
|
4840
|
-
const
|
|
4841
|
-
|
|
4842
|
-
|
|
5518
|
+
function resolveRunConcurrency(value, dryRun, config) {
|
|
5519
|
+
const concurrency = resolveConcurrency(value);
|
|
5520
|
+
if (!dryRun && concurrency > 1 && config.maxTokens !== void 0) {
|
|
5521
|
+
throw new SdkError(
|
|
5522
|
+
"CONCURRENCY_BUDGET_CONFLICT",
|
|
5523
|
+
"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."
|
|
5524
|
+
);
|
|
4843
5525
|
}
|
|
4844
|
-
return
|
|
5526
|
+
return concurrency;
|
|
4845
5527
|
}
|
|
4846
5528
|
async function translate2(input, deps = {}) {
|
|
4847
5529
|
const config = input.config;
|
|
4848
5530
|
const cwd = input.cwd ?? process.cwd();
|
|
4849
5531
|
const dryRun = input.dryRun ?? false;
|
|
5532
|
+
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
4850
5533
|
const prune = input.prune ?? config.prune ?? false;
|
|
4851
5534
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
4852
5535
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
@@ -4858,6 +5541,7 @@ async function translate2(input, deps = {}) {
|
|
|
4858
5541
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4859
5542
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
4860
5543
|
const source = await readSource(config, cwd, fs, adapter);
|
|
5544
|
+
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
4861
5545
|
const context = {
|
|
4862
5546
|
source,
|
|
4863
5547
|
adapter,
|
|
@@ -4869,10 +5553,13 @@ async function translate2(input, deps = {}) {
|
|
|
4869
5553
|
maxBatchSize,
|
|
4870
5554
|
fs,
|
|
4871
5555
|
budget,
|
|
5556
|
+
cache,
|
|
4872
5557
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5558
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
4873
5559
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
4874
5560
|
};
|
|
4875
|
-
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
5561
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales, concurrency) : await runAllLocalesLive(context, config.targetLocales, concurrency);
|
|
5562
|
+
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
4876
5563
|
const { succeeded, partial, failed } = partition(summaries);
|
|
4877
5564
|
const usage = summaries.reduce(
|
|
4878
5565
|
(total, summary2) => combineUsage(total, summary2.usage),
|
|
@@ -4888,6 +5575,7 @@ async function translate2(input, deps = {}) {
|
|
|
4888
5575
|
...usage !== void 0 ? { usage } : {},
|
|
4889
5576
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4890
5577
|
};
|
|
5578
|
+
await recordCacheAdditions(cwd, cache, fs);
|
|
4891
5579
|
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4892
5580
|
return summary;
|
|
4893
5581
|
}
|
|
@@ -4903,8 +5591,12 @@ var INSTRUCTIONS_LINES = [
|
|
|
4903
5591
|
"How to use this workbook",
|
|
4904
5592
|
"",
|
|
4905
5593
|
"1. There is one sheet per language. Open the sheet named for the language you are translating.",
|
|
5594
|
+
" Do not rename, delete, or reorder the language tabs. verbatra matches each tab to a language by",
|
|
5595
|
+
" its exact name; a renamed or missing tab is reported and its translations are not imported.",
|
|
4906
5596
|
"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
|
|
5597
|
+
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty",
|
|
5598
|
+
" string. A cell that contains only spaces is treated the same as an empty cell.",
|
|
5599
|
+
" To deliberately clear an existing value (set it to empty), type exactly [[CLEAR]] in the cell.",
|
|
4908
5600
|
"4. Keep placeholders exactly as they appear. A token such as {name} or {count} must stay verbatim,",
|
|
4909
5601
|
" in your translation, with the same spelling. Do not translate or remove it.",
|
|
4910
5602
|
"5. For ICU messages (for example {count, plural, one {# item} other {# items}}), keep the ICU",
|
|
@@ -5069,6 +5761,26 @@ function buildInstructionsSheet(workbook) {
|
|
|
5069
5761
|
}
|
|
5070
5762
|
sheet.getRow(1).font = { bold: true };
|
|
5071
5763
|
}
|
|
5764
|
+
var WORKBOOK_PROTECTION_XML = '<workbookProtection lockStructure="1" lockWindows="0"/>';
|
|
5765
|
+
function spliceWorkbookProtection(xml) {
|
|
5766
|
+
const anchor = xml.includes("<bookViews") ? "<bookViews" : "<sheets";
|
|
5767
|
+
return xml.replace(anchor, `${WORKBOOK_PROTECTION_XML}${anchor}`);
|
|
5768
|
+
}
|
|
5769
|
+
async function protectWorkbookStructure(bytes) {
|
|
5770
|
+
let zip;
|
|
5771
|
+
try {
|
|
5772
|
+
zip = await JSZip__default.default.loadAsync(bytes);
|
|
5773
|
+
} catch {
|
|
5774
|
+
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5775
|
+
}
|
|
5776
|
+
const workbookPart = zip.file("xl/workbook.xml");
|
|
5777
|
+
if (workbookPart === null) {
|
|
5778
|
+
return bytes;
|
|
5779
|
+
}
|
|
5780
|
+
const xml = await workbookPart.async("string");
|
|
5781
|
+
zip.file("xl/workbook.xml", spliceWorkbookProtection(xml));
|
|
5782
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
5783
|
+
}
|
|
5072
5784
|
async function buildWorkbook(model) {
|
|
5073
5785
|
assertNoWorksheetNameCollisions(model.sheets);
|
|
5074
5786
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
@@ -5076,13 +5788,15 @@ async function buildWorkbook(model) {
|
|
|
5076
5788
|
for (const sheet of model.sheets) {
|
|
5077
5789
|
await buildDataSheet(workbook, sheet);
|
|
5078
5790
|
}
|
|
5791
|
+
let serialized;
|
|
5079
5792
|
try {
|
|
5080
5793
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
5081
|
-
|
|
5082
|
-
return Uint8Array.prototype.slice.call(view);
|
|
5794
|
+
serialized = buffer;
|
|
5083
5795
|
} catch {
|
|
5084
5796
|
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5085
5797
|
}
|
|
5798
|
+
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5799
|
+
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
5086
5800
|
}
|
|
5087
5801
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
5088
5802
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
@@ -5202,26 +5916,24 @@ function assertHeader(sheet) {
|
|
|
5202
5916
|
);
|
|
5203
5917
|
}
|
|
5204
5918
|
}
|
|
5205
|
-
|
|
5919
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5920
|
+
function parseRow(row) {
|
|
5206
5921
|
const candidate = {
|
|
5207
5922
|
key: cellString(row.getCell(COLUMN.key)),
|
|
5208
5923
|
source: cellString(row.getCell(COLUMN.source)),
|
|
5209
5924
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
5210
5925
|
status: cellString(row.getCell(COLUMN.status)),
|
|
5211
5926
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
5212
|
-
translation: cellString(row.getCell(COLUMN.translation)),
|
|
5927
|
+
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
5213
5928
|
context: cellString(row.getCell(COLUMN.context)),
|
|
5214
5929
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
5215
5930
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
5216
5931
|
};
|
|
5217
5932
|
const result = rowSchema.safeParse(candidate);
|
|
5218
5933
|
if (!result.success) {
|
|
5219
|
-
|
|
5220
|
-
"WORKBOOK_INVALID",
|
|
5221
|
-
`The sheet "${sheet.name}" has a row with a missing key or an unrecognized status.`
|
|
5222
|
-
);
|
|
5934
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
5223
5935
|
}
|
|
5224
|
-
return result.data;
|
|
5936
|
+
return { ok: true, row: result.data };
|
|
5225
5937
|
}
|
|
5226
5938
|
function readDataSheet(sheet, limits) {
|
|
5227
5939
|
assertHeader(sheet);
|
|
@@ -5232,6 +5944,9 @@ function readDataSheet(sheet, limits) {
|
|
|
5232
5944
|
);
|
|
5233
5945
|
}
|
|
5234
5946
|
const rows = [];
|
|
5947
|
+
const malformed = [];
|
|
5948
|
+
const duplicates = [];
|
|
5949
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
5235
5950
|
for (let rowNumber = HEADER_ROW + 1; rowNumber <= sheet.rowCount; rowNumber += 1) {
|
|
5236
5951
|
const row = sheet.getRow(rowNumber);
|
|
5237
5952
|
if (row.cellCount > limits.maxCellsPerRow) {
|
|
@@ -5243,9 +5958,19 @@ function readDataSheet(sheet, limits) {
|
|
|
5243
5958
|
if (cellString(row.getCell(COLUMN.key)) === "") {
|
|
5244
5959
|
continue;
|
|
5245
5960
|
}
|
|
5246
|
-
|
|
5961
|
+
const outcome = parseRow(row);
|
|
5962
|
+
if (!outcome.ok) {
|
|
5963
|
+
malformed.push({ locale: sheet.name, row: rowNumber, column: outcome.column });
|
|
5964
|
+
continue;
|
|
5965
|
+
}
|
|
5966
|
+
if (seenKeys.has(outcome.row.key)) {
|
|
5967
|
+
duplicates.push({ locale: sheet.name, key: outcome.row.key, row: rowNumber });
|
|
5968
|
+
continue;
|
|
5969
|
+
}
|
|
5970
|
+
seenKeys.add(outcome.row.key);
|
|
5971
|
+
rows.push(outcome.row);
|
|
5247
5972
|
}
|
|
5248
|
-
return { locale: sheet.name, rows };
|
|
5973
|
+
return { sheet: { locale: sheet.name, rows }, malformed, duplicates };
|
|
5249
5974
|
}
|
|
5250
5975
|
async function loadWorkbook(bytes) {
|
|
5251
5976
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
@@ -5268,13 +5993,18 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
5268
5993
|
);
|
|
5269
5994
|
}
|
|
5270
5995
|
const sheets = [];
|
|
5996
|
+
const malformedRows = [];
|
|
5997
|
+
const duplicateKeys = [];
|
|
5271
5998
|
for (const sheet of workbook.worksheets) {
|
|
5272
5999
|
if (sheet.name === INSTRUCTIONS_SHEET_NAME) {
|
|
5273
6000
|
continue;
|
|
5274
6001
|
}
|
|
5275
|
-
|
|
6002
|
+
const read = readDataSheet(sheet, limits);
|
|
6003
|
+
sheets.push(read.sheet);
|
|
6004
|
+
malformedRows.push(...read.malformed);
|
|
6005
|
+
duplicateKeys.push(...read.duplicates);
|
|
5276
6006
|
}
|
|
5277
|
-
return { sheets };
|
|
6007
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
5278
6008
|
}
|
|
5279
6009
|
|
|
5280
6010
|
// src/flow/workbook/export-workbook.ts
|
|
@@ -5375,6 +6105,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
5375
6105
|
}
|
|
5376
6106
|
|
|
5377
6107
|
// src/flow/workbook/import-locale.ts
|
|
6108
|
+
var CLEAR_SENTINEL = "[[CLEAR]]";
|
|
5378
6109
|
var UnknownKeyError = class extends Error {
|
|
5379
6110
|
key;
|
|
5380
6111
|
constructor(key) {
|
|
@@ -5403,9 +6134,20 @@ function trackBlankDrift(row, params, buckets) {
|
|
|
5403
6134
|
buckets.blankDrifted.add(row.key);
|
|
5404
6135
|
}
|
|
5405
6136
|
}
|
|
6137
|
+
function classifyClear(row, sourceEntry, buckets) {
|
|
6138
|
+
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
6139
|
+
buckets.mismatches.push(row.key);
|
|
6140
|
+
buckets.withheld.add(row.key);
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6143
|
+
buckets.accepted.set(row.key, { value: "", source: sourceEntry });
|
|
6144
|
+
}
|
|
5406
6145
|
function classifyRows(params, buckets) {
|
|
5407
6146
|
for (const row of params.sheet.rows) {
|
|
5408
6147
|
if (row.translation === "") {
|
|
6148
|
+
if (row.status === "changed") {
|
|
6149
|
+
buckets.unfilled.push(row.key);
|
|
6150
|
+
}
|
|
5409
6151
|
trackBlankDrift(row, params, buckets);
|
|
5410
6152
|
continue;
|
|
5411
6153
|
}
|
|
@@ -5416,6 +6158,10 @@ function classifyRows(params, buckets) {
|
|
|
5416
6158
|
if (sourceEntry === void 0) {
|
|
5417
6159
|
continue;
|
|
5418
6160
|
}
|
|
6161
|
+
if (row.translation === CLEAR_SENTINEL) {
|
|
6162
|
+
classifyClear(row, sourceEntry, buckets);
|
|
6163
|
+
continue;
|
|
6164
|
+
}
|
|
5419
6165
|
const reason = judge(row, sourceEntry, params.adapter);
|
|
5420
6166
|
if (reason === void 0) {
|
|
5421
6167
|
buckets.accepted.set(row.key, { value: row.translation, source: sourceEntry });
|
|
@@ -5437,7 +6183,8 @@ function importLocale(params) {
|
|
|
5437
6183
|
accepted: /* @__PURE__ */ new Map(),
|
|
5438
6184
|
mismatches: [],
|
|
5439
6185
|
withheld: /* @__PURE__ */ new Set(),
|
|
5440
|
-
blankDrifted: /* @__PURE__ */ new Set()
|
|
6186
|
+
blankDrifted: /* @__PURE__ */ new Set(),
|
|
6187
|
+
unfilled: []
|
|
5441
6188
|
};
|
|
5442
6189
|
classifyRows(params, buckets);
|
|
5443
6190
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
@@ -5448,6 +6195,7 @@ function importLocale(params) {
|
|
|
5448
6195
|
locale: params.sheet.locale,
|
|
5449
6196
|
status: deriveLocaleStatus({
|
|
5450
6197
|
translated,
|
|
6198
|
+
cacheHits: [],
|
|
5451
6199
|
generated: [],
|
|
5452
6200
|
integrityMismatches,
|
|
5453
6201
|
providerFailures: [],
|
|
@@ -5458,12 +6206,16 @@ function importLocale(params) {
|
|
|
5458
6206
|
orphaned: diff2.orphaned,
|
|
5459
6207
|
pruned: [],
|
|
5460
6208
|
invalidIcuSource,
|
|
6209
|
+
cacheHits: [],
|
|
5461
6210
|
integrityMismatches,
|
|
5462
6211
|
providerFailures: [],
|
|
5463
6212
|
budgetWithheld: [],
|
|
5464
6213
|
generated: [],
|
|
5465
6214
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
5466
|
-
needsReview: []
|
|
6215
|
+
needsReview: [],
|
|
6216
|
+
unfilled: [...new Set(buckets.unfilled)].sort(),
|
|
6217
|
+
malformedRows: params.malformedRows,
|
|
6218
|
+
duplicateKeys: params.duplicateKeys
|
|
5467
6219
|
};
|
|
5468
6220
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
5469
6221
|
}
|
|
@@ -5490,6 +6242,19 @@ function mergeAccepted(target, accepted) {
|
|
|
5490
6242
|
}
|
|
5491
6243
|
return merged;
|
|
5492
6244
|
}
|
|
6245
|
+
function sheetCacheAdditions(accepted) {
|
|
6246
|
+
const record = {};
|
|
6247
|
+
for (const [, { value, source }] of accepted) {
|
|
6248
|
+
record[contentHash(source)] = value;
|
|
6249
|
+
}
|
|
6250
|
+
return record;
|
|
6251
|
+
}
|
|
6252
|
+
function collectSheetAdditions(byLocale, locale, additions) {
|
|
6253
|
+
if (Object.keys(additions).length === 0) {
|
|
6254
|
+
return;
|
|
6255
|
+
}
|
|
6256
|
+
byLocale.set(locale, { ...byLocale.get(locale), ...additions });
|
|
6257
|
+
}
|
|
5493
6258
|
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
5494
6259
|
const entries = {};
|
|
5495
6260
|
for (const key of merged.keys()) {
|
|
@@ -5506,11 +6271,20 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
5506
6271
|
}
|
|
5507
6272
|
return entries;
|
|
5508
6273
|
}
|
|
6274
|
+
var MissingSheetError = class extends Error {
|
|
6275
|
+
code = "WORKBOOK_SHEET_MISSING";
|
|
6276
|
+
constructor(locale) {
|
|
6277
|
+
super(
|
|
6278
|
+
`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.`
|
|
6279
|
+
);
|
|
6280
|
+
this.name = "MissingSheetError";
|
|
6281
|
+
}
|
|
6282
|
+
};
|
|
5509
6283
|
async function runSheet(ctx, sheet, lock) {
|
|
5510
6284
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
5511
6285
|
throw new SdkError(
|
|
5512
6286
|
"CONFIG_INVALID",
|
|
5513
|
-
`The workbook has a sheet
|
|
6287
|
+
`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
6288
|
);
|
|
5515
6289
|
}
|
|
5516
6290
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -5521,10 +6295,12 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5521
6295
|
target,
|
|
5522
6296
|
baseline,
|
|
5523
6297
|
adapter: ctx.adapter,
|
|
5524
|
-
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys
|
|
6298
|
+
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6299
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6300
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
5525
6301
|
});
|
|
5526
6302
|
if (ctx.dryRun) {
|
|
5527
|
-
return { summary, lockEntries: {} };
|
|
6303
|
+
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
5528
6304
|
}
|
|
5529
6305
|
const merged = mergeAccepted(target, accepted);
|
|
5530
6306
|
if (accepted.size > 0) {
|
|
@@ -5539,7 +6315,11 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5539
6315
|
path
|
|
5540
6316
|
);
|
|
5541
6317
|
}
|
|
5542
|
-
return {
|
|
6318
|
+
return {
|
|
6319
|
+
summary,
|
|
6320
|
+
lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted),
|
|
6321
|
+
cacheAdditions: sheetCacheAdditions(accepted)
|
|
6322
|
+
};
|
|
5543
6323
|
}
|
|
5544
6324
|
async function importWorkbook(input, deps = {}) {
|
|
5545
6325
|
const config = input.config;
|
|
@@ -5564,9 +6344,12 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5564
6344
|
fs,
|
|
5565
6345
|
source: source.resource,
|
|
5566
6346
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
5567
|
-
dryRun
|
|
6347
|
+
dryRun,
|
|
6348
|
+
malformedRows: data.malformedRows,
|
|
6349
|
+
duplicateKeys: data.duplicateKeys
|
|
5568
6350
|
};
|
|
5569
6351
|
const summaries = [];
|
|
6352
|
+
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
5570
6353
|
for (const sheet of data.sheets) {
|
|
5571
6354
|
try {
|
|
5572
6355
|
let summary;
|
|
@@ -5579,6 +6362,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5579
6362
|
mode: "replace",
|
|
5580
6363
|
entries: result.lockEntries
|
|
5581
6364
|
});
|
|
6365
|
+
collectSheetAdditions(cacheAdditions, sheet.locale, result.cacheAdditions);
|
|
5582
6366
|
return result.summary;
|
|
5583
6367
|
});
|
|
5584
6368
|
}
|
|
@@ -5587,6 +6371,15 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5587
6371
|
summaries.push(failureSummary(sheet.locale, error));
|
|
5588
6372
|
}
|
|
5589
6373
|
}
|
|
6374
|
+
const presentLocales = new Set(data.sheets.map((sheet) => sheet.locale));
|
|
6375
|
+
for (const locale of config.targetLocales) {
|
|
6376
|
+
if (!presentLocales.has(locale)) {
|
|
6377
|
+
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
if (!dryRun) {
|
|
6381
|
+
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6382
|
+
}
|
|
5590
6383
|
const { succeeded, partial, failed } = partition(summaries);
|
|
5591
6384
|
return { dryRun, locales: summaries, succeeded, partial, failed };
|
|
5592
6385
|
}
|
|
@@ -5650,7 +6443,10 @@ async function watch(input, deps = {}) {
|
|
|
5650
6443
|
config: input.config,
|
|
5651
6444
|
cwd,
|
|
5652
6445
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5653
|
-
...input.
|
|
6446
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6447
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
6448
|
+
...input.concurrency !== void 0 ? { concurrency: input.concurrency } : {},
|
|
6449
|
+
...input.cache !== void 0 ? { cache: input.cache } : {}
|
|
5654
6450
|
};
|
|
5655
6451
|
let state = "idle";
|
|
5656
6452
|
let pending = false;
|
|
@@ -5718,6 +6514,7 @@ async function watch(input, deps = {}) {
|
|
|
5718
6514
|
return { stop };
|
|
5719
6515
|
}
|
|
5720
6516
|
|
|
6517
|
+
exports.CACHE_FILE_NAME = CACHE_FILE_NAME;
|
|
5721
6518
|
exports.DEFAULT_WORKBOOK_PATH = DEFAULT_WORKBOOK_PATH;
|
|
5722
6519
|
exports.LOCK_FILE_NAME = LOCK_FILE_NAME;
|
|
5723
6520
|
exports.SdkError = SdkError;
|