@verbatra/sdk 0.6.1 → 0.6.3

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