@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.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
1
|
import { dirname, resolve, join, basename, extname } from 'path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
import { cosmiconfig } from 'cosmiconfig';
|
|
4
5
|
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
5
6
|
import { randomUUID } from 'crypto';
|
|
6
7
|
import { rm, access, mkdir, open, writeFile, rename } from 'fs/promises';
|
|
7
|
-
import { z } from 'zod';
|
|
8
8
|
import Anthropic from '@anthropic-ai/sdk';
|
|
9
9
|
import * as deepl from 'deepl-node';
|
|
10
10
|
import { createRequire } from 'module';
|
|
@@ -15,10 +15,100 @@ import { parse, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
|
15
15
|
import { XMLSerializer, DOMParser } from '@xmldom/xmldom';
|
|
16
16
|
import { stringify, parse as parse$1 } from 'yaml';
|
|
17
17
|
import ExcelJS from 'exceljs';
|
|
18
|
-
import { Readable } from 'stream';
|
|
19
18
|
import JSZip from 'jszip';
|
|
19
|
+
import { Readable } from 'stream';
|
|
20
20
|
import { watch as watch$1 } from 'chokidar';
|
|
21
21
|
|
|
22
|
+
// src/cache/translation-memory.ts
|
|
23
|
+
var CACHE_FILE_NAME = "verbatra.cache.json";
|
|
24
|
+
var CURRENT_VERSION = 1;
|
|
25
|
+
var EMPTY_MEMORY = { version: CURRENT_VERSION, entries: {} };
|
|
26
|
+
var MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
|
|
27
|
+
var translationMemorySchema = z.object({
|
|
28
|
+
version: z.number().int().positive(),
|
|
29
|
+
entries: z.record(z.string(), z.record(z.string(), z.record(z.string(), z.string())))
|
|
30
|
+
});
|
|
31
|
+
function cacheFilePath(cwd) {
|
|
32
|
+
return resolve(cwd, CACHE_FILE_NAME);
|
|
33
|
+
}
|
|
34
|
+
async function readTranslationMemory(path, fs) {
|
|
35
|
+
let read;
|
|
36
|
+
try {
|
|
37
|
+
read = await fs.readFileBounded(path, MAX_CACHE_FILE_BYTES);
|
|
38
|
+
} catch {
|
|
39
|
+
return EMPTY_MEMORY;
|
|
40
|
+
}
|
|
41
|
+
if (read.kind !== "ok") {
|
|
42
|
+
return EMPTY_MEMORY;
|
|
43
|
+
}
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(read.content);
|
|
47
|
+
} catch {
|
|
48
|
+
return EMPTY_MEMORY;
|
|
49
|
+
}
|
|
50
|
+
const result = translationMemorySchema.safeParse(parsed);
|
|
51
|
+
if (!result.success || result.data.version !== CURRENT_VERSION) {
|
|
52
|
+
return EMPTY_MEMORY;
|
|
53
|
+
}
|
|
54
|
+
return result.data;
|
|
55
|
+
}
|
|
56
|
+
function lookupMemory(memory, fingerprint, locale, contentHash2) {
|
|
57
|
+
return memory.entries[fingerprint]?.[locale]?.[contentHash2];
|
|
58
|
+
}
|
|
59
|
+
function applyAdditions(base, fingerprint, additionsByLocale) {
|
|
60
|
+
if (additionsByLocale.size === 0) {
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
63
|
+
const fingerprintEntries = {};
|
|
64
|
+
for (const [locale, hashes] of Object.entries(base.entries[fingerprint] ?? {})) {
|
|
65
|
+
fingerprintEntries[locale] = { ...hashes };
|
|
66
|
+
}
|
|
67
|
+
for (const [locale, hashes] of additionsByLocale) {
|
|
68
|
+
fingerprintEntries[locale] = { ...fingerprintEntries[locale], ...hashes };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
version: base.version,
|
|
72
|
+
entries: { ...base.entries, [fingerprint]: fingerprintEntries }
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function additionsToRecord(additions) {
|
|
76
|
+
const record = {};
|
|
77
|
+
for (const addition of additions) {
|
|
78
|
+
record[addition.contentHash] = addition.value;
|
|
79
|
+
}
|
|
80
|
+
return record;
|
|
81
|
+
}
|
|
82
|
+
function sortEntries(record) {
|
|
83
|
+
return Object.entries(record).sort(([a], [b]) => a < b ? -1 : 1);
|
|
84
|
+
}
|
|
85
|
+
function serialize(memory) {
|
|
86
|
+
const entries = {};
|
|
87
|
+
for (const [fingerprint, locales] of sortEntries(memory.entries)) {
|
|
88
|
+
const localeMap = {};
|
|
89
|
+
for (const [locale, hashes] of sortEntries(locales)) {
|
|
90
|
+
localeMap[locale] = Object.fromEntries(sortEntries(hashes));
|
|
91
|
+
}
|
|
92
|
+
entries[fingerprint] = localeMap;
|
|
93
|
+
}
|
|
94
|
+
return `${JSON.stringify({ version: memory.version, entries }, null, 2)}
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
async function writeTranslationMemory(path, memory, fs) {
|
|
98
|
+
await fs.writeFile(path, serialize(memory));
|
|
99
|
+
}
|
|
100
|
+
async function feedTranslationMemory(cwd, fs, fingerprint, additionsByLocale) {
|
|
101
|
+
if (additionsByLocale.size === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const path = cacheFilePath(cwd);
|
|
106
|
+
const memory = await readTranslationMemory(path, fs);
|
|
107
|
+
await writeTranslationMemory(path, applyAdditions(memory, fingerprint, additionsByLocale), fs);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
22
112
|
// src/config/define-config.ts
|
|
23
113
|
function defineConfig(config) {
|
|
24
114
|
return config;
|
|
@@ -212,7 +302,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
212
302
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
213
303
|
var FNV_PRIME = 1099511628211n;
|
|
214
304
|
var U64_MASK = (1n << 64n) - 1n;
|
|
215
|
-
function
|
|
305
|
+
function stableStringHash(input) {
|
|
216
306
|
let hash = FNV_OFFSET_BASIS;
|
|
217
307
|
for (let index = 0; index < input.length; index += 1) {
|
|
218
308
|
hash ^= BigInt(input.charCodeAt(index));
|
|
@@ -233,7 +323,7 @@ function canonicalize(entry) {
|
|
|
233
323
|
]);
|
|
234
324
|
}
|
|
235
325
|
function contentHash(entry) {
|
|
236
|
-
return
|
|
326
|
+
return stableStringHash(canonicalize(entry));
|
|
237
327
|
}
|
|
238
328
|
function sorted(keys) {
|
|
239
329
|
return [...keys].sort();
|
|
@@ -278,7 +368,8 @@ var SUPPORTED_FORMATS = [
|
|
|
278
368
|
"ngx-translate-json",
|
|
279
369
|
"xliff",
|
|
280
370
|
"yaml",
|
|
281
|
-
"arb"
|
|
371
|
+
"arb",
|
|
372
|
+
"properties"
|
|
282
373
|
];
|
|
283
374
|
var supportedFormatSchema = z.enum(SUPPORTED_FORMATS);
|
|
284
375
|
var translationEntrySchema = z.object({
|
|
@@ -1220,14 +1311,14 @@ function throwAbort(signal) {
|
|
|
1220
1311
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1221
1312
|
}
|
|
1222
1313
|
function delay(ms, signal) {
|
|
1223
|
-
return new Promise((
|
|
1314
|
+
return new Promise((resolve10) => {
|
|
1224
1315
|
const onAbort = () => {
|
|
1225
1316
|
clearTimeout(timer);
|
|
1226
|
-
|
|
1317
|
+
resolve10();
|
|
1227
1318
|
};
|
|
1228
1319
|
const timer = setTimeout(() => {
|
|
1229
1320
|
signal?.removeEventListener("abort", onAbort);
|
|
1230
|
-
|
|
1321
|
+
resolve10();
|
|
1231
1322
|
}, ms);
|
|
1232
1323
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1233
1324
|
});
|
|
@@ -1965,8 +2056,8 @@ async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
|
1965
2056
|
|
|
1966
2057
|
// src/lock/lock-file.ts
|
|
1967
2058
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1968
|
-
var
|
|
1969
|
-
var EMPTY_LOCK = { version:
|
|
2059
|
+
var CURRENT_VERSION2 = 1;
|
|
2060
|
+
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
1970
2061
|
var MAX_LOCK_FILE_BYTES = 16 * 1024 * 1024;
|
|
1971
2062
|
var lockFileSchema = z.object({
|
|
1972
2063
|
version: z.number().int().positive(),
|
|
@@ -1995,10 +2086,10 @@ function parseLockFileRead(read, path) {
|
|
|
1995
2086
|
if (!result.success) {
|
|
1996
2087
|
throw new SdkError("LOCK_FILE_INVALID", `The lock-file at ${path} has an unexpected shape.`);
|
|
1997
2088
|
}
|
|
1998
|
-
if (result.data.version !==
|
|
2089
|
+
if (result.data.version !== CURRENT_VERSION2) {
|
|
1999
2090
|
throw new SdkError(
|
|
2000
2091
|
"LOCK_FILE_INVALID",
|
|
2001
|
-
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${
|
|
2092
|
+
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${CURRENT_VERSION2}.`
|
|
2002
2093
|
);
|
|
2003
2094
|
}
|
|
2004
2095
|
return result.data;
|
|
@@ -2788,7 +2879,7 @@ function createTreeFileAdapter(options) {
|
|
|
2788
2879
|
extensions,
|
|
2789
2880
|
sniff,
|
|
2790
2881
|
parse: parse3,
|
|
2791
|
-
serialize,
|
|
2882
|
+
serialize: serialize2,
|
|
2792
2883
|
deriveEntry,
|
|
2793
2884
|
extractPlaceholders,
|
|
2794
2885
|
computeInvalidIcuKeys,
|
|
@@ -2823,7 +2914,7 @@ function createTreeFileAdapter(options) {
|
|
|
2823
2914
|
},
|
|
2824
2915
|
async write(resource, filePath) {
|
|
2825
2916
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
2826
|
-
await atomicWriteFile(filePath,
|
|
2917
|
+
await atomicWriteFile(filePath, serialize2(tree));
|
|
2827
2918
|
}
|
|
2828
2919
|
};
|
|
2829
2920
|
}
|
|
@@ -3080,6 +3171,393 @@ function createNgxTranslateJsonAdapter() {
|
|
|
3080
3171
|
keyMode: "path-notation"
|
|
3081
3172
|
});
|
|
3082
3173
|
}
|
|
3174
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
3175
|
+
try {
|
|
3176
|
+
return parseEntries(content, namespace);
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
function createFlatFileAdapter(options) {
|
|
3182
|
+
const {
|
|
3183
|
+
format,
|
|
3184
|
+
extensions,
|
|
3185
|
+
sniff,
|
|
3186
|
+
parseEntries,
|
|
3187
|
+
serializeEntries,
|
|
3188
|
+
extractPlaceholders,
|
|
3189
|
+
validateMessage,
|
|
3190
|
+
computeInvalidIcuKeys
|
|
3191
|
+
} = options;
|
|
3192
|
+
return {
|
|
3193
|
+
format,
|
|
3194
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
3195
|
+
extractPlaceholders,
|
|
3196
|
+
validateMessage: validateMessage ?? (() => true),
|
|
3197
|
+
async read(filePath, locale) {
|
|
3198
|
+
const content = await readFileContent(filePath);
|
|
3199
|
+
const namespace = namespaceOf(filePath);
|
|
3200
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
3201
|
+
const resource = { locale, namespace, format, entries };
|
|
3202
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3203
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3204
|
+
},
|
|
3205
|
+
async write(resource, filePath) {
|
|
3206
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
3207
|
+
await atomicWriteFile(filePath, data);
|
|
3208
|
+
}
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordinal", "choice"]);
|
|
3212
|
+
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3213
|
+
function matchingBraces(value) {
|
|
3214
|
+
const close = new Int32Array(value.length).fill(-1);
|
|
3215
|
+
const open3 = [];
|
|
3216
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
3217
|
+
const char = value[i];
|
|
3218
|
+
if (char === "{") {
|
|
3219
|
+
open3.push(i);
|
|
3220
|
+
} else if (char === "}") {
|
|
3221
|
+
const start = open3.pop();
|
|
3222
|
+
if (start !== void 0) {
|
|
3223
|
+
close[start] = i;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
return close;
|
|
3228
|
+
}
|
|
3229
|
+
function findNameEnd(value, from, limit) {
|
|
3230
|
+
for (let i = from; i < limit; i += 1) {
|
|
3231
|
+
const char = value[i];
|
|
3232
|
+
if (char === ",") {
|
|
3233
|
+
return { comma: i, hasBrace: false };
|
|
3234
|
+
}
|
|
3235
|
+
if (char === "{" || char === "}") {
|
|
3236
|
+
return { comma: -1, hasBrace: true };
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
return { comma: -1, hasBrace: false };
|
|
3240
|
+
}
|
|
3241
|
+
function parseArgumentAt(value, open3, close) {
|
|
3242
|
+
const { comma, hasBrace } = findNameEnd(value, open3 + 1, close);
|
|
3243
|
+
if (hasBrace) {
|
|
3244
|
+
return null;
|
|
3245
|
+
}
|
|
3246
|
+
const nameEnd = comma === -1 ? close : comma;
|
|
3247
|
+
const name = value.slice(open3 + 1, nameEnd).trim();
|
|
3248
|
+
if (!ARGUMENT_NAME.test(name)) {
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
if (comma === -1) {
|
|
3252
|
+
return { name, type: null, styleStart: -1 };
|
|
3253
|
+
}
|
|
3254
|
+
const secondComma = value.indexOf(",", comma + 1);
|
|
3255
|
+
if (secondComma === -1 || secondComma >= close) {
|
|
3256
|
+
return { name, type: value.slice(comma + 1, close).trim(), styleStart: -1 };
|
|
3257
|
+
}
|
|
3258
|
+
return { name, type: value.slice(comma + 1, secondComma).trim(), styleStart: secondComma + 1 };
|
|
3259
|
+
}
|
|
3260
|
+
function canonicalToken(value, arg, close) {
|
|
3261
|
+
if (arg.type === null) {
|
|
3262
|
+
return `{${arg.name}}`;
|
|
3263
|
+
}
|
|
3264
|
+
if (arg.styleStart === -1) {
|
|
3265
|
+
return `{${arg.name},${arg.type}}`;
|
|
3266
|
+
}
|
|
3267
|
+
return `{${arg.name},${arg.type},${value.slice(arg.styleStart, close).trim()}}`;
|
|
3268
|
+
}
|
|
3269
|
+
function emitArgument(value, close, arg, closeIndex, out) {
|
|
3270
|
+
if (arg.type !== null && SUBMESSAGE_TYPES.has(arg.type)) {
|
|
3271
|
+
out.push(`{${arg.name},${arg.type}}`);
|
|
3272
|
+
if (arg.styleStart !== -1) {
|
|
3273
|
+
scanRange(value, close, arg.styleStart, closeIndex, false, out);
|
|
3274
|
+
}
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
out.push(canonicalToken(value, arg, closeIndex));
|
|
3278
|
+
}
|
|
3279
|
+
function scanRange(value, close, start, end, topLevel, out) {
|
|
3280
|
+
let i = start;
|
|
3281
|
+
while (i < end) {
|
|
3282
|
+
if (value[i] !== "{") {
|
|
3283
|
+
i += 1;
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
if (topLevel && value[i + 1] === "{") {
|
|
3287
|
+
i += 2;
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
const closeIndex = close[i] ?? -1;
|
|
3291
|
+
if (closeIndex === -1 || closeIndex >= end) {
|
|
3292
|
+
i += 1;
|
|
3293
|
+
continue;
|
|
3294
|
+
}
|
|
3295
|
+
const arg = parseArgumentAt(value, i, closeIndex);
|
|
3296
|
+
if (arg === null) {
|
|
3297
|
+
i += 1;
|
|
3298
|
+
continue;
|
|
3299
|
+
}
|
|
3300
|
+
emitArgument(value, close, arg, closeIndex, out);
|
|
3301
|
+
i = closeIndex + 1;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function extractPropertiesPlaceholders(value) {
|
|
3305
|
+
const out = [];
|
|
3306
|
+
scanRange(value, matchingBraces(value), 0, value.length, true, out);
|
|
3307
|
+
return out;
|
|
3308
|
+
}
|
|
3309
|
+
var UNICODE_ESCAPE = /^[0-9a-fA-F]{4}$/;
|
|
3310
|
+
var LEADING_WHITESPACE = /^[ \t\f]+/;
|
|
3311
|
+
var TRAILING_TERMINATOR = /(?:\r\n|\r|\n)$/;
|
|
3312
|
+
function isPropertiesWhitespace(char) {
|
|
3313
|
+
return char === " " || char === " " || char === "\f";
|
|
3314
|
+
}
|
|
3315
|
+
function splitPhysicalLines(content) {
|
|
3316
|
+
if (content === "") {
|
|
3317
|
+
return [];
|
|
3318
|
+
}
|
|
3319
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
3320
|
+
if (TRAILING_TERMINATOR.test(content)) {
|
|
3321
|
+
lines.pop();
|
|
3322
|
+
}
|
|
3323
|
+
return lines;
|
|
3324
|
+
}
|
|
3325
|
+
function countTrailingBackslashes(line) {
|
|
3326
|
+
let count = 0;
|
|
3327
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) {
|
|
3328
|
+
count += 1;
|
|
3329
|
+
}
|
|
3330
|
+
return count;
|
|
3331
|
+
}
|
|
3332
|
+
function isContinued(line) {
|
|
3333
|
+
return countTrailingBackslashes(line) % 2 === 1;
|
|
3334
|
+
}
|
|
3335
|
+
function isBlankLine(line) {
|
|
3336
|
+
return line.trim() === "";
|
|
3337
|
+
}
|
|
3338
|
+
function isCommentLine(line) {
|
|
3339
|
+
const trimmed = line.replace(LEADING_WHITESPACE, "");
|
|
3340
|
+
return trimmed.startsWith("#") || trimmed.startsWith("!");
|
|
3341
|
+
}
|
|
3342
|
+
function joinContinuation(lines, start) {
|
|
3343
|
+
const pieces = [];
|
|
3344
|
+
let index = start;
|
|
3345
|
+
let current = lines[start] ?? "";
|
|
3346
|
+
while (isContinued(current) && index + 1 < lines.length) {
|
|
3347
|
+
pieces.push(current.slice(0, -1));
|
|
3348
|
+
index += 1;
|
|
3349
|
+
current = (lines[index] ?? "").replace(LEADING_WHITESPACE, "");
|
|
3350
|
+
}
|
|
3351
|
+
if (isContinued(current)) {
|
|
3352
|
+
current = current.slice(0, -1);
|
|
3353
|
+
}
|
|
3354
|
+
pieces.push(current);
|
|
3355
|
+
return { logical: pieces.join(""), nextIndex: index + 1 };
|
|
3356
|
+
}
|
|
3357
|
+
function decodeSimpleEscape(char) {
|
|
3358
|
+
switch (char) {
|
|
3359
|
+
case "t":
|
|
3360
|
+
return " ";
|
|
3361
|
+
case "n":
|
|
3362
|
+
return "\n";
|
|
3363
|
+
case "r":
|
|
3364
|
+
return "\r";
|
|
3365
|
+
case "f":
|
|
3366
|
+
return "\f";
|
|
3367
|
+
default:
|
|
3368
|
+
return char;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
function decodeUnicodeEscape(raw, at) {
|
|
3372
|
+
const hex = raw.slice(at, at + 4);
|
|
3373
|
+
if (!UNICODE_ESCAPE.test(hex)) {
|
|
3374
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file has a malformed unicode escape.");
|
|
3375
|
+
}
|
|
3376
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
3377
|
+
}
|
|
3378
|
+
function decodeEscapes(raw) {
|
|
3379
|
+
let out = "";
|
|
3380
|
+
let i = 0;
|
|
3381
|
+
while (i < raw.length) {
|
|
3382
|
+
const char = raw[i];
|
|
3383
|
+
if (char !== "\\") {
|
|
3384
|
+
out += char;
|
|
3385
|
+
i += 1;
|
|
3386
|
+
continue;
|
|
3387
|
+
}
|
|
3388
|
+
const next = raw[i + 1];
|
|
3389
|
+
if (next === void 0) {
|
|
3390
|
+
break;
|
|
3391
|
+
}
|
|
3392
|
+
if (next === "u") {
|
|
3393
|
+
out += decodeUnicodeEscape(raw, i + 2);
|
|
3394
|
+
i += 6;
|
|
3395
|
+
continue;
|
|
3396
|
+
}
|
|
3397
|
+
out += decodeSimpleEscape(next);
|
|
3398
|
+
i += 2;
|
|
3399
|
+
}
|
|
3400
|
+
return out;
|
|
3401
|
+
}
|
|
3402
|
+
function keyEndIndex(logical, from) {
|
|
3403
|
+
let i = from;
|
|
3404
|
+
while (i < logical.length) {
|
|
3405
|
+
const char = logical[i];
|
|
3406
|
+
if (char === "\\") {
|
|
3407
|
+
i += 2;
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
if (char === void 0 || isPropertiesWhitespace(char) || char === "=" || char === ":") {
|
|
3411
|
+
break;
|
|
3412
|
+
}
|
|
3413
|
+
i += 1;
|
|
3414
|
+
}
|
|
3415
|
+
return i;
|
|
3416
|
+
}
|
|
3417
|
+
function skipWhitespace(logical, from) {
|
|
3418
|
+
let i = from;
|
|
3419
|
+
while (i < logical.length) {
|
|
3420
|
+
const char = logical[i];
|
|
3421
|
+
if (char === void 0 || !isPropertiesWhitespace(char)) {
|
|
3422
|
+
break;
|
|
3423
|
+
}
|
|
3424
|
+
i += 1;
|
|
3425
|
+
}
|
|
3426
|
+
return i;
|
|
3427
|
+
}
|
|
3428
|
+
function parseEntryLine(logical) {
|
|
3429
|
+
const keyStart = skipWhitespace(logical, 0);
|
|
3430
|
+
const keyEnd = keyEndIndex(logical, keyStart);
|
|
3431
|
+
const afterKey = skipWhitespace(logical, keyEnd);
|
|
3432
|
+
const separator = logical[afterKey];
|
|
3433
|
+
const valueStart = separator === "=" || separator === ":" ? skipWhitespace(logical, afterKey + 1) : afterKey;
|
|
3434
|
+
return {
|
|
3435
|
+
key: decodeEscapes(logical.slice(keyStart, keyEnd)),
|
|
3436
|
+
value: decodeEscapes(logical.slice(valueStart))
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
function parseItems(content) {
|
|
3440
|
+
const lines = splitPhysicalLines(content);
|
|
3441
|
+
const items = [];
|
|
3442
|
+
let i = 0;
|
|
3443
|
+
while (i < lines.length) {
|
|
3444
|
+
const line = lines[i] ?? "";
|
|
3445
|
+
if (isBlankLine(line) || isCommentLine(line)) {
|
|
3446
|
+
items.push({ kind: "raw", text: line });
|
|
3447
|
+
i += 1;
|
|
3448
|
+
continue;
|
|
3449
|
+
}
|
|
3450
|
+
const { logical, nextIndex } = joinContinuation(lines, i);
|
|
3451
|
+
items.push({ kind: "entry", ...parseEntryLine(logical) });
|
|
3452
|
+
i = nextIndex;
|
|
3453
|
+
}
|
|
3454
|
+
return items;
|
|
3455
|
+
}
|
|
3456
|
+
function parsePropertiesEntries(content, namespace) {
|
|
3457
|
+
const map = /* @__PURE__ */ new Map();
|
|
3458
|
+
for (const item of parseItems(content)) {
|
|
3459
|
+
if (item.kind === "entry") {
|
|
3460
|
+
map.set(item.key, {
|
|
3461
|
+
key: item.key,
|
|
3462
|
+
namespace,
|
|
3463
|
+
value: item.value,
|
|
3464
|
+
placeholders: extractPropertiesPlaceholders(item.value),
|
|
3465
|
+
isPlural: false
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
return map;
|
|
3470
|
+
}
|
|
3471
|
+
function unicodeEscape(code) {
|
|
3472
|
+
return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3473
|
+
}
|
|
3474
|
+
function escapeChar(char, isFirst, isKey) {
|
|
3475
|
+
switch (char) {
|
|
3476
|
+
case "\\":
|
|
3477
|
+
return "\\\\";
|
|
3478
|
+
case " ":
|
|
3479
|
+
return "\\t";
|
|
3480
|
+
case "\n":
|
|
3481
|
+
return "\\n";
|
|
3482
|
+
case "\r":
|
|
3483
|
+
return "\\r";
|
|
3484
|
+
case "\f":
|
|
3485
|
+
return "\\f";
|
|
3486
|
+
case " ":
|
|
3487
|
+
return isKey || isFirst ? "\\ " : " ";
|
|
3488
|
+
}
|
|
3489
|
+
if (isKey && (char === "=" || char === ":" || char === "#" || char === "!")) {
|
|
3490
|
+
return `\\${char}`;
|
|
3491
|
+
}
|
|
3492
|
+
const code = char.charCodeAt(0);
|
|
3493
|
+
return code < 32 || code > 126 ? unicodeEscape(code) : char;
|
|
3494
|
+
}
|
|
3495
|
+
function escapeString(input, isKey) {
|
|
3496
|
+
let out = "";
|
|
3497
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
3498
|
+
const char = input[i];
|
|
3499
|
+
if (char !== void 0) {
|
|
3500
|
+
out += escapeChar(char, i === 0, isKey);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
return out;
|
|
3504
|
+
}
|
|
3505
|
+
function formatEntry(key, value) {
|
|
3506
|
+
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3507
|
+
}
|
|
3508
|
+
function isFileNotFound(error) {
|
|
3509
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3510
|
+
}
|
|
3511
|
+
async function readStructure(filePath) {
|
|
3512
|
+
let outcome;
|
|
3513
|
+
try {
|
|
3514
|
+
outcome = await readBounded2(filePath);
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
if (isFileNotFound(error)) {
|
|
3517
|
+
return [];
|
|
3518
|
+
}
|
|
3519
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination file could not be read.");
|
|
3520
|
+
}
|
|
3521
|
+
if (outcome.kind === "not-a-file") {
|
|
3522
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
3523
|
+
}
|
|
3524
|
+
if (outcome.kind === "too-large") {
|
|
3525
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
3526
|
+
}
|
|
3527
|
+
return parseItems(outcome.content);
|
|
3528
|
+
}
|
|
3529
|
+
async function serializePropertiesEntries(entries, filePath) {
|
|
3530
|
+
const structure = await readStructure(filePath);
|
|
3531
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3532
|
+
const lines = [];
|
|
3533
|
+
for (const item of structure) {
|
|
3534
|
+
if (item.kind === "raw") {
|
|
3535
|
+
lines.push(item.text);
|
|
3536
|
+
continue;
|
|
3537
|
+
}
|
|
3538
|
+
const entry = entries.get(item.key);
|
|
3539
|
+
if (entry !== void 0 && !emitted.has(item.key)) {
|
|
3540
|
+
lines.push(formatEntry(item.key, entry.value));
|
|
3541
|
+
emitted.add(item.key);
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
for (const [key, entry] of entries) {
|
|
3545
|
+
if (!emitted.has(key)) {
|
|
3546
|
+
lines.push(formatEntry(key, entry.value));
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
return lines.length === 0 ? "" : `${lines.join("\n")}
|
|
3550
|
+
`;
|
|
3551
|
+
}
|
|
3552
|
+
function createPropertiesAdapter() {
|
|
3553
|
+
return createFlatFileAdapter({
|
|
3554
|
+
format: "properties",
|
|
3555
|
+
extensions: [".properties"],
|
|
3556
|
+
parseEntries: parsePropertiesEntries,
|
|
3557
|
+
serializeEntries: serializePropertiesEntries,
|
|
3558
|
+
extractPlaceholders: extractPropertiesPlaceholders
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3083
3561
|
var AdapterRegistry = class {
|
|
3084
3562
|
adapters = [];
|
|
3085
3563
|
/**
|
|
@@ -3157,43 +3635,6 @@ function createVueI18nJsonAdapter() {
|
|
|
3157
3635
|
})
|
|
3158
3636
|
});
|
|
3159
3637
|
}
|
|
3160
|
-
function toEntries2(content, namespace, parseEntries) {
|
|
3161
|
-
try {
|
|
3162
|
-
return parseEntries(content, namespace);
|
|
3163
|
-
} catch (error) {
|
|
3164
|
-
rethrowStructured(error, "The file could not be parsed.");
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
function createFlatFileAdapter(options) {
|
|
3168
|
-
const {
|
|
3169
|
-
format,
|
|
3170
|
-
extensions,
|
|
3171
|
-
sniff,
|
|
3172
|
-
parseEntries,
|
|
3173
|
-
serializeEntries,
|
|
3174
|
-
extractPlaceholders,
|
|
3175
|
-
validateMessage,
|
|
3176
|
-
computeInvalidIcuKeys
|
|
3177
|
-
} = options;
|
|
3178
|
-
return {
|
|
3179
|
-
format,
|
|
3180
|
-
canHandle: buildCanHandle(extensions, sniff),
|
|
3181
|
-
extractPlaceholders,
|
|
3182
|
-
validateMessage: validateMessage ?? (() => true),
|
|
3183
|
-
async read(filePath, locale) {
|
|
3184
|
-
const content = await readFileContent(filePath);
|
|
3185
|
-
const namespace = namespaceOf(filePath);
|
|
3186
|
-
const entries = toEntries2(content, namespace, parseEntries);
|
|
3187
|
-
const resource = { locale, namespace, format, entries };
|
|
3188
|
-
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3189
|
-
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3190
|
-
},
|
|
3191
|
-
async write(resource, filePath) {
|
|
3192
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3193
|
-
await atomicWriteFile(filePath, data);
|
|
3194
|
-
}
|
|
3195
|
-
};
|
|
3196
|
-
}
|
|
3197
3638
|
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
3198
3639
|
function extractXliffPlaceholders(value) {
|
|
3199
3640
|
const result = [];
|
|
@@ -3504,7 +3945,7 @@ function createYamlAdapter() {
|
|
|
3504
3945
|
});
|
|
3505
3946
|
}
|
|
3506
3947
|
function createDefaultRegistry() {
|
|
3507
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
3948
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter()).register(createPropertiesAdapter());
|
|
3508
3949
|
}
|
|
3509
3950
|
|
|
3510
3951
|
// src/selection/select-adapter.ts
|
|
@@ -3625,6 +4066,28 @@ async function diff(input, deps = {}) {
|
|
|
3625
4066
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3626
4067
|
}
|
|
3627
4068
|
|
|
4069
|
+
// src/cache/fingerprint.ts
|
|
4070
|
+
function fingerprintModel(provider) {
|
|
4071
|
+
const options = provider.options;
|
|
4072
|
+
const model = options.model;
|
|
4073
|
+
return typeof model === "string" ? model : null;
|
|
4074
|
+
}
|
|
4075
|
+
function sortGlossary(glossary) {
|
|
4076
|
+
if (glossary === void 0) {
|
|
4077
|
+
return {};
|
|
4078
|
+
}
|
|
4079
|
+
return Object.fromEntries(Object.entries(glossary).sort(([a], [b]) => a < b ? -1 : 1));
|
|
4080
|
+
}
|
|
4081
|
+
function computeFingerprint(config) {
|
|
4082
|
+
const canonical = JSON.stringify({
|
|
4083
|
+
provider: config.provider.id,
|
|
4084
|
+
model: fingerprintModel(config.provider),
|
|
4085
|
+
tone: config.tone ?? null,
|
|
4086
|
+
glossary: sortGlossary(config.glossary)
|
|
4087
|
+
});
|
|
4088
|
+
return stableStringHash(canonical);
|
|
4089
|
+
}
|
|
4090
|
+
|
|
3628
4091
|
// src/flow/integrity-gate.ts
|
|
3629
4092
|
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3630
4093
|
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
@@ -3675,6 +4138,12 @@ async function editEntry(input, deps = {}) {
|
|
|
3675
4138
|
mode: "merge",
|
|
3676
4139
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3677
4140
|
});
|
|
4141
|
+
await feedTranslationMemory(
|
|
4142
|
+
cwd,
|
|
4143
|
+
fs,
|
|
4144
|
+
computeFingerprint(config),
|
|
4145
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: input.value }]])
|
|
4146
|
+
);
|
|
3678
4147
|
return { accepted: true, value: input.value };
|
|
3679
4148
|
});
|
|
3680
4149
|
}
|
|
@@ -3892,13 +4361,19 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
3892
4361
|
mode: "merge",
|
|
3893
4362
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3894
4363
|
});
|
|
4364
|
+
await feedTranslationMemory(
|
|
4365
|
+
cwd,
|
|
4366
|
+
fs,
|
|
4367
|
+
computeFingerprint(config),
|
|
4368
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: value }]])
|
|
4369
|
+
);
|
|
3895
4370
|
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3896
4371
|
return { accepted: true, value, reviewReasons };
|
|
3897
4372
|
});
|
|
3898
4373
|
}
|
|
3899
4374
|
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3900
4375
|
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3901
|
-
var
|
|
4376
|
+
var CURRENT_VERSION3 = 1;
|
|
3902
4377
|
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3903
4378
|
var reviewReasonCodeSchema = z.enum([
|
|
3904
4379
|
"LENGTH_RATIO_OUTLIER",
|
|
@@ -3948,7 +4423,7 @@ function toRunStatusLocale(locale) {
|
|
|
3948
4423
|
}
|
|
3949
4424
|
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3950
4425
|
return {
|
|
3951
|
-
version:
|
|
4426
|
+
version: CURRENT_VERSION3,
|
|
3952
4427
|
generatedAt,
|
|
3953
4428
|
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3954
4429
|
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
@@ -3981,7 +4456,7 @@ async function readRunStatusFile(path, fs) {
|
|
|
3981
4456
|
return void 0;
|
|
3982
4457
|
}
|
|
3983
4458
|
const result = runStatusFileSchema.safeParse(parsed);
|
|
3984
|
-
if (!result.success || result.data.version !==
|
|
4459
|
+
if (!result.success || result.data.version !== CURRENT_VERSION3) {
|
|
3985
4460
|
return void 0;
|
|
3986
4461
|
}
|
|
3987
4462
|
return fromParsed(result.data);
|
|
@@ -4060,12 +4535,16 @@ function failureSummary(locale, error) {
|
|
|
4060
4535
|
orphaned: [],
|
|
4061
4536
|
pruned: [],
|
|
4062
4537
|
invalidIcuSource: [],
|
|
4538
|
+
cacheHits: [],
|
|
4063
4539
|
integrityMismatches: [],
|
|
4064
4540
|
providerFailures: [],
|
|
4065
4541
|
budgetWithheld: [],
|
|
4066
4542
|
generated: [],
|
|
4067
4543
|
notices: [],
|
|
4068
4544
|
needsReview: [],
|
|
4545
|
+
unfilled: [],
|
|
4546
|
+
malformedRows: [],
|
|
4547
|
+
duplicateKeys: [],
|
|
4069
4548
|
error: describeError(error)
|
|
4070
4549
|
};
|
|
4071
4550
|
}
|
|
@@ -4074,7 +4553,7 @@ function deriveLocaleStatus(parts) {
|
|
|
4074
4553
|
if (!withheld) {
|
|
4075
4554
|
return "succeeded";
|
|
4076
4555
|
}
|
|
4077
|
-
const accepted = parts.translated.length > 0 || parts.generated.length > 0;
|
|
4556
|
+
const accepted = parts.translated.length > 0 || parts.cacheHits.length > 0 || parts.generated.length > 0;
|
|
4078
4557
|
return accepted ? "partial" : "failed";
|
|
4079
4558
|
}
|
|
4080
4559
|
function partition(locales) {
|
|
@@ -4394,6 +4873,97 @@ function foldGenerationItem(item, result, adapter, accepted, withheld, providerF
|
|
|
4394
4873
|
}
|
|
4395
4874
|
|
|
4396
4875
|
// src/flow/locale-run.ts
|
|
4876
|
+
function partitionCacheHits(params, toTranslate) {
|
|
4877
|
+
const cache = params.cache;
|
|
4878
|
+
const hits = /* @__PURE__ */ new Map();
|
|
4879
|
+
if (cache === void 0) {
|
|
4880
|
+
return { hits, misses: toTranslate };
|
|
4881
|
+
}
|
|
4882
|
+
const misses = [];
|
|
4883
|
+
for (const key of toTranslate) {
|
|
4884
|
+
const source = params.source.entries.get(key);
|
|
4885
|
+
if (source === void 0) {
|
|
4886
|
+
continue;
|
|
4887
|
+
}
|
|
4888
|
+
const cached = lookupMemory(
|
|
4889
|
+
cache.snapshot,
|
|
4890
|
+
cache.fingerprint,
|
|
4891
|
+
params.targetLocale,
|
|
4892
|
+
contentHash(source)
|
|
4893
|
+
);
|
|
4894
|
+
if (cached !== void 0 && gateCandidateValue(source, cached, params.adapter).accepted) {
|
|
4895
|
+
hits.set(key, { value: cached, source });
|
|
4896
|
+
} else {
|
|
4897
|
+
misses.push(key);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
return { hits, misses };
|
|
4901
|
+
}
|
|
4902
|
+
function collectCacheAdditions(params, accepted, cacheHitKeys) {
|
|
4903
|
+
if (params.cache === void 0) {
|
|
4904
|
+
return [];
|
|
4905
|
+
}
|
|
4906
|
+
const additions = [];
|
|
4907
|
+
for (const [key, entry] of accepted) {
|
|
4908
|
+
if (!cacheHitKeys.has(key)) {
|
|
4909
|
+
additions.push({ contentHash: contentHash(entry.source), value: entry.value });
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
return additions;
|
|
4913
|
+
}
|
|
4914
|
+
function groupMissesByContent(params, misses) {
|
|
4915
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4916
|
+
for (const key of misses) {
|
|
4917
|
+
const source = params.source.entries.get(key);
|
|
4918
|
+
if (source === void 0) {
|
|
4919
|
+
continue;
|
|
4920
|
+
}
|
|
4921
|
+
const existing = byHash.get(contentHash(source));
|
|
4922
|
+
if (existing === void 0) {
|
|
4923
|
+
byHash.set(contentHash(source), { representative: key, duplicates: [] });
|
|
4924
|
+
} else {
|
|
4925
|
+
existing.duplicates.push(key);
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
return [...byHash.values()];
|
|
4929
|
+
}
|
|
4930
|
+
function fanOutContentDuplicates(params, groups, outcome) {
|
|
4931
|
+
for (const group of groups) {
|
|
4932
|
+
if (group.duplicates.length > 0) {
|
|
4933
|
+
applyGroupOutcome(params, group, outcome);
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
function applyGroupOutcome(params, group, outcome) {
|
|
4938
|
+
const acceptedRepresentative = outcome.accepted.get(group.representative);
|
|
4939
|
+
if (acceptedRepresentative !== void 0) {
|
|
4940
|
+
fanOutAccepted(params, group, acceptedRepresentative, outcome);
|
|
4941
|
+
return;
|
|
4942
|
+
}
|
|
4943
|
+
withheldBucketFor(group.representative, outcome).push(...group.duplicates);
|
|
4944
|
+
}
|
|
4945
|
+
function fanOutAccepted(params, group, acceptedRepresentative, outcome) {
|
|
4946
|
+
const flag = outcome.reviewFlags.get(group.representative);
|
|
4947
|
+
for (const key of group.duplicates) {
|
|
4948
|
+
const source = params.source.entries.get(key);
|
|
4949
|
+
if (source === void 0) {
|
|
4950
|
+
continue;
|
|
4951
|
+
}
|
|
4952
|
+
outcome.accepted.set(key, { value: acceptedRepresentative.value, source });
|
|
4953
|
+
if (flag !== void 0) {
|
|
4954
|
+
outcome.reviewFlags.set(key, flag);
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
function withheldBucketFor(representative, outcome) {
|
|
4959
|
+
if (outcome.integrityMismatches.includes(representative)) {
|
|
4960
|
+
return outcome.integrityMismatches;
|
|
4961
|
+
}
|
|
4962
|
+
if (outcome.budgetWithheld.includes(representative)) {
|
|
4963
|
+
return outcome.budgetWithheld;
|
|
4964
|
+
}
|
|
4965
|
+
return outcome.providerFailures;
|
|
4966
|
+
}
|
|
4397
4967
|
function buildRequest3(params, entries) {
|
|
4398
4968
|
return {
|
|
4399
4969
|
sourceLocale: params.sourceLocale,
|
|
@@ -4436,6 +5006,7 @@ async function runLocale(params) {
|
|
|
4436
5006
|
orphaned,
|
|
4437
5007
|
invalidIcuSource,
|
|
4438
5008
|
translated: toTranslate,
|
|
5009
|
+
cacheHits: [],
|
|
4439
5010
|
generated: [],
|
|
4440
5011
|
integrityMismatches: [],
|
|
4441
5012
|
providerFailures: [],
|
|
@@ -4443,12 +5014,16 @@ async function runLocale(params) {
|
|
|
4443
5014
|
pruned,
|
|
4444
5015
|
notices: sdkNotices
|
|
4445
5016
|
}),
|
|
4446
|
-
lockEntries: {}
|
|
5017
|
+
lockEntries: {},
|
|
5018
|
+
cacheAdditions: []
|
|
4447
5019
|
};
|
|
4448
5020
|
}
|
|
4449
|
-
const
|
|
5021
|
+
const partition2 = partitionCacheHits(params, toTranslate);
|
|
5022
|
+
const cacheHitKeys = new Set(partition2.hits.keys());
|
|
5023
|
+
const missGroups = groupMissesByContent(params, partition2.misses);
|
|
5024
|
+
const entries = missGroups.map((group) => params.source.entries.get(group.representative)).filter((entry) => entry !== void 0);
|
|
4450
5025
|
const startedStopped = params.budget.stopped;
|
|
4451
|
-
const accepted =
|
|
5026
|
+
const accepted = new Map(partition2.hits);
|
|
4452
5027
|
const integrityMismatches = [];
|
|
4453
5028
|
const providerFailures = [];
|
|
4454
5029
|
const budgetWithheld = [];
|
|
@@ -4463,6 +5038,13 @@ async function runLocale(params) {
|
|
|
4463
5038
|
budgetWithheld,
|
|
4464
5039
|
reviewFlags
|
|
4465
5040
|
);
|
|
5041
|
+
fanOutContentDuplicates(params, missGroups, {
|
|
5042
|
+
accepted,
|
|
5043
|
+
integrityMismatches,
|
|
5044
|
+
providerFailures,
|
|
5045
|
+
budgetWithheld,
|
|
5046
|
+
reviewFlags
|
|
5047
|
+
});
|
|
4466
5048
|
const merged = new Map(target.entries);
|
|
4467
5049
|
for (const key of pruned) {
|
|
4468
5050
|
merged.delete(key);
|
|
@@ -4509,7 +5091,8 @@ async function runLocale(params) {
|
|
|
4509
5091
|
unchanged: diff2.unchanged,
|
|
4510
5092
|
orphaned,
|
|
4511
5093
|
invalidIcuSource,
|
|
4512
|
-
translated: [...accepted.keys()],
|
|
5094
|
+
translated: [...accepted.keys()].filter((key) => !cacheHitKeys.has(key)),
|
|
5095
|
+
cacheHits: [...cacheHitKeys].sort(),
|
|
4513
5096
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
4514
5097
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
4515
5098
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
@@ -4519,7 +5102,8 @@ async function runLocale(params) {
|
|
|
4519
5102
|
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
4520
5103
|
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
4521
5104
|
}),
|
|
4522
|
-
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
5105
|
+
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted),
|
|
5106
|
+
cacheAdditions: collectCacheAdditions(params, accepted, cacheHitKeys)
|
|
4523
5107
|
};
|
|
4524
5108
|
}
|
|
4525
5109
|
var NO_GENERATION_RESULT = {
|
|
@@ -4570,12 +5154,16 @@ function baseSummary(parts) {
|
|
|
4570
5154
|
orphaned: parts.orphaned,
|
|
4571
5155
|
pruned: parts.pruned,
|
|
4572
5156
|
invalidIcuSource: parts.invalidIcuSource,
|
|
5157
|
+
cacheHits: parts.cacheHits,
|
|
4573
5158
|
integrityMismatches: parts.integrityMismatches,
|
|
4574
5159
|
providerFailures: parts.providerFailures,
|
|
4575
5160
|
budgetWithheld: parts.budgetWithheld,
|
|
4576
5161
|
generated: parts.generated,
|
|
4577
5162
|
notices: parts.notices,
|
|
4578
5163
|
needsReview: parts.needsReview ?? [],
|
|
5164
|
+
unfilled: [],
|
|
5165
|
+
malformedRows: [],
|
|
5166
|
+
duplicateKeys: [],
|
|
4579
5167
|
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
4580
5168
|
};
|
|
4581
5169
|
}
|
|
@@ -4593,7 +5181,16 @@ async function translateAndCheck(provider, params, entries, accepted, integrityM
|
|
|
4593
5181
|
const notices = [];
|
|
4594
5182
|
const usage = createUsageAccumulator();
|
|
4595
5183
|
let tripped = false;
|
|
4596
|
-
|
|
5184
|
+
const batches = chunk(entries, params.maxBatchSize);
|
|
5185
|
+
let batchIndex = 0;
|
|
5186
|
+
for (const batch of batches) {
|
|
5187
|
+
batchIndex += 1;
|
|
5188
|
+
params.onProgress?.({
|
|
5189
|
+
type: "sub-batch",
|
|
5190
|
+
locale: params.targetLocale,
|
|
5191
|
+
batchIndex,
|
|
5192
|
+
totalBatches: batches.length
|
|
5193
|
+
});
|
|
4597
5194
|
if (params.budget.stopped) {
|
|
4598
5195
|
for (const entry of batch) {
|
|
4599
5196
|
budgetWithheld.push(entry.key);
|
|
@@ -4739,6 +5336,26 @@ async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
|
4739
5336
|
} catch {
|
|
4740
5337
|
}
|
|
4741
5338
|
}
|
|
5339
|
+
async function createRunCacheState(input, config, cwd, dryRun, fs) {
|
|
5340
|
+
if (dryRun || input.cache === false) {
|
|
5341
|
+
return void 0;
|
|
5342
|
+
}
|
|
5343
|
+
return {
|
|
5344
|
+
memory: await readTranslationMemory(cacheFilePath(cwd), fs),
|
|
5345
|
+
fingerprint: computeFingerprint(config),
|
|
5346
|
+
additions: /* @__PURE__ */ new Map()
|
|
5347
|
+
};
|
|
5348
|
+
}
|
|
5349
|
+
async function recordCacheAdditions(cwd, cache, fs) {
|
|
5350
|
+
if (cache === void 0 || cache.additions.size === 0) {
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
try {
|
|
5354
|
+
const merged = applyAdditions(cache.memory, cache.fingerprint, cache.additions);
|
|
5355
|
+
await writeTranslationMemory(cacheFilePath(cwd), merged, fs);
|
|
5356
|
+
} catch {
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
4742
5359
|
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4743
5360
|
return {
|
|
4744
5361
|
source: context.source.resource,
|
|
@@ -4757,7 +5374,9 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
4757
5374
|
generatePlurals: context.generatePlurals,
|
|
4758
5375
|
maxBatchSize: context.maxBatchSize,
|
|
4759
5376
|
fs: context.fs,
|
|
4760
|
-
budget: context.budget
|
|
5377
|
+
budget: context.budget,
|
|
5378
|
+
...context.cache !== void 0 ? { cache: { snapshot: context.cache.memory, fingerprint: context.cache.fingerprint } } : {},
|
|
5379
|
+
...context.onProgress !== void 0 ? { onProgress: context.onProgress } : {}
|
|
4761
5380
|
};
|
|
4762
5381
|
}
|
|
4763
5382
|
async function runDryLocale(context, targetLocale, lock) {
|
|
@@ -4781,6 +5400,9 @@ async function runLiveLocale(context, targetLocale) {
|
|
|
4781
5400
|
mode: "replace",
|
|
4782
5401
|
entries: result.lockEntries
|
|
4783
5402
|
});
|
|
5403
|
+
if (context.cache !== void 0 && result.cacheAdditions.length > 0) {
|
|
5404
|
+
context.cache.additions.set(targetLocale, additionsToRecord(result.cacheAdditions));
|
|
5405
|
+
}
|
|
4784
5406
|
return result.summary;
|
|
4785
5407
|
},
|
|
4786
5408
|
lockOptions
|
|
@@ -4796,27 +5418,88 @@ async function runOneLocale(targetLocale, run) {
|
|
|
4796
5418
|
return failureSummary(targetLocale, error);
|
|
4797
5419
|
}
|
|
4798
5420
|
}
|
|
4799
|
-
async function
|
|
5421
|
+
async function runLocaleAt(context, targetLocales, localeIndex, runOne, results) {
|
|
5422
|
+
const targetLocale = targetLocales[localeIndex];
|
|
5423
|
+
if (targetLocale === void 0) {
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
5426
|
+
context.onProgress?.({
|
|
5427
|
+
type: "locale-started",
|
|
5428
|
+
locale: targetLocale,
|
|
5429
|
+
localeIndex,
|
|
5430
|
+
totalLocales: targetLocales.length
|
|
5431
|
+
});
|
|
5432
|
+
const summary = await runOneLocale(targetLocale, () => runOne(targetLocale));
|
|
5433
|
+
results[localeIndex] = summary;
|
|
5434
|
+
context.onProgress?.({
|
|
5435
|
+
type: "locale-finished",
|
|
5436
|
+
locale: targetLocale,
|
|
5437
|
+
translated: summary.translated.length
|
|
5438
|
+
});
|
|
5439
|
+
}
|
|
5440
|
+
async function runLocalesWithProgress(context, targetLocales, runOne, concurrency) {
|
|
5441
|
+
const totalLocales = targetLocales.length;
|
|
5442
|
+
const results = new Array(totalLocales);
|
|
5443
|
+
let nextIndex = 0;
|
|
5444
|
+
async function worker() {
|
|
5445
|
+
while (nextIndex < totalLocales) {
|
|
5446
|
+
const localeIndex = nextIndex;
|
|
5447
|
+
nextIndex += 1;
|
|
5448
|
+
await runLocaleAt(context, targetLocales, localeIndex, runOne, results);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
const workerCount = Math.min(concurrency, totalLocales);
|
|
5452
|
+
const workers = [];
|
|
5453
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
5454
|
+
workers.push(worker());
|
|
5455
|
+
}
|
|
5456
|
+
await Promise.all(workers);
|
|
5457
|
+
return results.filter((summary) => summary !== void 0);
|
|
5458
|
+
}
|
|
5459
|
+
async function runAllLocalesDry(context, targetLocales, concurrency) {
|
|
4800
5460
|
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
5461
|
+
return runLocalesWithProgress(
|
|
5462
|
+
context,
|
|
5463
|
+
targetLocales,
|
|
5464
|
+
(targetLocale) => runDryLocale(context, targetLocale, lock),
|
|
5465
|
+
concurrency
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
async function runAllLocalesLive(context, targetLocales, concurrency) {
|
|
5469
|
+
return runLocalesWithProgress(
|
|
5470
|
+
context,
|
|
5471
|
+
targetLocales,
|
|
5472
|
+
(targetLocale) => runLiveLocale(context, targetLocale),
|
|
5473
|
+
concurrency
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
function resolveConcurrency(value) {
|
|
5477
|
+
if (value === void 0) {
|
|
5478
|
+
return 1;
|
|
5479
|
+
}
|
|
5480
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5481
|
+
throw new SdkError(
|
|
5482
|
+
"CONCURRENCY_INVALID",
|
|
5483
|
+
`The concurrency option must be an integer of at least 1, got ${value}.`
|
|
4805
5484
|
);
|
|
4806
5485
|
}
|
|
4807
|
-
return
|
|
5486
|
+
return value;
|
|
4808
5487
|
}
|
|
4809
|
-
|
|
4810
|
-
const
|
|
4811
|
-
|
|
4812
|
-
|
|
5488
|
+
function resolveRunConcurrency(value, dryRun, config) {
|
|
5489
|
+
const concurrency = resolveConcurrency(value);
|
|
5490
|
+
if (!dryRun && concurrency > 1 && config.maxTokens !== void 0) {
|
|
5491
|
+
throw new SdkError(
|
|
5492
|
+
"CONCURRENCY_BUDGET_CONFLICT",
|
|
5493
|
+
"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."
|
|
5494
|
+
);
|
|
4813
5495
|
}
|
|
4814
|
-
return
|
|
5496
|
+
return concurrency;
|
|
4815
5497
|
}
|
|
4816
5498
|
async function translate2(input, deps = {}) {
|
|
4817
5499
|
const config = input.config;
|
|
4818
5500
|
const cwd = input.cwd ?? process.cwd();
|
|
4819
5501
|
const dryRun = input.dryRun ?? false;
|
|
5502
|
+
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
4820
5503
|
const prune = input.prune ?? config.prune ?? false;
|
|
4821
5504
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
4822
5505
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
@@ -4828,6 +5511,7 @@ async function translate2(input, deps = {}) {
|
|
|
4828
5511
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4829
5512
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
4830
5513
|
const source = await readSource(config, cwd, fs, adapter);
|
|
5514
|
+
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
4831
5515
|
const context = {
|
|
4832
5516
|
source,
|
|
4833
5517
|
adapter,
|
|
@@ -4839,10 +5523,13 @@ async function translate2(input, deps = {}) {
|
|
|
4839
5523
|
maxBatchSize,
|
|
4840
5524
|
fs,
|
|
4841
5525
|
budget,
|
|
5526
|
+
cache,
|
|
4842
5527
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5528
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
4843
5529
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
4844
5530
|
};
|
|
4845
|
-
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
5531
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales, concurrency) : await runAllLocalesLive(context, config.targetLocales, concurrency);
|
|
5532
|
+
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
4846
5533
|
const { succeeded, partial, failed } = partition(summaries);
|
|
4847
5534
|
const usage = summaries.reduce(
|
|
4848
5535
|
(total, summary2) => combineUsage(total, summary2.usage),
|
|
@@ -4858,6 +5545,7 @@ async function translate2(input, deps = {}) {
|
|
|
4858
5545
|
...usage !== void 0 ? { usage } : {},
|
|
4859
5546
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4860
5547
|
};
|
|
5548
|
+
await recordCacheAdditions(cwd, cache, fs);
|
|
4861
5549
|
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4862
5550
|
return summary;
|
|
4863
5551
|
}
|
|
@@ -4873,8 +5561,12 @@ var INSTRUCTIONS_LINES = [
|
|
|
4873
5561
|
"How to use this workbook",
|
|
4874
5562
|
"",
|
|
4875
5563
|
"1. There is one sheet per language. Open the sheet named for the language you are translating.",
|
|
5564
|
+
" Do not rename, delete, or reorder the language tabs. verbatra matches each tab to a language by",
|
|
5565
|
+
" its exact name; a renamed or missing tab is reported and its translations are not imported.",
|
|
4876
5566
|
"2. Fill ONLY the 'Translation' column. Leave every other column unchanged.",
|
|
4877
|
-
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty
|
|
5567
|
+
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty",
|
|
5568
|
+
" string. A cell that contains only spaces is treated the same as an empty cell.",
|
|
5569
|
+
" To deliberately clear an existing value (set it to empty), type exactly [[CLEAR]] in the cell.",
|
|
4878
5570
|
"4. Keep placeholders exactly as they appear. A token such as {name} or {count} must stay verbatim,",
|
|
4879
5571
|
" in your translation, with the same spelling. Do not translate or remove it.",
|
|
4880
5572
|
"5. For ICU messages (for example {count, plural, one {# item} other {# items}}), keep the ICU",
|
|
@@ -5039,6 +5731,26 @@ function buildInstructionsSheet(workbook) {
|
|
|
5039
5731
|
}
|
|
5040
5732
|
sheet.getRow(1).font = { bold: true };
|
|
5041
5733
|
}
|
|
5734
|
+
var WORKBOOK_PROTECTION_XML = '<workbookProtection lockStructure="1" lockWindows="0"/>';
|
|
5735
|
+
function spliceWorkbookProtection(xml) {
|
|
5736
|
+
const anchor = xml.includes("<bookViews") ? "<bookViews" : "<sheets";
|
|
5737
|
+
return xml.replace(anchor, `${WORKBOOK_PROTECTION_XML}${anchor}`);
|
|
5738
|
+
}
|
|
5739
|
+
async function protectWorkbookStructure(bytes) {
|
|
5740
|
+
let zip;
|
|
5741
|
+
try {
|
|
5742
|
+
zip = await JSZip.loadAsync(bytes);
|
|
5743
|
+
} catch {
|
|
5744
|
+
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5745
|
+
}
|
|
5746
|
+
const workbookPart = zip.file("xl/workbook.xml");
|
|
5747
|
+
if (workbookPart === null) {
|
|
5748
|
+
return bytes;
|
|
5749
|
+
}
|
|
5750
|
+
const xml = await workbookPart.async("string");
|
|
5751
|
+
zip.file("xl/workbook.xml", spliceWorkbookProtection(xml));
|
|
5752
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
5753
|
+
}
|
|
5042
5754
|
async function buildWorkbook(model) {
|
|
5043
5755
|
assertNoWorksheetNameCollisions(model.sheets);
|
|
5044
5756
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -5046,13 +5758,15 @@ async function buildWorkbook(model) {
|
|
|
5046
5758
|
for (const sheet of model.sheets) {
|
|
5047
5759
|
await buildDataSheet(workbook, sheet);
|
|
5048
5760
|
}
|
|
5761
|
+
let serialized;
|
|
5049
5762
|
try {
|
|
5050
5763
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
5051
|
-
|
|
5052
|
-
return Uint8Array.prototype.slice.call(view);
|
|
5764
|
+
serialized = buffer;
|
|
5053
5765
|
} catch {
|
|
5054
5766
|
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5055
5767
|
}
|
|
5768
|
+
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5769
|
+
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
5056
5770
|
}
|
|
5057
5771
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
5058
5772
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
@@ -5172,26 +5886,24 @@ function assertHeader(sheet) {
|
|
|
5172
5886
|
);
|
|
5173
5887
|
}
|
|
5174
5888
|
}
|
|
5175
|
-
|
|
5889
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5890
|
+
function parseRow(row) {
|
|
5176
5891
|
const candidate = {
|
|
5177
5892
|
key: cellString(row.getCell(COLUMN.key)),
|
|
5178
5893
|
source: cellString(row.getCell(COLUMN.source)),
|
|
5179
5894
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
5180
5895
|
status: cellString(row.getCell(COLUMN.status)),
|
|
5181
5896
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
5182
|
-
translation: cellString(row.getCell(COLUMN.translation)),
|
|
5897
|
+
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
5183
5898
|
context: cellString(row.getCell(COLUMN.context)),
|
|
5184
5899
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
5185
5900
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
5186
5901
|
};
|
|
5187
5902
|
const result = rowSchema.safeParse(candidate);
|
|
5188
5903
|
if (!result.success) {
|
|
5189
|
-
|
|
5190
|
-
"WORKBOOK_INVALID",
|
|
5191
|
-
`The sheet "${sheet.name}" has a row with a missing key or an unrecognized status.`
|
|
5192
|
-
);
|
|
5904
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
5193
5905
|
}
|
|
5194
|
-
return result.data;
|
|
5906
|
+
return { ok: true, row: result.data };
|
|
5195
5907
|
}
|
|
5196
5908
|
function readDataSheet(sheet, limits) {
|
|
5197
5909
|
assertHeader(sheet);
|
|
@@ -5202,6 +5914,9 @@ function readDataSheet(sheet, limits) {
|
|
|
5202
5914
|
);
|
|
5203
5915
|
}
|
|
5204
5916
|
const rows = [];
|
|
5917
|
+
const malformed = [];
|
|
5918
|
+
const duplicates = [];
|
|
5919
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
5205
5920
|
for (let rowNumber = HEADER_ROW + 1; rowNumber <= sheet.rowCount; rowNumber += 1) {
|
|
5206
5921
|
const row = sheet.getRow(rowNumber);
|
|
5207
5922
|
if (row.cellCount > limits.maxCellsPerRow) {
|
|
@@ -5213,9 +5928,19 @@ function readDataSheet(sheet, limits) {
|
|
|
5213
5928
|
if (cellString(row.getCell(COLUMN.key)) === "") {
|
|
5214
5929
|
continue;
|
|
5215
5930
|
}
|
|
5216
|
-
|
|
5931
|
+
const outcome = parseRow(row);
|
|
5932
|
+
if (!outcome.ok) {
|
|
5933
|
+
malformed.push({ locale: sheet.name, row: rowNumber, column: outcome.column });
|
|
5934
|
+
continue;
|
|
5935
|
+
}
|
|
5936
|
+
if (seenKeys.has(outcome.row.key)) {
|
|
5937
|
+
duplicates.push({ locale: sheet.name, key: outcome.row.key, row: rowNumber });
|
|
5938
|
+
continue;
|
|
5939
|
+
}
|
|
5940
|
+
seenKeys.add(outcome.row.key);
|
|
5941
|
+
rows.push(outcome.row);
|
|
5217
5942
|
}
|
|
5218
|
-
return { locale: sheet.name, rows };
|
|
5943
|
+
return { sheet: { locale: sheet.name, rows }, malformed, duplicates };
|
|
5219
5944
|
}
|
|
5220
5945
|
async function loadWorkbook(bytes) {
|
|
5221
5946
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -5238,13 +5963,18 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
5238
5963
|
);
|
|
5239
5964
|
}
|
|
5240
5965
|
const sheets = [];
|
|
5966
|
+
const malformedRows = [];
|
|
5967
|
+
const duplicateKeys = [];
|
|
5241
5968
|
for (const sheet of workbook.worksheets) {
|
|
5242
5969
|
if (sheet.name === INSTRUCTIONS_SHEET_NAME) {
|
|
5243
5970
|
continue;
|
|
5244
5971
|
}
|
|
5245
|
-
|
|
5972
|
+
const read = readDataSheet(sheet, limits);
|
|
5973
|
+
sheets.push(read.sheet);
|
|
5974
|
+
malformedRows.push(...read.malformed);
|
|
5975
|
+
duplicateKeys.push(...read.duplicates);
|
|
5246
5976
|
}
|
|
5247
|
-
return { sheets };
|
|
5977
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
5248
5978
|
}
|
|
5249
5979
|
|
|
5250
5980
|
// src/flow/workbook/export-workbook.ts
|
|
@@ -5345,6 +6075,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
5345
6075
|
}
|
|
5346
6076
|
|
|
5347
6077
|
// src/flow/workbook/import-locale.ts
|
|
6078
|
+
var CLEAR_SENTINEL = "[[CLEAR]]";
|
|
5348
6079
|
var UnknownKeyError = class extends Error {
|
|
5349
6080
|
key;
|
|
5350
6081
|
constructor(key) {
|
|
@@ -5373,9 +6104,20 @@ function trackBlankDrift(row, params, buckets) {
|
|
|
5373
6104
|
buckets.blankDrifted.add(row.key);
|
|
5374
6105
|
}
|
|
5375
6106
|
}
|
|
6107
|
+
function classifyClear(row, sourceEntry, buckets) {
|
|
6108
|
+
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
6109
|
+
buckets.mismatches.push(row.key);
|
|
6110
|
+
buckets.withheld.add(row.key);
|
|
6111
|
+
return;
|
|
6112
|
+
}
|
|
6113
|
+
buckets.accepted.set(row.key, { value: "", source: sourceEntry });
|
|
6114
|
+
}
|
|
5376
6115
|
function classifyRows(params, buckets) {
|
|
5377
6116
|
for (const row of params.sheet.rows) {
|
|
5378
6117
|
if (row.translation === "") {
|
|
6118
|
+
if (row.status === "changed") {
|
|
6119
|
+
buckets.unfilled.push(row.key);
|
|
6120
|
+
}
|
|
5379
6121
|
trackBlankDrift(row, params, buckets);
|
|
5380
6122
|
continue;
|
|
5381
6123
|
}
|
|
@@ -5386,6 +6128,10 @@ function classifyRows(params, buckets) {
|
|
|
5386
6128
|
if (sourceEntry === void 0) {
|
|
5387
6129
|
continue;
|
|
5388
6130
|
}
|
|
6131
|
+
if (row.translation === CLEAR_SENTINEL) {
|
|
6132
|
+
classifyClear(row, sourceEntry, buckets);
|
|
6133
|
+
continue;
|
|
6134
|
+
}
|
|
5389
6135
|
const reason = judge(row, sourceEntry, params.adapter);
|
|
5390
6136
|
if (reason === void 0) {
|
|
5391
6137
|
buckets.accepted.set(row.key, { value: row.translation, source: sourceEntry });
|
|
@@ -5407,7 +6153,8 @@ function importLocale(params) {
|
|
|
5407
6153
|
accepted: /* @__PURE__ */ new Map(),
|
|
5408
6154
|
mismatches: [],
|
|
5409
6155
|
withheld: /* @__PURE__ */ new Set(),
|
|
5410
|
-
blankDrifted: /* @__PURE__ */ new Set()
|
|
6156
|
+
blankDrifted: /* @__PURE__ */ new Set(),
|
|
6157
|
+
unfilled: []
|
|
5411
6158
|
};
|
|
5412
6159
|
classifyRows(params, buckets);
|
|
5413
6160
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
@@ -5418,6 +6165,7 @@ function importLocale(params) {
|
|
|
5418
6165
|
locale: params.sheet.locale,
|
|
5419
6166
|
status: deriveLocaleStatus({
|
|
5420
6167
|
translated,
|
|
6168
|
+
cacheHits: [],
|
|
5421
6169
|
generated: [],
|
|
5422
6170
|
integrityMismatches,
|
|
5423
6171
|
providerFailures: [],
|
|
@@ -5428,12 +6176,16 @@ function importLocale(params) {
|
|
|
5428
6176
|
orphaned: diff2.orphaned,
|
|
5429
6177
|
pruned: [],
|
|
5430
6178
|
invalidIcuSource,
|
|
6179
|
+
cacheHits: [],
|
|
5431
6180
|
integrityMismatches,
|
|
5432
6181
|
providerFailures: [],
|
|
5433
6182
|
budgetWithheld: [],
|
|
5434
6183
|
generated: [],
|
|
5435
6184
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
5436
|
-
needsReview: []
|
|
6185
|
+
needsReview: [],
|
|
6186
|
+
unfilled: [...new Set(buckets.unfilled)].sort(),
|
|
6187
|
+
malformedRows: params.malformedRows,
|
|
6188
|
+
duplicateKeys: params.duplicateKeys
|
|
5437
6189
|
};
|
|
5438
6190
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
5439
6191
|
}
|
|
@@ -5460,6 +6212,19 @@ function mergeAccepted(target, accepted) {
|
|
|
5460
6212
|
}
|
|
5461
6213
|
return merged;
|
|
5462
6214
|
}
|
|
6215
|
+
function sheetCacheAdditions(accepted) {
|
|
6216
|
+
const record = {};
|
|
6217
|
+
for (const [, { value, source }] of accepted) {
|
|
6218
|
+
record[contentHash(source)] = value;
|
|
6219
|
+
}
|
|
6220
|
+
return record;
|
|
6221
|
+
}
|
|
6222
|
+
function collectSheetAdditions(byLocale, locale, additions) {
|
|
6223
|
+
if (Object.keys(additions).length === 0) {
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
byLocale.set(locale, { ...byLocale.get(locale), ...additions });
|
|
6227
|
+
}
|
|
5463
6228
|
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
5464
6229
|
const entries = {};
|
|
5465
6230
|
for (const key of merged.keys()) {
|
|
@@ -5476,11 +6241,20 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
5476
6241
|
}
|
|
5477
6242
|
return entries;
|
|
5478
6243
|
}
|
|
6244
|
+
var MissingSheetError = class extends Error {
|
|
6245
|
+
code = "WORKBOOK_SHEET_MISSING";
|
|
6246
|
+
constructor(locale) {
|
|
6247
|
+
super(
|
|
6248
|
+
`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.`
|
|
6249
|
+
);
|
|
6250
|
+
this.name = "MissingSheetError";
|
|
6251
|
+
}
|
|
6252
|
+
};
|
|
5479
6253
|
async function runSheet(ctx, sheet, lock) {
|
|
5480
6254
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
5481
6255
|
throw new SdkError(
|
|
5482
6256
|
"CONFIG_INVALID",
|
|
5483
|
-
`The workbook has a sheet
|
|
6257
|
+
`The workbook has a sheet named "${sheet.locale}", which is not a configured target locale. It may be a renamed, added, or reordered tab; leave every language tab named exactly as exported.`
|
|
5484
6258
|
);
|
|
5485
6259
|
}
|
|
5486
6260
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -5491,10 +6265,12 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5491
6265
|
target,
|
|
5492
6266
|
baseline,
|
|
5493
6267
|
adapter: ctx.adapter,
|
|
5494
|
-
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys
|
|
6268
|
+
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6269
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6270
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
5495
6271
|
});
|
|
5496
6272
|
if (ctx.dryRun) {
|
|
5497
|
-
return { summary, lockEntries: {} };
|
|
6273
|
+
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
5498
6274
|
}
|
|
5499
6275
|
const merged = mergeAccepted(target, accepted);
|
|
5500
6276
|
if (accepted.size > 0) {
|
|
@@ -5509,7 +6285,11 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5509
6285
|
path
|
|
5510
6286
|
);
|
|
5511
6287
|
}
|
|
5512
|
-
return {
|
|
6288
|
+
return {
|
|
6289
|
+
summary,
|
|
6290
|
+
lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted),
|
|
6291
|
+
cacheAdditions: sheetCacheAdditions(accepted)
|
|
6292
|
+
};
|
|
5513
6293
|
}
|
|
5514
6294
|
async function importWorkbook(input, deps = {}) {
|
|
5515
6295
|
const config = input.config;
|
|
@@ -5534,9 +6314,12 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5534
6314
|
fs,
|
|
5535
6315
|
source: source.resource,
|
|
5536
6316
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
5537
|
-
dryRun
|
|
6317
|
+
dryRun,
|
|
6318
|
+
malformedRows: data.malformedRows,
|
|
6319
|
+
duplicateKeys: data.duplicateKeys
|
|
5538
6320
|
};
|
|
5539
6321
|
const summaries = [];
|
|
6322
|
+
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
5540
6323
|
for (const sheet of data.sheets) {
|
|
5541
6324
|
try {
|
|
5542
6325
|
let summary;
|
|
@@ -5549,6 +6332,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5549
6332
|
mode: "replace",
|
|
5550
6333
|
entries: result.lockEntries
|
|
5551
6334
|
});
|
|
6335
|
+
collectSheetAdditions(cacheAdditions, sheet.locale, result.cacheAdditions);
|
|
5552
6336
|
return result.summary;
|
|
5553
6337
|
});
|
|
5554
6338
|
}
|
|
@@ -5557,6 +6341,15 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5557
6341
|
summaries.push(failureSummary(sheet.locale, error));
|
|
5558
6342
|
}
|
|
5559
6343
|
}
|
|
6344
|
+
const presentLocales = new Set(data.sheets.map((sheet) => sheet.locale));
|
|
6345
|
+
for (const locale of config.targetLocales) {
|
|
6346
|
+
if (!presentLocales.has(locale)) {
|
|
6347
|
+
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
if (!dryRun) {
|
|
6351
|
+
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6352
|
+
}
|
|
5560
6353
|
const { succeeded, partial, failed } = partition(summaries);
|
|
5561
6354
|
return { dryRun, locales: summaries, succeeded, partial, failed };
|
|
5562
6355
|
}
|
|
@@ -5620,7 +6413,10 @@ async function watch(input, deps = {}) {
|
|
|
5620
6413
|
config: input.config,
|
|
5621
6414
|
cwd,
|
|
5622
6415
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5623
|
-
...input.
|
|
6416
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6417
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
6418
|
+
...input.concurrency !== void 0 ? { concurrency: input.concurrency } : {},
|
|
6419
|
+
...input.cache !== void 0 ? { cache: input.cache } : {}
|
|
5624
6420
|
};
|
|
5625
6421
|
let state = "idle";
|
|
5626
6422
|
let pending = false;
|
|
@@ -5688,6 +6484,6 @@ async function watch(input, deps = {}) {
|
|
|
5688
6484
|
return { stop };
|
|
5689
6485
|
}
|
|
5690
6486
|
|
|
5691
|
-
export { DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
6487
|
+
export { CACHE_FILE_NAME, DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
5692
6488
|
//# sourceMappingURL=index.js.map
|
|
5693
6489
|
//# sourceMappingURL=index.js.map
|