@verbatra/sdk 0.6.0 → 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 +1338 -279
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +350 -70
- package/dist/index.d.ts +350 -70
- package/dist/index.js +1338 -280
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var fs = require('fs');
|
|
4
3
|
var path = require('path');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
var fs = require('fs');
|
|
5
6
|
var cosmiconfig = require('cosmiconfig');
|
|
6
7
|
var cosmiconfigTypescriptLoader = require('cosmiconfig-typescript-loader');
|
|
7
8
|
var crypto = require('crypto');
|
|
8
9
|
var promises = require('fs/promises');
|
|
9
|
-
var zod = require('zod');
|
|
10
10
|
var Anthropic = require('@anthropic-ai/sdk');
|
|
11
11
|
var deepl = require('deepl-node');
|
|
12
12
|
var module$1 = require('module');
|
|
@@ -17,8 +17,8 @@ var icuMessageformatParser = require('@formatjs/icu-messageformat-parser');
|
|
|
17
17
|
var xmldom = require('@xmldom/xmldom');
|
|
18
18
|
var yaml = require('yaml');
|
|
19
19
|
var ExcelJS = require('exceljs');
|
|
20
|
-
var stream = require('stream');
|
|
21
20
|
var JSZip = require('jszip');
|
|
21
|
+
var stream = require('stream');
|
|
22
22
|
var chokidar = require('chokidar');
|
|
23
23
|
|
|
24
24
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -49,6 +49,96 @@ var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
|
|
|
49
49
|
var ExcelJS__default = /*#__PURE__*/_interopDefault(ExcelJS);
|
|
50
50
|
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
51
51
|
|
|
52
|
+
// src/cache/translation-memory.ts
|
|
53
|
+
var CACHE_FILE_NAME = "verbatra.cache.json";
|
|
54
|
+
var CURRENT_VERSION = 1;
|
|
55
|
+
var EMPTY_MEMORY = { version: CURRENT_VERSION, entries: {} };
|
|
56
|
+
var MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
|
|
57
|
+
var translationMemorySchema = zod.z.object({
|
|
58
|
+
version: zod.z.number().int().positive(),
|
|
59
|
+
entries: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.string())))
|
|
60
|
+
});
|
|
61
|
+
function cacheFilePath(cwd) {
|
|
62
|
+
return path.resolve(cwd, CACHE_FILE_NAME);
|
|
63
|
+
}
|
|
64
|
+
async function readTranslationMemory(path, fs) {
|
|
65
|
+
let read;
|
|
66
|
+
try {
|
|
67
|
+
read = await fs.readFileBounded(path, MAX_CACHE_FILE_BYTES);
|
|
68
|
+
} catch {
|
|
69
|
+
return EMPTY_MEMORY;
|
|
70
|
+
}
|
|
71
|
+
if (read.kind !== "ok") {
|
|
72
|
+
return EMPTY_MEMORY;
|
|
73
|
+
}
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(read.content);
|
|
77
|
+
} catch {
|
|
78
|
+
return EMPTY_MEMORY;
|
|
79
|
+
}
|
|
80
|
+
const result = translationMemorySchema.safeParse(parsed);
|
|
81
|
+
if (!result.success || result.data.version !== CURRENT_VERSION) {
|
|
82
|
+
return EMPTY_MEMORY;
|
|
83
|
+
}
|
|
84
|
+
return result.data;
|
|
85
|
+
}
|
|
86
|
+
function lookupMemory(memory, fingerprint, locale, contentHash2) {
|
|
87
|
+
return memory.entries[fingerprint]?.[locale]?.[contentHash2];
|
|
88
|
+
}
|
|
89
|
+
function applyAdditions(base, fingerprint, additionsByLocale) {
|
|
90
|
+
if (additionsByLocale.size === 0) {
|
|
91
|
+
return base;
|
|
92
|
+
}
|
|
93
|
+
const fingerprintEntries = {};
|
|
94
|
+
for (const [locale, hashes] of Object.entries(base.entries[fingerprint] ?? {})) {
|
|
95
|
+
fingerprintEntries[locale] = { ...hashes };
|
|
96
|
+
}
|
|
97
|
+
for (const [locale, hashes] of additionsByLocale) {
|
|
98
|
+
fingerprintEntries[locale] = { ...fingerprintEntries[locale], ...hashes };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
version: base.version,
|
|
102
|
+
entries: { ...base.entries, [fingerprint]: fingerprintEntries }
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function additionsToRecord(additions) {
|
|
106
|
+
const record = {};
|
|
107
|
+
for (const addition of additions) {
|
|
108
|
+
record[addition.contentHash] = addition.value;
|
|
109
|
+
}
|
|
110
|
+
return record;
|
|
111
|
+
}
|
|
112
|
+
function sortEntries(record) {
|
|
113
|
+
return Object.entries(record).sort(([a], [b]) => a < b ? -1 : 1);
|
|
114
|
+
}
|
|
115
|
+
function serialize(memory) {
|
|
116
|
+
const entries = {};
|
|
117
|
+
for (const [fingerprint, locales] of sortEntries(memory.entries)) {
|
|
118
|
+
const localeMap = {};
|
|
119
|
+
for (const [locale, hashes] of sortEntries(locales)) {
|
|
120
|
+
localeMap[locale] = Object.fromEntries(sortEntries(hashes));
|
|
121
|
+
}
|
|
122
|
+
entries[fingerprint] = localeMap;
|
|
123
|
+
}
|
|
124
|
+
return `${JSON.stringify({ version: memory.version, entries }, null, 2)}
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
async function writeTranslationMemory(path, memory, fs) {
|
|
128
|
+
await fs.writeFile(path, serialize(memory));
|
|
129
|
+
}
|
|
130
|
+
async function feedTranslationMemory(cwd, fs, fingerprint, additionsByLocale) {
|
|
131
|
+
if (additionsByLocale.size === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const path = cacheFilePath(cwd);
|
|
136
|
+
const memory = await readTranslationMemory(path, fs);
|
|
137
|
+
await writeTranslationMemory(path, applyAdditions(memory, fingerprint, additionsByLocale), fs);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
52
142
|
// src/config/define-config.ts
|
|
53
143
|
function defineConfig(config) {
|
|
54
144
|
return config;
|
|
@@ -242,7 +332,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
242
332
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
243
333
|
var FNV_PRIME = 1099511628211n;
|
|
244
334
|
var U64_MASK = (1n << 64n) - 1n;
|
|
245
|
-
function
|
|
335
|
+
function stableStringHash(input) {
|
|
246
336
|
let hash = FNV_OFFSET_BASIS;
|
|
247
337
|
for (let index = 0; index < input.length; index += 1) {
|
|
248
338
|
hash ^= BigInt(input.charCodeAt(index));
|
|
@@ -263,7 +353,7 @@ function canonicalize(entry) {
|
|
|
263
353
|
]);
|
|
264
354
|
}
|
|
265
355
|
function contentHash(entry) {
|
|
266
|
-
return
|
|
356
|
+
return stableStringHash(canonicalize(entry));
|
|
267
357
|
}
|
|
268
358
|
function sorted(keys) {
|
|
269
359
|
return [...keys].sort();
|
|
@@ -308,7 +398,8 @@ var SUPPORTED_FORMATS = [
|
|
|
308
398
|
"ngx-translate-json",
|
|
309
399
|
"xliff",
|
|
310
400
|
"yaml",
|
|
311
|
-
"arb"
|
|
401
|
+
"arb",
|
|
402
|
+
"properties"
|
|
312
403
|
];
|
|
313
404
|
var supportedFormatSchema = zod.z.enum(SUPPORTED_FORMATS);
|
|
314
405
|
var translationEntrySchema = zod.z.object({
|
|
@@ -359,77 +450,76 @@ function checkPlaceholders(source, translated) {
|
|
|
359
450
|
reordered
|
|
360
451
|
};
|
|
361
452
|
}
|
|
362
|
-
var
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
var
|
|
367
|
-
var
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
var AUTH_FAILED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
374
|
-
"AuthenticationError",
|
|
375
|
-
"PermissionDeniedError",
|
|
376
|
-
"AuthorizationError"
|
|
377
|
-
]);
|
|
378
|
-
var TIMEOUT_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
379
|
-
"APIConnectionTimeoutError",
|
|
380
|
-
"ConnectionError"
|
|
381
|
-
]);
|
|
382
|
-
var ABORT_ERROR_CLASS_NAMES = /* @__PURE__ */ new Set(["APIUserAbortError"]);
|
|
383
|
-
function getErrorStatus(error) {
|
|
384
|
-
if (typeof error !== "object" || error === null || !("status" in error)) {
|
|
385
|
-
return void 0;
|
|
453
|
+
var MIN_SOURCE_LENGTH = 8;
|
|
454
|
+
var MAX_LENGTH_MULTIPLE = 12;
|
|
455
|
+
var MAX_REPEAT_UNIT_LENGTH = 16;
|
|
456
|
+
var MIN_CONSECUTIVE_REPEATS = 8;
|
|
457
|
+
var REPEAT_COVERAGE_FRACTION = 0.5;
|
|
458
|
+
var MAX_SCAN_LENGTH = 8192;
|
|
459
|
+
function matchesUnit(text, a, b, unit) {
|
|
460
|
+
for (let i = 0; i < unit; i++) {
|
|
461
|
+
if (text[a + i] !== text[b + i]) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
386
464
|
}
|
|
387
|
-
|
|
388
|
-
return typeof status === "number" ? status : void 0;
|
|
465
|
+
return true;
|
|
389
466
|
}
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
|
|
467
|
+
function countConsecutiveCopies(text, start, unit, scanLength) {
|
|
468
|
+
let copies = 1;
|
|
469
|
+
let next = start + unit;
|
|
470
|
+
while (next + unit <= scanLength && matchesUnit(text, start, next, unit)) {
|
|
471
|
+
copies++;
|
|
472
|
+
next += unit;
|
|
473
|
+
}
|
|
474
|
+
return copies;
|
|
475
|
+
}
|
|
476
|
+
function hasDominantRun(text, scanLength, unit, coverage) {
|
|
477
|
+
const limit = scanLength - unit;
|
|
478
|
+
let start = 0;
|
|
479
|
+
while (start <= limit) {
|
|
480
|
+
const copies = countConsecutiveCopies(text, start, unit, scanLength);
|
|
481
|
+
if (copies >= MIN_CONSECUTIVE_REPEATS && copies * unit >= coverage) {
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
start += copies * unit;
|
|
393
485
|
}
|
|
394
|
-
return
|
|
486
|
+
return false;
|
|
395
487
|
}
|
|
396
|
-
function
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
const className = readClassName(error);
|
|
408
|
-
if (className !== void 0) {
|
|
409
|
-
if (RATE_LIMITED_CLASS_NAMES.has(className)) {
|
|
410
|
-
return "RATE_LIMITED";
|
|
411
|
-
}
|
|
412
|
-
if (AUTH_FAILED_CLASS_NAMES.has(className)) {
|
|
413
|
-
return "AUTH_FAILED";
|
|
414
|
-
}
|
|
415
|
-
if (TIMEOUT_CLASS_NAMES.has(className)) {
|
|
416
|
-
return "TIMEOUT";
|
|
488
|
+
function hasRunawayRepetition(text) {
|
|
489
|
+
const scanLength = Math.min(text.length, MAX_SCAN_LENGTH);
|
|
490
|
+
const coverage = scanLength * REPEAT_COVERAGE_FRACTION;
|
|
491
|
+
const maxUnit = Math.min(
|
|
492
|
+
MAX_REPEAT_UNIT_LENGTH,
|
|
493
|
+
Math.floor(scanLength / MIN_CONSECUTIVE_REPEATS)
|
|
494
|
+
);
|
|
495
|
+
for (let unit = 1; unit <= maxUnit; unit++) {
|
|
496
|
+
if (hasDominantRun(text, scanLength, unit, coverage)) {
|
|
497
|
+
return true;
|
|
417
498
|
}
|
|
418
499
|
}
|
|
419
|
-
return
|
|
500
|
+
return false;
|
|
420
501
|
}
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
|
|
502
|
+
function assessValueDegeneracy(sourceValue, candidateValue) {
|
|
503
|
+
const source = sourceValue.trim();
|
|
504
|
+
const candidate = candidateValue.trim();
|
|
505
|
+
if (source.length >= MIN_SOURCE_LENGTH && candidate.length >= source.length * MAX_LENGTH_MULTIPLE) {
|
|
506
|
+
return { degenerate: true };
|
|
424
507
|
}
|
|
425
|
-
|
|
426
|
-
return className !== void 0 && ABORT_ERROR_CLASS_NAMES.has(className);
|
|
508
|
+
return { degenerate: hasRunawayRepetition(candidate) };
|
|
427
509
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
510
|
+
var LOCALE_TOKEN = "{locale}";
|
|
511
|
+
function localeFilePath(cwd, pattern, locale) {
|
|
512
|
+
return path.resolve(cwd, pattern.replaceAll(LOCALE_TOKEN, locale));
|
|
513
|
+
}
|
|
514
|
+
function checkBatchIntegrity(inputs, extract, compare) {
|
|
515
|
+
const integrity = /* @__PURE__ */ new Map();
|
|
516
|
+
for (const { key, sourceValue, translatedValue } of inputs) {
|
|
517
|
+
integrity.set(
|
|
518
|
+
key,
|
|
519
|
+
compare !== void 0 ? compare(sourceValue, translatedValue) : checkPlaceholders(extract(sourceValue), extract(translatedValue))
|
|
520
|
+
);
|
|
431
521
|
}
|
|
432
|
-
return
|
|
522
|
+
return integrity;
|
|
433
523
|
}
|
|
434
524
|
var REDACTED = "[REDACTED]";
|
|
435
525
|
var KEY_PATTERNS = [
|
|
@@ -467,38 +557,6 @@ var ProviderError = class extends Error {
|
|
|
467
557
|
this.code = code;
|
|
468
558
|
}
|
|
469
559
|
};
|
|
470
|
-
var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
471
|
-
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
472
|
-
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
473
|
-
var AUTH_FAILED_MESSAGE = "The translation provider rejected the request credentials.";
|
|
474
|
-
var MESSAGE_BY_CODE = {
|
|
475
|
-
RATE_LIMITED: RATE_LIMITED_MESSAGE,
|
|
476
|
-
TIMEOUT: TIMEOUT_MESSAGE,
|
|
477
|
-
AUTH_FAILED: AUTH_FAILED_MESSAGE,
|
|
478
|
-
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
479
|
-
};
|
|
480
|
-
async function guardProviderCall(call, signal) {
|
|
481
|
-
signal?.throwIfAborted();
|
|
482
|
-
try {
|
|
483
|
-
return await call();
|
|
484
|
-
} catch (error) {
|
|
485
|
-
if (isAbortError(error, signal)) {
|
|
486
|
-
throw error;
|
|
487
|
-
}
|
|
488
|
-
const code = classifyProviderError(error);
|
|
489
|
-
throw new ProviderError(code, MESSAGE_BY_CODE[code]);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
function checkBatchIntegrity(inputs, extract, compare) {
|
|
493
|
-
const integrity = /* @__PURE__ */ new Map();
|
|
494
|
-
for (const { key, sourceValue, translatedValue } of inputs) {
|
|
495
|
-
integrity.set(
|
|
496
|
-
key,
|
|
497
|
-
compare !== void 0 ? compare(sourceValue, translatedValue) : checkPlaceholders(extract(sourceValue), extract(translatedValue))
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
return integrity;
|
|
501
|
-
}
|
|
502
560
|
var requestDataSchema = zod.z.object({
|
|
503
561
|
sourceLocale: zod.z.string().min(1),
|
|
504
562
|
targetLocale: zod.z.string().min(1),
|
|
@@ -741,6 +799,121 @@ function assertNotTruncated(truncated) {
|
|
|
741
799
|
throw new ProviderError("OUTPUT_TRUNCATED", OUTPUT_TRUNCATED_MESSAGE);
|
|
742
800
|
}
|
|
743
801
|
}
|
|
802
|
+
var RATE_LIMITED_STATUS = 429;
|
|
803
|
+
var TIMEOUT_STATUS = 408;
|
|
804
|
+
var AUTH_FAILED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
805
|
+
var RATE_LIMITED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
806
|
+
"RateLimitError",
|
|
807
|
+
"TooManyRequestsError"
|
|
808
|
+
]);
|
|
809
|
+
var AUTH_FAILED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
810
|
+
"AuthenticationError",
|
|
811
|
+
"PermissionDeniedError",
|
|
812
|
+
"AuthorizationError"
|
|
813
|
+
]);
|
|
814
|
+
var TIMEOUT_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
815
|
+
"APIConnectionTimeoutError",
|
|
816
|
+
"ConnectionError"
|
|
817
|
+
]);
|
|
818
|
+
var ABORT_ERROR_CLASS_NAMES = /* @__PURE__ */ new Set(["APIUserAbortError"]);
|
|
819
|
+
function getErrorStatus(error) {
|
|
820
|
+
if (typeof error !== "object" || error === null || !("status" in error)) {
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
const status = error.status;
|
|
824
|
+
return typeof status === "number" ? status : void 0;
|
|
825
|
+
}
|
|
826
|
+
function readClassName(error) {
|
|
827
|
+
if (typeof error !== "object" || error === null) {
|
|
828
|
+
return void 0;
|
|
829
|
+
}
|
|
830
|
+
return error.constructor?.name;
|
|
831
|
+
}
|
|
832
|
+
function classifyProviderError(error) {
|
|
833
|
+
const status = getErrorStatus(error);
|
|
834
|
+
if (status === RATE_LIMITED_STATUS) {
|
|
835
|
+
return "RATE_LIMITED";
|
|
836
|
+
}
|
|
837
|
+
if (status !== void 0 && AUTH_FAILED_STATUSES.has(status)) {
|
|
838
|
+
return "AUTH_FAILED";
|
|
839
|
+
}
|
|
840
|
+
if (status === TIMEOUT_STATUS) {
|
|
841
|
+
return "TIMEOUT";
|
|
842
|
+
}
|
|
843
|
+
const className = readClassName(error);
|
|
844
|
+
if (className !== void 0) {
|
|
845
|
+
if (RATE_LIMITED_CLASS_NAMES.has(className)) {
|
|
846
|
+
return "RATE_LIMITED";
|
|
847
|
+
}
|
|
848
|
+
if (AUTH_FAILED_CLASS_NAMES.has(className)) {
|
|
849
|
+
return "AUTH_FAILED";
|
|
850
|
+
}
|
|
851
|
+
if (TIMEOUT_CLASS_NAMES.has(className)) {
|
|
852
|
+
return "TIMEOUT";
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return "PROVIDER_ERROR";
|
|
856
|
+
}
|
|
857
|
+
function isAbortShapedError(error) {
|
|
858
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
859
|
+
return true;
|
|
860
|
+
}
|
|
861
|
+
const className = readClassName(error);
|
|
862
|
+
return className !== void 0 && ABORT_ERROR_CLASS_NAMES.has(className);
|
|
863
|
+
}
|
|
864
|
+
function isAbortError(error, signal) {
|
|
865
|
+
if (signal !== void 0) {
|
|
866
|
+
return signal.aborted === true && isAbortShapedError(error);
|
|
867
|
+
}
|
|
868
|
+
return isAbortShapedError(error);
|
|
869
|
+
}
|
|
870
|
+
var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
871
|
+
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
872
|
+
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
873
|
+
var AUTH_FAILED_MESSAGE = "The translation provider rejected the request credentials.";
|
|
874
|
+
var MESSAGE_BY_CODE = {
|
|
875
|
+
RATE_LIMITED: RATE_LIMITED_MESSAGE,
|
|
876
|
+
TIMEOUT: TIMEOUT_MESSAGE,
|
|
877
|
+
AUTH_FAILED: AUTH_FAILED_MESSAGE,
|
|
878
|
+
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
879
|
+
};
|
|
880
|
+
async function guardProviderCall(call, signal) {
|
|
881
|
+
signal?.throwIfAborted();
|
|
882
|
+
try {
|
|
883
|
+
return await call();
|
|
884
|
+
} catch (error) {
|
|
885
|
+
if (isAbortError(error, signal)) {
|
|
886
|
+
throw error;
|
|
887
|
+
}
|
|
888
|
+
const code = classifyProviderError(error);
|
|
889
|
+
throw new ProviderError(code, MESSAGE_BY_CODE[code]);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
893
|
+
function requestTimedOutMessage(timeoutMs) {
|
|
894
|
+
return `The translation provider request exceeded the ${timeoutMs} ms request timeout.`;
|
|
895
|
+
}
|
|
896
|
+
function combineSignals(caller, timeout) {
|
|
897
|
+
return caller === void 0 ? timeout : AbortSignal.any([caller, timeout]);
|
|
898
|
+
}
|
|
899
|
+
async function withRequestTimeout(timeoutMs, callerSignal, call) {
|
|
900
|
+
const timeoutController = new AbortController();
|
|
901
|
+
const signal = combineSignals(callerSignal, timeoutController.signal);
|
|
902
|
+
let timer;
|
|
903
|
+
const timedOut = new Promise((_, reject) => {
|
|
904
|
+
timer = setTimeout(() => {
|
|
905
|
+
timeoutController.abort();
|
|
906
|
+
reject(new ProviderError("TIMEOUT", requestTimedOutMessage(timeoutMs)));
|
|
907
|
+
}, timeoutMs);
|
|
908
|
+
});
|
|
909
|
+
try {
|
|
910
|
+
return await Promise.race([guardProviderCall(() => call(signal), signal), timedOut]);
|
|
911
|
+
} finally {
|
|
912
|
+
if (timer !== void 0) {
|
|
913
|
+
clearTimeout(timer);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
744
917
|
var PROVIDER_ENV = {
|
|
745
918
|
anthropic: "ANTHROPIC_API_KEY",
|
|
746
919
|
openai: "OPENAI_API_KEY",
|
|
@@ -795,7 +968,13 @@ function createDefaultClient() {
|
|
|
795
968
|
}
|
|
796
969
|
var anthropicConfigSchema = zod.z.object({
|
|
797
970
|
model: zod.z.string().min(1),
|
|
798
|
-
maxTokens: zod.z.number().int().positive()
|
|
971
|
+
maxTokens: zod.z.number().int().positive(),
|
|
972
|
+
/**
|
|
973
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
974
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
975
|
+
* server cannot hold a locale's write lock open forever.
|
|
976
|
+
*/
|
|
977
|
+
requestTimeoutMs: zod.z.number().int().positive().optional()
|
|
799
978
|
});
|
|
800
979
|
var SUBMIT_TOOL_NAME = "submit_translations";
|
|
801
980
|
var SYSTEM_RULES = [
|
|
@@ -855,10 +1034,11 @@ function createAnthropicProvider(config, deps = {}) {
|
|
|
855
1034
|
};
|
|
856
1035
|
}
|
|
857
1036
|
function createMechanism(client, config) {
|
|
1037
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
858
1038
|
return {
|
|
859
1039
|
translate: async ({ payloadJson, signal }) => {
|
|
860
1040
|
const body = buildRequest(config, payloadJson);
|
|
861
|
-
const message = await callClient(client, body, signal);
|
|
1041
|
+
const message = await callClient(client, body, timeoutMs, signal);
|
|
862
1042
|
assertNotTruncated(message.stop_reason === "max_tokens");
|
|
863
1043
|
const raw = requireToolInput(message.content);
|
|
864
1044
|
const usage = toUsage(message.usage);
|
|
@@ -866,10 +1046,11 @@ function createMechanism(client, config) {
|
|
|
866
1046
|
}
|
|
867
1047
|
};
|
|
868
1048
|
}
|
|
869
|
-
function callClient(client, body, signal) {
|
|
870
|
-
return
|
|
871
|
-
|
|
872
|
-
signal
|
|
1049
|
+
function callClient(client, body, timeoutMs, signal) {
|
|
1050
|
+
return withRequestTimeout(
|
|
1051
|
+
timeoutMs,
|
|
1052
|
+
signal,
|
|
1053
|
+
(requestSignal) => client.messages.create(body, { signal: requestSignal })
|
|
873
1054
|
);
|
|
874
1055
|
}
|
|
875
1056
|
function toUsage(usage) {
|
|
@@ -883,7 +1064,14 @@ function toUsage(usage) {
|
|
|
883
1064
|
return { inputTokens: input_tokens, outputTokens: output_tokens };
|
|
884
1065
|
}
|
|
885
1066
|
var deepLConfigSchema = zod.z.object({
|
|
886
|
-
glossaryId: zod.z.string().min(1).optional()
|
|
1067
|
+
glossaryId: zod.z.string().min(1).optional(),
|
|
1068
|
+
/**
|
|
1069
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1070
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
1071
|
+
* server cannot hold a locale's write lock open forever. deepl-node cannot cancel an in-flight
|
|
1072
|
+
* request, so on timeout verbatra's await is released but the underlying request is left to settle.
|
|
1073
|
+
*/
|
|
1074
|
+
requestTimeoutMs: zod.z.number().int().positive().optional()
|
|
887
1075
|
});
|
|
888
1076
|
var DEEPL_LOGGER = "deepl";
|
|
889
1077
|
function resolveDeeplLoglevel(requireFn = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
@@ -1054,6 +1242,7 @@ async function translate(bundle, config, request) {
|
|
|
1054
1242
|
options,
|
|
1055
1243
|
request.extractPlaceholders,
|
|
1056
1244
|
request.comparePlaceholders,
|
|
1245
|
+
config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
1057
1246
|
request.signal
|
|
1058
1247
|
);
|
|
1059
1248
|
if (unprotectable.length > 0) {
|
|
@@ -1088,7 +1277,7 @@ function buildReviewFlags2(protectable, data, values, integrity, glossary) {
|
|
|
1088
1277
|
}
|
|
1089
1278
|
return reviewFlags;
|
|
1090
1279
|
}
|
|
1091
|
-
async function translateProtectable(client, data, protectable, options, extract, compare, signal) {
|
|
1280
|
+
async function translateProtectable(client, data, protectable, options, extract, compare, timeoutMs, signal) {
|
|
1092
1281
|
if (protectable.length === 0) {
|
|
1093
1282
|
return { values: /* @__PURE__ */ new Map(), integrity: /* @__PURE__ */ new Map() };
|
|
1094
1283
|
}
|
|
@@ -1099,29 +1288,45 @@ async function translateProtectable(client, data, protectable, options, extract,
|
|
|
1099
1288
|
data.sourceLocale,
|
|
1100
1289
|
data.targetLocale,
|
|
1101
1290
|
options,
|
|
1291
|
+
timeoutMs,
|
|
1102
1292
|
signal
|
|
1103
1293
|
);
|
|
1104
1294
|
const { values, integrityInputs } = zipResults(protectable, results);
|
|
1105
1295
|
const integrity = checkBatchIntegrity(integrityInputs, extract, compare);
|
|
1106
1296
|
return { values, integrity };
|
|
1107
1297
|
}
|
|
1108
|
-
function callClient2(client, texts, sourceLang, targetLang, options, signal) {
|
|
1109
|
-
return
|
|
1110
|
-
|
|
1111
|
-
signal
|
|
1298
|
+
function callClient2(client, texts, sourceLang, targetLang, options, timeoutMs, signal) {
|
|
1299
|
+
return withRequestTimeout(
|
|
1300
|
+
timeoutMs,
|
|
1301
|
+
signal,
|
|
1302
|
+
() => client.translateText(texts, sourceLang, targetLang, options)
|
|
1112
1303
|
);
|
|
1113
1304
|
}
|
|
1114
|
-
async function callClientChunked(client, texts, sourceLang, targetLang, options, signal) {
|
|
1305
|
+
async function callClientChunked(client, texts, sourceLang, targetLang, options, timeoutMs, signal) {
|
|
1115
1306
|
const results = [];
|
|
1116
1307
|
for (const chunk2 of chunkTextsForDeepL(texts)) {
|
|
1117
|
-
const chunkResults = await callClient2(
|
|
1308
|
+
const chunkResults = await callClient2(
|
|
1309
|
+
client,
|
|
1310
|
+
chunk2,
|
|
1311
|
+
sourceLang,
|
|
1312
|
+
targetLang,
|
|
1313
|
+
options,
|
|
1314
|
+
timeoutMs,
|
|
1315
|
+
signal
|
|
1316
|
+
);
|
|
1118
1317
|
results.push(...chunkResults);
|
|
1119
1318
|
}
|
|
1120
1319
|
return results;
|
|
1121
1320
|
}
|
|
1122
1321
|
var geminiConfigSchema = zod.z.object({
|
|
1123
1322
|
model: zod.z.string().min(1),
|
|
1124
|
-
maxOutputTokens: zod.z.number().int().positive()
|
|
1323
|
+
maxOutputTokens: zod.z.number().int().positive(),
|
|
1324
|
+
/**
|
|
1325
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1326
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
1327
|
+
* server cannot hold a locale's write lock open forever.
|
|
1328
|
+
*/
|
|
1329
|
+
requestTimeoutMs: zod.z.number().int().positive().optional()
|
|
1125
1330
|
});
|
|
1126
1331
|
var DEFAULT_GEMINI_RETRY = { attempts: 3, baseDelayMs: 250 };
|
|
1127
1332
|
function isRetryableStatus(error) {
|
|
@@ -1136,14 +1341,14 @@ function throwAbort(signal) {
|
|
|
1136
1341
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1137
1342
|
}
|
|
1138
1343
|
function delay(ms, signal) {
|
|
1139
|
-
return new Promise((
|
|
1344
|
+
return new Promise((resolve10) => {
|
|
1140
1345
|
const onAbort = () => {
|
|
1141
1346
|
clearTimeout(timer);
|
|
1142
|
-
|
|
1347
|
+
resolve10();
|
|
1143
1348
|
};
|
|
1144
1349
|
const timer = setTimeout(() => {
|
|
1145
1350
|
signal?.removeEventListener("abort", onAbort);
|
|
1146
|
-
|
|
1351
|
+
resolve10();
|
|
1147
1352
|
}, ms);
|
|
1148
1353
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1149
1354
|
});
|
|
@@ -1311,20 +1516,27 @@ function createGeminiProvider(config, deps = {}) {
|
|
|
1311
1516
|
};
|
|
1312
1517
|
}
|
|
1313
1518
|
function createMechanism2(client, config) {
|
|
1519
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1314
1520
|
return {
|
|
1315
1521
|
translate: async ({ payloadJson, signal }) => {
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1522
|
+
const response = await withRequestTimeout(
|
|
1523
|
+
timeoutMs,
|
|
1524
|
+
signal,
|
|
1525
|
+
(requestSignal) => client.models.generateContent(buildGeminiRequest(config, payloadJson, requestSignal))
|
|
1526
|
+
);
|
|
1318
1527
|
return extractGeminiResult(response);
|
|
1319
1528
|
}
|
|
1320
1529
|
};
|
|
1321
1530
|
}
|
|
1322
|
-
function callClient3(client, request, signal) {
|
|
1323
|
-
return guardProviderCall(() => client.models.generateContent(request), signal);
|
|
1324
|
-
}
|
|
1325
1531
|
var openAiConfigSchema = zod.z.object({
|
|
1326
1532
|
model: zod.z.string().min(1),
|
|
1327
|
-
maxOutputTokens: zod.z.number().int().positive()
|
|
1533
|
+
maxOutputTokens: zod.z.number().int().positive(),
|
|
1534
|
+
/**
|
|
1535
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1536
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
1537
|
+
* server cannot hold a locale's write lock open forever.
|
|
1538
|
+
*/
|
|
1539
|
+
requestTimeoutMs: zod.z.number().int().positive().optional()
|
|
1328
1540
|
});
|
|
1329
1541
|
function createDefaultClient4() {
|
|
1330
1542
|
const sdk = new OpenAI__default.default({ apiKey: requireOpenAiKey(), logLevel: "off" });
|
|
@@ -1479,18 +1691,20 @@ function createOpenAiProvider(config, deps = {}) {
|
|
|
1479
1691
|
};
|
|
1480
1692
|
}
|
|
1481
1693
|
function createMechanism3(client, config) {
|
|
1694
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1482
1695
|
return {
|
|
1483
1696
|
translate: async ({ payloadJson, signal }) => {
|
|
1484
1697
|
const body = buildOpenAiRequest(config, payloadJson);
|
|
1485
|
-
const completion = await
|
|
1698
|
+
const completion = await callClient3(client, body, timeoutMs, signal);
|
|
1486
1699
|
return extractOpenAiResult(completion);
|
|
1487
1700
|
}
|
|
1488
1701
|
};
|
|
1489
1702
|
}
|
|
1490
|
-
function
|
|
1491
|
-
return
|
|
1492
|
-
|
|
1493
|
-
signal
|
|
1703
|
+
function callClient3(client, body, timeoutMs, signal) {
|
|
1704
|
+
return withRequestTimeout(
|
|
1705
|
+
timeoutMs,
|
|
1706
|
+
signal,
|
|
1707
|
+
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal })
|
|
1494
1708
|
);
|
|
1495
1709
|
}
|
|
1496
1710
|
var HOSTED_PROVIDER_ENV_VARS = new Set(
|
|
@@ -1513,7 +1727,13 @@ var openAiCompatibleConfigSchema = zod.z.object({
|
|
|
1513
1727
|
maxOutputTokens: zod.z.number().int().positive(),
|
|
1514
1728
|
apiKeyEnvVar: zod.z.string().min(1).refine(isNotHostedProviderEnvVar, {
|
|
1515
1729
|
message: "apiKeyEnvVar must not name a hosted provider's environment variable."
|
|
1516
|
-
}).optional()
|
|
1730
|
+
}).optional(),
|
|
1731
|
+
/**
|
|
1732
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1733
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive local
|
|
1734
|
+
* server (the reported stuck-LM-Studio case) cannot hold a locale's write lock open forever.
|
|
1735
|
+
*/
|
|
1736
|
+
requestTimeoutMs: zod.z.number().int().positive().optional()
|
|
1517
1737
|
});
|
|
1518
1738
|
function createDefaultClient5(config) {
|
|
1519
1739
|
const sdk = new OpenAI__default.default({
|
|
@@ -1545,18 +1765,20 @@ function createOpenAiCompatibleProvider(config, deps = {}) {
|
|
|
1545
1765
|
};
|
|
1546
1766
|
}
|
|
1547
1767
|
function createMechanism4(client, config) {
|
|
1768
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1548
1769
|
return {
|
|
1549
1770
|
translate: async ({ payloadJson, signal }) => {
|
|
1550
1771
|
const body = buildOpenAiRequest(config, payloadJson, "strict-schema", "max_tokens");
|
|
1551
|
-
const completion = await
|
|
1772
|
+
const completion = await callClient4(client, body, timeoutMs, signal);
|
|
1552
1773
|
return extractOpenAiResult(completion, true);
|
|
1553
1774
|
}
|
|
1554
1775
|
};
|
|
1555
1776
|
}
|
|
1556
|
-
function
|
|
1557
|
-
return
|
|
1558
|
-
|
|
1559
|
-
signal
|
|
1777
|
+
function callClient4(client, body, timeoutMs, signal) {
|
|
1778
|
+
return withRequestTimeout(
|
|
1779
|
+
timeoutMs,
|
|
1780
|
+
signal,
|
|
1781
|
+
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal })
|
|
1560
1782
|
);
|
|
1561
1783
|
}
|
|
1562
1784
|
var SCAFFOLD_MODELS = {
|
|
@@ -1766,6 +1988,8 @@ var LOCAL_DIR_NAME = ".verbatra-local";
|
|
|
1766
1988
|
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
1767
1989
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
1768
1990
|
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
1991
|
+
var WAIT_NOTICE_INTERVAL_MS = 1e3;
|
|
1992
|
+
var MAX_LOCK_PAYLOAD_BYTES = 64 * 1024;
|
|
1769
1993
|
function lockPath(cwd, stem) {
|
|
1770
1994
|
return path.resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
1771
1995
|
}
|
|
@@ -1783,7 +2007,47 @@ function sleep(ms) {
|
|
|
1783
2007
|
function lockPayload() {
|
|
1784
2008
|
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1785
2009
|
}
|
|
1786
|
-
|
|
2010
|
+
function parseHolder(read) {
|
|
2011
|
+
if (read.kind !== "ok") {
|
|
2012
|
+
return void 0;
|
|
2013
|
+
}
|
|
2014
|
+
let parsed;
|
|
2015
|
+
try {
|
|
2016
|
+
parsed = JSON.parse(read.content);
|
|
2017
|
+
} catch {
|
|
2018
|
+
return void 0;
|
|
2019
|
+
}
|
|
2020
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
2021
|
+
return void 0;
|
|
2022
|
+
}
|
|
2023
|
+
const record = parsed;
|
|
2024
|
+
const holder = {};
|
|
2025
|
+
if (typeof record.pid === "number") {
|
|
2026
|
+
holder.pid = record.pid;
|
|
2027
|
+
}
|
|
2028
|
+
if (typeof record.acquiredAt === "string") {
|
|
2029
|
+
holder.acquiredAt = record.acquiredAt;
|
|
2030
|
+
}
|
|
2031
|
+
return holder;
|
|
2032
|
+
}
|
|
2033
|
+
function makeWaitNotifier(path, fs, onWait, start) {
|
|
2034
|
+
let holder;
|
|
2035
|
+
let holderRead = false;
|
|
2036
|
+
let lastEmit;
|
|
2037
|
+
return async () => {
|
|
2038
|
+
if (!holderRead) {
|
|
2039
|
+
holderRead = true;
|
|
2040
|
+
holder = parseHolder(await fs.readFileBounded(path, MAX_LOCK_PAYLOAD_BYTES));
|
|
2041
|
+
}
|
|
2042
|
+
const elapsedMs = Date.now() - start;
|
|
2043
|
+
if (lastEmit !== void 0 && elapsedMs - lastEmit < WAIT_NOTICE_INTERVAL_MS) {
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
lastEmit = elapsedMs;
|
|
2047
|
+
onWait({ lockPath: path, elapsedMs, ...holder !== void 0 ? { holder } : {} });
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
async function acquireLock(path, fs, pollIntervalMs, deadline, notify) {
|
|
1787
2051
|
for (; ; ) {
|
|
1788
2052
|
if (await fs.createExclusive(path, lockPayload())) {
|
|
1789
2053
|
return;
|
|
@@ -1794,6 +2058,9 @@ async function acquireLock(path, fs, pollIntervalMs, deadline) {
|
|
|
1794
2058
|
`Could not acquire the write lock at ${path}: another process may be holding it. If no verbatra process is currently running, this lock file was likely left behind by one that was killed; delete it and retry.`
|
|
1795
2059
|
);
|
|
1796
2060
|
}
|
|
2061
|
+
if (notify !== void 0) {
|
|
2062
|
+
await notify();
|
|
2063
|
+
}
|
|
1797
2064
|
const jitter = Math.random() * pollIntervalMs;
|
|
1798
2065
|
await sleep(pollIntervalMs + jitter);
|
|
1799
2066
|
}
|
|
@@ -1801,7 +2068,9 @@ async function acquireLock(path, fs, pollIntervalMs, deadline) {
|
|
|
1801
2068
|
async function withFileLock(path, fs, fn, options) {
|
|
1802
2069
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1803
2070
|
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
1804
|
-
|
|
2071
|
+
const start = Date.now();
|
|
2072
|
+
const notify = options.onWait !== void 0 ? makeWaitNotifier(path, fs, options.onWait, start) : void 0;
|
|
2073
|
+
await acquireLock(path, fs, pollIntervalMs, start + acquireTimeoutMs, notify);
|
|
1805
2074
|
try {
|
|
1806
2075
|
return await fn();
|
|
1807
2076
|
} finally {
|
|
@@ -1817,8 +2086,8 @@ async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
|
1817
2086
|
|
|
1818
2087
|
// src/lock/lock-file.ts
|
|
1819
2088
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1820
|
-
var
|
|
1821
|
-
var EMPTY_LOCK = { version:
|
|
2089
|
+
var CURRENT_VERSION2 = 1;
|
|
2090
|
+
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
1822
2091
|
var MAX_LOCK_FILE_BYTES = 16 * 1024 * 1024;
|
|
1823
2092
|
var lockFileSchema = zod.z.object({
|
|
1824
2093
|
version: zod.z.number().int().positive(),
|
|
@@ -1847,10 +2116,10 @@ function parseLockFileRead(read, path) {
|
|
|
1847
2116
|
if (!result.success) {
|
|
1848
2117
|
throw new SdkError("LOCK_FILE_INVALID", `The lock-file at ${path} has an unexpected shape.`);
|
|
1849
2118
|
}
|
|
1850
|
-
if (result.data.version !==
|
|
2119
|
+
if (result.data.version !== CURRENT_VERSION2) {
|
|
1851
2120
|
throw new SdkError(
|
|
1852
2121
|
"LOCK_FILE_INVALID",
|
|
1853
|
-
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${
|
|
2122
|
+
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${CURRENT_VERSION2}.`
|
|
1854
2123
|
);
|
|
1855
2124
|
}
|
|
1856
2125
|
return result.data;
|
|
@@ -2640,7 +2909,7 @@ function createTreeFileAdapter(options) {
|
|
|
2640
2909
|
extensions,
|
|
2641
2910
|
sniff,
|
|
2642
2911
|
parse: parse3,
|
|
2643
|
-
serialize,
|
|
2912
|
+
serialize: serialize2,
|
|
2644
2913
|
deriveEntry,
|
|
2645
2914
|
extractPlaceholders,
|
|
2646
2915
|
computeInvalidIcuKeys,
|
|
@@ -2675,7 +2944,7 @@ function createTreeFileAdapter(options) {
|
|
|
2675
2944
|
},
|
|
2676
2945
|
async write(resource, filePath) {
|
|
2677
2946
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
2678
|
-
await atomicWriteFile(filePath,
|
|
2947
|
+
await atomicWriteFile(filePath, serialize2(tree));
|
|
2679
2948
|
}
|
|
2680
2949
|
};
|
|
2681
2950
|
}
|
|
@@ -2894,42 +3163,429 @@ async function detectStyle(filePath) {
|
|
|
2894
3163
|
if (outcome.kind !== "ok") {
|
|
2895
3164
|
return "nested";
|
|
2896
3165
|
}
|
|
2897
|
-
parsed = JSON.parse(outcome.content);
|
|
2898
|
-
} catch {
|
|
2899
|
-
return "nested";
|
|
3166
|
+
parsed = JSON.parse(outcome.content);
|
|
3167
|
+
} catch {
|
|
3168
|
+
return "nested";
|
|
3169
|
+
}
|
|
3170
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3171
|
+
return "nested";
|
|
3172
|
+
}
|
|
3173
|
+
for (const value of Object.values(parsed)) {
|
|
3174
|
+
if (typeof value === "object" && value !== null) {
|
|
3175
|
+
return "nested";
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
return "flat";
|
|
3179
|
+
}
|
|
3180
|
+
function buildFlatTree(entries) {
|
|
3181
|
+
const out = /* @__PURE__ */ new Map();
|
|
3182
|
+
for (const [key, entry] of entries) {
|
|
3183
|
+
out.set(key, entry.value);
|
|
3184
|
+
}
|
|
3185
|
+
return out;
|
|
3186
|
+
}
|
|
3187
|
+
async function buildNgxWriteTree(entries, filePath) {
|
|
3188
|
+
const style = await detectStyle(filePath);
|
|
3189
|
+
return style === "flat" ? buildFlatTree(entries) : unflattenEntries(entries);
|
|
3190
|
+
}
|
|
3191
|
+
function createNgxTranslateJsonAdapter() {
|
|
3192
|
+
return createJsonFileAdapter({
|
|
3193
|
+
format: "ngx-translate-json",
|
|
3194
|
+
extractPlaceholders: extractDoubleBracePlaceholders,
|
|
3195
|
+
deriveEntry: (_key, value) => ({
|
|
3196
|
+
placeholders: extractDoubleBracePlaceholders(value),
|
|
3197
|
+
isPlural: false
|
|
3198
|
+
}),
|
|
3199
|
+
validateTree: assertNotMixed,
|
|
3200
|
+
buildWriteTree: buildNgxWriteTree,
|
|
3201
|
+
keyMode: "path-notation"
|
|
3202
|
+
});
|
|
3203
|
+
}
|
|
3204
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
3205
|
+
try {
|
|
3206
|
+
return parseEntries(content, namespace);
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
function createFlatFileAdapter(options) {
|
|
3212
|
+
const {
|
|
3213
|
+
format,
|
|
3214
|
+
extensions,
|
|
3215
|
+
sniff,
|
|
3216
|
+
parseEntries,
|
|
3217
|
+
serializeEntries,
|
|
3218
|
+
extractPlaceholders,
|
|
3219
|
+
validateMessage,
|
|
3220
|
+
computeInvalidIcuKeys
|
|
3221
|
+
} = options;
|
|
3222
|
+
return {
|
|
3223
|
+
format,
|
|
3224
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
3225
|
+
extractPlaceholders,
|
|
3226
|
+
validateMessage: validateMessage ?? (() => true),
|
|
3227
|
+
async read(filePath, locale) {
|
|
3228
|
+
const content = await readFileContent(filePath);
|
|
3229
|
+
const namespace = namespaceOf(filePath);
|
|
3230
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
3231
|
+
const resource = { locale, namespace, format, entries };
|
|
3232
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3233
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3234
|
+
},
|
|
3235
|
+
async write(resource, filePath) {
|
|
3236
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
3237
|
+
await atomicWriteFile(filePath, data);
|
|
3238
|
+
}
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordinal", "choice"]);
|
|
3242
|
+
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3243
|
+
function matchingBraces(value) {
|
|
3244
|
+
const close = new Int32Array(value.length).fill(-1);
|
|
3245
|
+
const open3 = [];
|
|
3246
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
3247
|
+
const char = value[i];
|
|
3248
|
+
if (char === "{") {
|
|
3249
|
+
open3.push(i);
|
|
3250
|
+
} else if (char === "}") {
|
|
3251
|
+
const start = open3.pop();
|
|
3252
|
+
if (start !== void 0) {
|
|
3253
|
+
close[start] = i;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
return close;
|
|
3258
|
+
}
|
|
3259
|
+
function findNameEnd(value, from, limit) {
|
|
3260
|
+
for (let i = from; i < limit; i += 1) {
|
|
3261
|
+
const char = value[i];
|
|
3262
|
+
if (char === ",") {
|
|
3263
|
+
return { comma: i, hasBrace: false };
|
|
3264
|
+
}
|
|
3265
|
+
if (char === "{" || char === "}") {
|
|
3266
|
+
return { comma: -1, hasBrace: true };
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
return { comma: -1, hasBrace: false };
|
|
3270
|
+
}
|
|
3271
|
+
function parseArgumentAt(value, open3, close) {
|
|
3272
|
+
const { comma, hasBrace } = findNameEnd(value, open3 + 1, close);
|
|
3273
|
+
if (hasBrace) {
|
|
3274
|
+
return null;
|
|
3275
|
+
}
|
|
3276
|
+
const nameEnd = comma === -1 ? close : comma;
|
|
3277
|
+
const name = value.slice(open3 + 1, nameEnd).trim();
|
|
3278
|
+
if (!ARGUMENT_NAME.test(name)) {
|
|
3279
|
+
return null;
|
|
3280
|
+
}
|
|
3281
|
+
if (comma === -1) {
|
|
3282
|
+
return { name, type: null, styleStart: -1 };
|
|
3283
|
+
}
|
|
3284
|
+
const secondComma = value.indexOf(",", comma + 1);
|
|
3285
|
+
if (secondComma === -1 || secondComma >= close) {
|
|
3286
|
+
return { name, type: value.slice(comma + 1, close).trim(), styleStart: -1 };
|
|
3287
|
+
}
|
|
3288
|
+
return { name, type: value.slice(comma + 1, secondComma).trim(), styleStart: secondComma + 1 };
|
|
3289
|
+
}
|
|
3290
|
+
function canonicalToken(value, arg, close) {
|
|
3291
|
+
if (arg.type === null) {
|
|
3292
|
+
return `{${arg.name}}`;
|
|
3293
|
+
}
|
|
3294
|
+
if (arg.styleStart === -1) {
|
|
3295
|
+
return `{${arg.name},${arg.type}}`;
|
|
3296
|
+
}
|
|
3297
|
+
return `{${arg.name},${arg.type},${value.slice(arg.styleStart, close).trim()}}`;
|
|
3298
|
+
}
|
|
3299
|
+
function emitArgument(value, close, arg, closeIndex, out) {
|
|
3300
|
+
if (arg.type !== null && SUBMESSAGE_TYPES.has(arg.type)) {
|
|
3301
|
+
out.push(`{${arg.name},${arg.type}}`);
|
|
3302
|
+
if (arg.styleStart !== -1) {
|
|
3303
|
+
scanRange(value, close, arg.styleStart, closeIndex, false, out);
|
|
3304
|
+
}
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
out.push(canonicalToken(value, arg, closeIndex));
|
|
3308
|
+
}
|
|
3309
|
+
function scanRange(value, close, start, end, topLevel, out) {
|
|
3310
|
+
let i = start;
|
|
3311
|
+
while (i < end) {
|
|
3312
|
+
if (value[i] !== "{") {
|
|
3313
|
+
i += 1;
|
|
3314
|
+
continue;
|
|
3315
|
+
}
|
|
3316
|
+
if (topLevel && value[i + 1] === "{") {
|
|
3317
|
+
i += 2;
|
|
3318
|
+
continue;
|
|
3319
|
+
}
|
|
3320
|
+
const closeIndex = close[i] ?? -1;
|
|
3321
|
+
if (closeIndex === -1 || closeIndex >= end) {
|
|
3322
|
+
i += 1;
|
|
3323
|
+
continue;
|
|
3324
|
+
}
|
|
3325
|
+
const arg = parseArgumentAt(value, i, closeIndex);
|
|
3326
|
+
if (arg === null) {
|
|
3327
|
+
i += 1;
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
emitArgument(value, close, arg, closeIndex, out);
|
|
3331
|
+
i = closeIndex + 1;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function extractPropertiesPlaceholders(value) {
|
|
3335
|
+
const out = [];
|
|
3336
|
+
scanRange(value, matchingBraces(value), 0, value.length, true, out);
|
|
3337
|
+
return out;
|
|
3338
|
+
}
|
|
3339
|
+
var UNICODE_ESCAPE = /^[0-9a-fA-F]{4}$/;
|
|
3340
|
+
var LEADING_WHITESPACE = /^[ \t\f]+/;
|
|
3341
|
+
var TRAILING_TERMINATOR = /(?:\r\n|\r|\n)$/;
|
|
3342
|
+
function isPropertiesWhitespace(char) {
|
|
3343
|
+
return char === " " || char === " " || char === "\f";
|
|
3344
|
+
}
|
|
3345
|
+
function splitPhysicalLines(content) {
|
|
3346
|
+
if (content === "") {
|
|
3347
|
+
return [];
|
|
3348
|
+
}
|
|
3349
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
3350
|
+
if (TRAILING_TERMINATOR.test(content)) {
|
|
3351
|
+
lines.pop();
|
|
3352
|
+
}
|
|
3353
|
+
return lines;
|
|
3354
|
+
}
|
|
3355
|
+
function countTrailingBackslashes(line) {
|
|
3356
|
+
let count = 0;
|
|
3357
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) {
|
|
3358
|
+
count += 1;
|
|
3359
|
+
}
|
|
3360
|
+
return count;
|
|
3361
|
+
}
|
|
3362
|
+
function isContinued(line) {
|
|
3363
|
+
return countTrailingBackslashes(line) % 2 === 1;
|
|
3364
|
+
}
|
|
3365
|
+
function isBlankLine(line) {
|
|
3366
|
+
return line.trim() === "";
|
|
3367
|
+
}
|
|
3368
|
+
function isCommentLine(line) {
|
|
3369
|
+
const trimmed = line.replace(LEADING_WHITESPACE, "");
|
|
3370
|
+
return trimmed.startsWith("#") || trimmed.startsWith("!");
|
|
3371
|
+
}
|
|
3372
|
+
function joinContinuation(lines, start) {
|
|
3373
|
+
const pieces = [];
|
|
3374
|
+
let index = start;
|
|
3375
|
+
let current = lines[start] ?? "";
|
|
3376
|
+
while (isContinued(current) && index + 1 < lines.length) {
|
|
3377
|
+
pieces.push(current.slice(0, -1));
|
|
3378
|
+
index += 1;
|
|
3379
|
+
current = (lines[index] ?? "").replace(LEADING_WHITESPACE, "");
|
|
3380
|
+
}
|
|
3381
|
+
if (isContinued(current)) {
|
|
3382
|
+
current = current.slice(0, -1);
|
|
3383
|
+
}
|
|
3384
|
+
pieces.push(current);
|
|
3385
|
+
return { logical: pieces.join(""), nextIndex: index + 1 };
|
|
3386
|
+
}
|
|
3387
|
+
function decodeSimpleEscape(char) {
|
|
3388
|
+
switch (char) {
|
|
3389
|
+
case "t":
|
|
3390
|
+
return " ";
|
|
3391
|
+
case "n":
|
|
3392
|
+
return "\n";
|
|
3393
|
+
case "r":
|
|
3394
|
+
return "\r";
|
|
3395
|
+
case "f":
|
|
3396
|
+
return "\f";
|
|
3397
|
+
default:
|
|
3398
|
+
return char;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
function decodeUnicodeEscape(raw, at) {
|
|
3402
|
+
const hex = raw.slice(at, at + 4);
|
|
3403
|
+
if (!UNICODE_ESCAPE.test(hex)) {
|
|
3404
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file has a malformed unicode escape.");
|
|
3405
|
+
}
|
|
3406
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
3407
|
+
}
|
|
3408
|
+
function decodeEscapes(raw) {
|
|
3409
|
+
let out = "";
|
|
3410
|
+
let i = 0;
|
|
3411
|
+
while (i < raw.length) {
|
|
3412
|
+
const char = raw[i];
|
|
3413
|
+
if (char !== "\\") {
|
|
3414
|
+
out += char;
|
|
3415
|
+
i += 1;
|
|
3416
|
+
continue;
|
|
3417
|
+
}
|
|
3418
|
+
const next = raw[i + 1];
|
|
3419
|
+
if (next === void 0) {
|
|
3420
|
+
break;
|
|
3421
|
+
}
|
|
3422
|
+
if (next === "u") {
|
|
3423
|
+
out += decodeUnicodeEscape(raw, i + 2);
|
|
3424
|
+
i += 6;
|
|
3425
|
+
continue;
|
|
3426
|
+
}
|
|
3427
|
+
out += decodeSimpleEscape(next);
|
|
3428
|
+
i += 2;
|
|
3429
|
+
}
|
|
3430
|
+
return out;
|
|
3431
|
+
}
|
|
3432
|
+
function keyEndIndex(logical, from) {
|
|
3433
|
+
let i = from;
|
|
3434
|
+
while (i < logical.length) {
|
|
3435
|
+
const char = logical[i];
|
|
3436
|
+
if (char === "\\") {
|
|
3437
|
+
i += 2;
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
if (char === void 0 || isPropertiesWhitespace(char) || char === "=" || char === ":") {
|
|
3441
|
+
break;
|
|
3442
|
+
}
|
|
3443
|
+
i += 1;
|
|
3444
|
+
}
|
|
3445
|
+
return i;
|
|
3446
|
+
}
|
|
3447
|
+
function skipWhitespace(logical, from) {
|
|
3448
|
+
let i = from;
|
|
3449
|
+
while (i < logical.length) {
|
|
3450
|
+
const char = logical[i];
|
|
3451
|
+
if (char === void 0 || !isPropertiesWhitespace(char)) {
|
|
3452
|
+
break;
|
|
3453
|
+
}
|
|
3454
|
+
i += 1;
|
|
3455
|
+
}
|
|
3456
|
+
return i;
|
|
3457
|
+
}
|
|
3458
|
+
function parseEntryLine(logical) {
|
|
3459
|
+
const keyStart = skipWhitespace(logical, 0);
|
|
3460
|
+
const keyEnd = keyEndIndex(logical, keyStart);
|
|
3461
|
+
const afterKey = skipWhitespace(logical, keyEnd);
|
|
3462
|
+
const separator = logical[afterKey];
|
|
3463
|
+
const valueStart = separator === "=" || separator === ":" ? skipWhitespace(logical, afterKey + 1) : afterKey;
|
|
3464
|
+
return {
|
|
3465
|
+
key: decodeEscapes(logical.slice(keyStart, keyEnd)),
|
|
3466
|
+
value: decodeEscapes(logical.slice(valueStart))
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
function parseItems(content) {
|
|
3470
|
+
const lines = splitPhysicalLines(content);
|
|
3471
|
+
const items = [];
|
|
3472
|
+
let i = 0;
|
|
3473
|
+
while (i < lines.length) {
|
|
3474
|
+
const line = lines[i] ?? "";
|
|
3475
|
+
if (isBlankLine(line) || isCommentLine(line)) {
|
|
3476
|
+
items.push({ kind: "raw", text: line });
|
|
3477
|
+
i += 1;
|
|
3478
|
+
continue;
|
|
3479
|
+
}
|
|
3480
|
+
const { logical, nextIndex } = joinContinuation(lines, i);
|
|
3481
|
+
items.push({ kind: "entry", ...parseEntryLine(logical) });
|
|
3482
|
+
i = nextIndex;
|
|
3483
|
+
}
|
|
3484
|
+
return items;
|
|
3485
|
+
}
|
|
3486
|
+
function parsePropertiesEntries(content, namespace) {
|
|
3487
|
+
const map = /* @__PURE__ */ new Map();
|
|
3488
|
+
for (const item of parseItems(content)) {
|
|
3489
|
+
if (item.kind === "entry") {
|
|
3490
|
+
map.set(item.key, {
|
|
3491
|
+
key: item.key,
|
|
3492
|
+
namespace,
|
|
3493
|
+
value: item.value,
|
|
3494
|
+
placeholders: extractPropertiesPlaceholders(item.value),
|
|
3495
|
+
isPlural: false
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
return map;
|
|
3500
|
+
}
|
|
3501
|
+
function unicodeEscape(code) {
|
|
3502
|
+
return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3503
|
+
}
|
|
3504
|
+
function escapeChar(char, isFirst, isKey) {
|
|
3505
|
+
switch (char) {
|
|
3506
|
+
case "\\":
|
|
3507
|
+
return "\\\\";
|
|
3508
|
+
case " ":
|
|
3509
|
+
return "\\t";
|
|
3510
|
+
case "\n":
|
|
3511
|
+
return "\\n";
|
|
3512
|
+
case "\r":
|
|
3513
|
+
return "\\r";
|
|
3514
|
+
case "\f":
|
|
3515
|
+
return "\\f";
|
|
3516
|
+
case " ":
|
|
3517
|
+
return isKey || isFirst ? "\\ " : " ";
|
|
3518
|
+
}
|
|
3519
|
+
if (isKey && (char === "=" || char === ":" || char === "#" || char === "!")) {
|
|
3520
|
+
return `\\${char}`;
|
|
3521
|
+
}
|
|
3522
|
+
const code = char.charCodeAt(0);
|
|
3523
|
+
return code < 32 || code > 126 ? unicodeEscape(code) : char;
|
|
3524
|
+
}
|
|
3525
|
+
function escapeString(input, isKey) {
|
|
3526
|
+
let out = "";
|
|
3527
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
3528
|
+
const char = input[i];
|
|
3529
|
+
if (char !== void 0) {
|
|
3530
|
+
out += escapeChar(char, i === 0, isKey);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return out;
|
|
3534
|
+
}
|
|
3535
|
+
function formatEntry(key, value) {
|
|
3536
|
+
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3537
|
+
}
|
|
3538
|
+
function isFileNotFound(error) {
|
|
3539
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3540
|
+
}
|
|
3541
|
+
async function readStructure(filePath) {
|
|
3542
|
+
let outcome;
|
|
3543
|
+
try {
|
|
3544
|
+
outcome = await readBounded2(filePath);
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
if (isFileNotFound(error)) {
|
|
3547
|
+
return [];
|
|
3548
|
+
}
|
|
3549
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination file could not be read.");
|
|
2900
3550
|
}
|
|
2901
|
-
if (
|
|
2902
|
-
|
|
3551
|
+
if (outcome.kind === "not-a-file") {
|
|
3552
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
2903
3553
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
return "nested";
|
|
2907
|
-
}
|
|
3554
|
+
if (outcome.kind === "too-large") {
|
|
3555
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
2908
3556
|
}
|
|
2909
|
-
return
|
|
3557
|
+
return parseItems(outcome.content);
|
|
2910
3558
|
}
|
|
2911
|
-
function
|
|
2912
|
-
const
|
|
3559
|
+
async function serializePropertiesEntries(entries, filePath) {
|
|
3560
|
+
const structure = await readStructure(filePath);
|
|
3561
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3562
|
+
const lines = [];
|
|
3563
|
+
for (const item of structure) {
|
|
3564
|
+
if (item.kind === "raw") {
|
|
3565
|
+
lines.push(item.text);
|
|
3566
|
+
continue;
|
|
3567
|
+
}
|
|
3568
|
+
const entry = entries.get(item.key);
|
|
3569
|
+
if (entry !== void 0 && !emitted.has(item.key)) {
|
|
3570
|
+
lines.push(formatEntry(item.key, entry.value));
|
|
3571
|
+
emitted.add(item.key);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
2913
3574
|
for (const [key, entry] of entries) {
|
|
2914
|
-
|
|
3575
|
+
if (!emitted.has(key)) {
|
|
3576
|
+
lines.push(formatEntry(key, entry.value));
|
|
3577
|
+
}
|
|
2915
3578
|
}
|
|
2916
|
-
return
|
|
2917
|
-
|
|
2918
|
-
async function buildNgxWriteTree(entries, filePath) {
|
|
2919
|
-
const style = await detectStyle(filePath);
|
|
2920
|
-
return style === "flat" ? buildFlatTree(entries) : unflattenEntries(entries);
|
|
3579
|
+
return lines.length === 0 ? "" : `${lines.join("\n")}
|
|
3580
|
+
`;
|
|
2921
3581
|
}
|
|
2922
|
-
function
|
|
2923
|
-
return
|
|
2924
|
-
format: "
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
}),
|
|
2930
|
-
validateTree: assertNotMixed,
|
|
2931
|
-
buildWriteTree: buildNgxWriteTree,
|
|
2932
|
-
keyMode: "path-notation"
|
|
3582
|
+
function createPropertiesAdapter() {
|
|
3583
|
+
return createFlatFileAdapter({
|
|
3584
|
+
format: "properties",
|
|
3585
|
+
extensions: [".properties"],
|
|
3586
|
+
parseEntries: parsePropertiesEntries,
|
|
3587
|
+
serializeEntries: serializePropertiesEntries,
|
|
3588
|
+
extractPlaceholders: extractPropertiesPlaceholders
|
|
2933
3589
|
});
|
|
2934
3590
|
}
|
|
2935
3591
|
var AdapterRegistry = class {
|
|
@@ -3009,43 +3665,6 @@ function createVueI18nJsonAdapter() {
|
|
|
3009
3665
|
})
|
|
3010
3666
|
});
|
|
3011
3667
|
}
|
|
3012
|
-
function toEntries2(content, namespace, parseEntries) {
|
|
3013
|
-
try {
|
|
3014
|
-
return parseEntries(content, namespace);
|
|
3015
|
-
} catch (error) {
|
|
3016
|
-
rethrowStructured(error, "The file could not be parsed.");
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
function createFlatFileAdapter(options) {
|
|
3020
|
-
const {
|
|
3021
|
-
format,
|
|
3022
|
-
extensions,
|
|
3023
|
-
sniff,
|
|
3024
|
-
parseEntries,
|
|
3025
|
-
serializeEntries,
|
|
3026
|
-
extractPlaceholders,
|
|
3027
|
-
validateMessage,
|
|
3028
|
-
computeInvalidIcuKeys
|
|
3029
|
-
} = options;
|
|
3030
|
-
return {
|
|
3031
|
-
format,
|
|
3032
|
-
canHandle: buildCanHandle(extensions, sniff),
|
|
3033
|
-
extractPlaceholders,
|
|
3034
|
-
validateMessage: validateMessage ?? (() => true),
|
|
3035
|
-
async read(filePath, locale) {
|
|
3036
|
-
const content = await readFileContent(filePath);
|
|
3037
|
-
const namespace = namespaceOf(filePath);
|
|
3038
|
-
const entries = toEntries2(content, namespace, parseEntries);
|
|
3039
|
-
const resource = { locale, namespace, format, entries };
|
|
3040
|
-
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3041
|
-
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3042
|
-
},
|
|
3043
|
-
async write(resource, filePath) {
|
|
3044
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3045
|
-
await atomicWriteFile(filePath, data);
|
|
3046
|
-
}
|
|
3047
|
-
};
|
|
3048
|
-
}
|
|
3049
3668
|
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
3050
3669
|
function extractXliffPlaceholders(value) {
|
|
3051
3670
|
const result = [];
|
|
@@ -3356,7 +3975,7 @@ function createYamlAdapter() {
|
|
|
3356
3975
|
});
|
|
3357
3976
|
}
|
|
3358
3977
|
function createDefaultRegistry() {
|
|
3359
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
3978
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter()).register(createPropertiesAdapter());
|
|
3360
3979
|
}
|
|
3361
3980
|
|
|
3362
3981
|
// src/selection/select-adapter.ts
|
|
@@ -3477,6 +4096,28 @@ async function diff(input, deps = {}) {
|
|
|
3477
4096
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3478
4097
|
}
|
|
3479
4098
|
|
|
4099
|
+
// src/cache/fingerprint.ts
|
|
4100
|
+
function fingerprintModel(provider) {
|
|
4101
|
+
const options = provider.options;
|
|
4102
|
+
const model = options.model;
|
|
4103
|
+
return typeof model === "string" ? model : null;
|
|
4104
|
+
}
|
|
4105
|
+
function sortGlossary(glossary) {
|
|
4106
|
+
if (glossary === void 0) {
|
|
4107
|
+
return {};
|
|
4108
|
+
}
|
|
4109
|
+
return Object.fromEntries(Object.entries(glossary).sort(([a], [b]) => a < b ? -1 : 1));
|
|
4110
|
+
}
|
|
4111
|
+
function computeFingerprint(config) {
|
|
4112
|
+
const canonical = JSON.stringify({
|
|
4113
|
+
provider: config.provider.id,
|
|
4114
|
+
model: fingerprintModel(config.provider),
|
|
4115
|
+
tone: config.tone ?? null,
|
|
4116
|
+
glossary: sortGlossary(config.glossary)
|
|
4117
|
+
});
|
|
4118
|
+
return stableStringHash(canonical);
|
|
4119
|
+
}
|
|
4120
|
+
|
|
3480
4121
|
// src/flow/integrity-gate.ts
|
|
3481
4122
|
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3482
4123
|
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
@@ -3486,6 +4127,9 @@ function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
|
3486
4127
|
if (!adapter.validateMessage(candidateValue)) {
|
|
3487
4128
|
return { accepted: false, reason: "icu" };
|
|
3488
4129
|
}
|
|
4130
|
+
if (assessValueDegeneracy(sourceEntry.value, candidateValue).degenerate) {
|
|
4131
|
+
return { accepted: false, reason: "degenerate" };
|
|
4132
|
+
}
|
|
3489
4133
|
return { accepted: true };
|
|
3490
4134
|
}
|
|
3491
4135
|
|
|
@@ -3524,6 +4168,12 @@ async function editEntry(input, deps = {}) {
|
|
|
3524
4168
|
mode: "merge",
|
|
3525
4169
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3526
4170
|
});
|
|
4171
|
+
await feedTranslationMemory(
|
|
4172
|
+
cwd,
|
|
4173
|
+
fs,
|
|
4174
|
+
computeFingerprint(config),
|
|
4175
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: input.value }]])
|
|
4176
|
+
);
|
|
3527
4177
|
return { accepted: true, value: input.value };
|
|
3528
4178
|
});
|
|
3529
4179
|
}
|
|
@@ -3741,13 +4391,19 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
3741
4391
|
mode: "merge",
|
|
3742
4392
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3743
4393
|
});
|
|
4394
|
+
await feedTranslationMemory(
|
|
4395
|
+
cwd,
|
|
4396
|
+
fs,
|
|
4397
|
+
computeFingerprint(config),
|
|
4398
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: value }]])
|
|
4399
|
+
);
|
|
3744
4400
|
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3745
4401
|
return { accepted: true, value, reviewReasons };
|
|
3746
4402
|
});
|
|
3747
4403
|
}
|
|
3748
4404
|
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3749
4405
|
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3750
|
-
var
|
|
4406
|
+
var CURRENT_VERSION3 = 1;
|
|
3751
4407
|
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3752
4408
|
var reviewReasonCodeSchema = zod.z.enum([
|
|
3753
4409
|
"LENGTH_RATIO_OUTLIER",
|
|
@@ -3773,7 +4429,7 @@ var runBudgetSchema = zod.z.object({
|
|
|
3773
4429
|
});
|
|
3774
4430
|
var runStatusLocaleSchema = zod.z.object({
|
|
3775
4431
|
locale: zod.z.string(),
|
|
3776
|
-
status: zod.z.enum(["succeeded", "failed"]),
|
|
4432
|
+
status: zod.z.enum(["succeeded", "partial", "failed"]),
|
|
3777
4433
|
needsReview: zod.z.array(needsReviewEntrySchema),
|
|
3778
4434
|
usage: usageSummarySchema.optional()
|
|
3779
4435
|
});
|
|
@@ -3797,7 +4453,7 @@ function toRunStatusLocale(locale) {
|
|
|
3797
4453
|
}
|
|
3798
4454
|
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3799
4455
|
return {
|
|
3800
|
-
version:
|
|
4456
|
+
version: CURRENT_VERSION3,
|
|
3801
4457
|
generatedAt,
|
|
3802
4458
|
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3803
4459
|
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
@@ -3830,7 +4486,7 @@ async function readRunStatusFile(path, fs) {
|
|
|
3830
4486
|
return void 0;
|
|
3831
4487
|
}
|
|
3832
4488
|
const result = runStatusFileSchema.safeParse(parsed);
|
|
3833
|
-
if (!result.success || result.data.version !==
|
|
4489
|
+
if (!result.success || result.data.version !== CURRENT_VERSION3) {
|
|
3834
4490
|
return void 0;
|
|
3835
4491
|
}
|
|
3836
4492
|
return fromParsed(result.data);
|
|
@@ -3909,19 +4565,34 @@ function failureSummary(locale, error) {
|
|
|
3909
4565
|
orphaned: [],
|
|
3910
4566
|
pruned: [],
|
|
3911
4567
|
invalidIcuSource: [],
|
|
4568
|
+
cacheHits: [],
|
|
3912
4569
|
integrityMismatches: [],
|
|
3913
4570
|
providerFailures: [],
|
|
3914
4571
|
budgetWithheld: [],
|
|
3915
4572
|
generated: [],
|
|
3916
4573
|
notices: [],
|
|
3917
4574
|
needsReview: [],
|
|
4575
|
+
unfilled: [],
|
|
4576
|
+
malformedRows: [],
|
|
4577
|
+
duplicateKeys: [],
|
|
3918
4578
|
error: describeError(error)
|
|
3919
4579
|
};
|
|
3920
4580
|
}
|
|
4581
|
+
function deriveLocaleStatus(parts) {
|
|
4582
|
+
const withheld = parts.integrityMismatches.length > 0 || parts.providerFailures.length > 0 || parts.budgetWithheld.length > 0;
|
|
4583
|
+
if (!withheld) {
|
|
4584
|
+
return "succeeded";
|
|
4585
|
+
}
|
|
4586
|
+
const accepted = parts.translated.length > 0 || parts.cacheHits.length > 0 || parts.generated.length > 0;
|
|
4587
|
+
return accepted ? "partial" : "failed";
|
|
4588
|
+
}
|
|
3921
4589
|
function partition(locales) {
|
|
3922
|
-
const
|
|
3923
|
-
|
|
3924
|
-
|
|
4590
|
+
const namesWith = (status) => locales.filter((s) => s.status === status).map((s) => s.locale);
|
|
4591
|
+
return {
|
|
4592
|
+
succeeded: namesWith("succeeded"),
|
|
4593
|
+
partial: namesWith("partial"),
|
|
4594
|
+
failed: namesWith("failed")
|
|
4595
|
+
};
|
|
3925
4596
|
}
|
|
3926
4597
|
|
|
3927
4598
|
// src/flow/batching.ts
|
|
@@ -4232,6 +4903,97 @@ function foldGenerationItem(item, result, adapter, accepted, withheld, providerF
|
|
|
4232
4903
|
}
|
|
4233
4904
|
|
|
4234
4905
|
// src/flow/locale-run.ts
|
|
4906
|
+
function partitionCacheHits(params, toTranslate) {
|
|
4907
|
+
const cache = params.cache;
|
|
4908
|
+
const hits = /* @__PURE__ */ new Map();
|
|
4909
|
+
if (cache === void 0) {
|
|
4910
|
+
return { hits, misses: toTranslate };
|
|
4911
|
+
}
|
|
4912
|
+
const misses = [];
|
|
4913
|
+
for (const key of toTranslate) {
|
|
4914
|
+
const source = params.source.entries.get(key);
|
|
4915
|
+
if (source === void 0) {
|
|
4916
|
+
continue;
|
|
4917
|
+
}
|
|
4918
|
+
const cached = lookupMemory(
|
|
4919
|
+
cache.snapshot,
|
|
4920
|
+
cache.fingerprint,
|
|
4921
|
+
params.targetLocale,
|
|
4922
|
+
contentHash(source)
|
|
4923
|
+
);
|
|
4924
|
+
if (cached !== void 0 && gateCandidateValue(source, cached, params.adapter).accepted) {
|
|
4925
|
+
hits.set(key, { value: cached, source });
|
|
4926
|
+
} else {
|
|
4927
|
+
misses.push(key);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
return { hits, misses };
|
|
4931
|
+
}
|
|
4932
|
+
function collectCacheAdditions(params, accepted, cacheHitKeys) {
|
|
4933
|
+
if (params.cache === void 0) {
|
|
4934
|
+
return [];
|
|
4935
|
+
}
|
|
4936
|
+
const additions = [];
|
|
4937
|
+
for (const [key, entry] of accepted) {
|
|
4938
|
+
if (!cacheHitKeys.has(key)) {
|
|
4939
|
+
additions.push({ contentHash: contentHash(entry.source), value: entry.value });
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
return additions;
|
|
4943
|
+
}
|
|
4944
|
+
function groupMissesByContent(params, misses) {
|
|
4945
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4946
|
+
for (const key of misses) {
|
|
4947
|
+
const source = params.source.entries.get(key);
|
|
4948
|
+
if (source === void 0) {
|
|
4949
|
+
continue;
|
|
4950
|
+
}
|
|
4951
|
+
const existing = byHash.get(contentHash(source));
|
|
4952
|
+
if (existing === void 0) {
|
|
4953
|
+
byHash.set(contentHash(source), { representative: key, duplicates: [] });
|
|
4954
|
+
} else {
|
|
4955
|
+
existing.duplicates.push(key);
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
return [...byHash.values()];
|
|
4959
|
+
}
|
|
4960
|
+
function fanOutContentDuplicates(params, groups, outcome) {
|
|
4961
|
+
for (const group of groups) {
|
|
4962
|
+
if (group.duplicates.length > 0) {
|
|
4963
|
+
applyGroupOutcome(params, group, outcome);
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
function applyGroupOutcome(params, group, outcome) {
|
|
4968
|
+
const acceptedRepresentative = outcome.accepted.get(group.representative);
|
|
4969
|
+
if (acceptedRepresentative !== void 0) {
|
|
4970
|
+
fanOutAccepted(params, group, acceptedRepresentative, outcome);
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
withheldBucketFor(group.representative, outcome).push(...group.duplicates);
|
|
4974
|
+
}
|
|
4975
|
+
function fanOutAccepted(params, group, acceptedRepresentative, outcome) {
|
|
4976
|
+
const flag = outcome.reviewFlags.get(group.representative);
|
|
4977
|
+
for (const key of group.duplicates) {
|
|
4978
|
+
const source = params.source.entries.get(key);
|
|
4979
|
+
if (source === void 0) {
|
|
4980
|
+
continue;
|
|
4981
|
+
}
|
|
4982
|
+
outcome.accepted.set(key, { value: acceptedRepresentative.value, source });
|
|
4983
|
+
if (flag !== void 0) {
|
|
4984
|
+
outcome.reviewFlags.set(key, flag);
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
function withheldBucketFor(representative, outcome) {
|
|
4989
|
+
if (outcome.integrityMismatches.includes(representative)) {
|
|
4990
|
+
return outcome.integrityMismatches;
|
|
4991
|
+
}
|
|
4992
|
+
if (outcome.budgetWithheld.includes(representative)) {
|
|
4993
|
+
return outcome.budgetWithheld;
|
|
4994
|
+
}
|
|
4995
|
+
return outcome.providerFailures;
|
|
4996
|
+
}
|
|
4235
4997
|
function buildRequest3(params, entries) {
|
|
4236
4998
|
return {
|
|
4237
4999
|
sourceLocale: params.sourceLocale,
|
|
@@ -4274,6 +5036,7 @@ async function runLocale(params) {
|
|
|
4274
5036
|
orphaned,
|
|
4275
5037
|
invalidIcuSource,
|
|
4276
5038
|
translated: toTranslate,
|
|
5039
|
+
cacheHits: [],
|
|
4277
5040
|
generated: [],
|
|
4278
5041
|
integrityMismatches: [],
|
|
4279
5042
|
providerFailures: [],
|
|
@@ -4281,12 +5044,16 @@ async function runLocale(params) {
|
|
|
4281
5044
|
pruned,
|
|
4282
5045
|
notices: sdkNotices
|
|
4283
5046
|
}),
|
|
4284
|
-
lockEntries: {}
|
|
5047
|
+
lockEntries: {},
|
|
5048
|
+
cacheAdditions: []
|
|
4285
5049
|
};
|
|
4286
5050
|
}
|
|
4287
|
-
const
|
|
5051
|
+
const partition2 = partitionCacheHits(params, toTranslate);
|
|
5052
|
+
const cacheHitKeys = new Set(partition2.hits.keys());
|
|
5053
|
+
const missGroups = groupMissesByContent(params, partition2.misses);
|
|
5054
|
+
const entries = missGroups.map((group) => params.source.entries.get(group.representative)).filter((entry) => entry !== void 0);
|
|
4288
5055
|
const startedStopped = params.budget.stopped;
|
|
4289
|
-
const accepted =
|
|
5056
|
+
const accepted = new Map(partition2.hits);
|
|
4290
5057
|
const integrityMismatches = [];
|
|
4291
5058
|
const providerFailures = [];
|
|
4292
5059
|
const budgetWithheld = [];
|
|
@@ -4301,6 +5068,13 @@ async function runLocale(params) {
|
|
|
4301
5068
|
budgetWithheld,
|
|
4302
5069
|
reviewFlags
|
|
4303
5070
|
);
|
|
5071
|
+
fanOutContentDuplicates(params, missGroups, {
|
|
5072
|
+
accepted,
|
|
5073
|
+
integrityMismatches,
|
|
5074
|
+
providerFailures,
|
|
5075
|
+
budgetWithheld,
|
|
5076
|
+
reviewFlags
|
|
5077
|
+
});
|
|
4304
5078
|
const merged = new Map(target.entries);
|
|
4305
5079
|
for (const key of pruned) {
|
|
4306
5080
|
merged.delete(key);
|
|
@@ -4347,7 +5121,8 @@ async function runLocale(params) {
|
|
|
4347
5121
|
unchanged: diff2.unchanged,
|
|
4348
5122
|
orphaned,
|
|
4349
5123
|
invalidIcuSource,
|
|
4350
|
-
translated: [...accepted.keys()],
|
|
5124
|
+
translated: [...accepted.keys()].filter((key) => !cacheHitKeys.has(key)),
|
|
5125
|
+
cacheHits: [...cacheHitKeys].sort(),
|
|
4351
5126
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
4352
5127
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
4353
5128
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
@@ -4357,7 +5132,8 @@ async function runLocale(params) {
|
|
|
4357
5132
|
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
4358
5133
|
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
4359
5134
|
}),
|
|
4360
|
-
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
5135
|
+
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted),
|
|
5136
|
+
cacheAdditions: collectCacheAdditions(params, accepted, cacheHitKeys)
|
|
4361
5137
|
};
|
|
4362
5138
|
}
|
|
4363
5139
|
var NO_GENERATION_RESULT = {
|
|
@@ -4402,18 +5178,22 @@ function pluralNoticeFor(params, merged) {
|
|
|
4402
5178
|
function baseSummary(parts) {
|
|
4403
5179
|
return {
|
|
4404
5180
|
locale: parts.locale,
|
|
4405
|
-
status:
|
|
5181
|
+
status: deriveLocaleStatus(parts),
|
|
4406
5182
|
translated: parts.translated,
|
|
4407
5183
|
unchanged: parts.unchanged,
|
|
4408
5184
|
orphaned: parts.orphaned,
|
|
4409
5185
|
pruned: parts.pruned,
|
|
4410
5186
|
invalidIcuSource: parts.invalidIcuSource,
|
|
5187
|
+
cacheHits: parts.cacheHits,
|
|
4411
5188
|
integrityMismatches: parts.integrityMismatches,
|
|
4412
5189
|
providerFailures: parts.providerFailures,
|
|
4413
5190
|
budgetWithheld: parts.budgetWithheld,
|
|
4414
5191
|
generated: parts.generated,
|
|
4415
5192
|
notices: parts.notices,
|
|
4416
5193
|
needsReview: parts.needsReview ?? [],
|
|
5194
|
+
unfilled: [],
|
|
5195
|
+
malformedRows: [],
|
|
5196
|
+
duplicateKeys: [],
|
|
4417
5197
|
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
4418
5198
|
};
|
|
4419
5199
|
}
|
|
@@ -4431,7 +5211,16 @@ async function translateAndCheck(provider, params, entries, accepted, integrityM
|
|
|
4431
5211
|
const notices = [];
|
|
4432
5212
|
const usage = createUsageAccumulator();
|
|
4433
5213
|
let tripped = false;
|
|
4434
|
-
|
|
5214
|
+
const batches = chunk(entries, params.maxBatchSize);
|
|
5215
|
+
let batchIndex = 0;
|
|
5216
|
+
for (const batch of batches) {
|
|
5217
|
+
batchIndex += 1;
|
|
5218
|
+
params.onProgress?.({
|
|
5219
|
+
type: "sub-batch",
|
|
5220
|
+
locale: params.targetLocale,
|
|
5221
|
+
batchIndex,
|
|
5222
|
+
totalBatches: batches.length
|
|
5223
|
+
});
|
|
4435
5224
|
if (params.budget.stopped) {
|
|
4436
5225
|
for (const entry of batch) {
|
|
4437
5226
|
budgetWithheld.push(entry.key);
|
|
@@ -4461,10 +5250,16 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
4461
5250
|
try {
|
|
4462
5251
|
result = await provider.translateBatch(buildRequest3(params, batch));
|
|
4463
5252
|
} catch (error) {
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
5253
|
+
return handleSubBatchFailure(
|
|
5254
|
+
error,
|
|
5255
|
+
provider,
|
|
5256
|
+
params,
|
|
5257
|
+
batch,
|
|
5258
|
+
accepted,
|
|
5259
|
+
integrityMismatches,
|
|
5260
|
+
providerFailures,
|
|
5261
|
+
reviewFlags
|
|
5262
|
+
);
|
|
4468
5263
|
}
|
|
4469
5264
|
for (const entry of batch) {
|
|
4470
5265
|
foldEntryResult(entry, result, params.adapter, accepted, integrityMismatches, providerFailures);
|
|
@@ -4476,6 +5271,44 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
4476
5271
|
}
|
|
4477
5272
|
return { notices: readNotices(result), usage: result.usage };
|
|
4478
5273
|
}
|
|
5274
|
+
function isOutputTruncated(error) {
|
|
5275
|
+
return error instanceof ProviderError && error.code === "OUTPUT_TRUNCATED";
|
|
5276
|
+
}
|
|
5277
|
+
async function handleSubBatchFailure(error, provider, params, batch, accepted, integrityMismatches, providerFailures, reviewFlags) {
|
|
5278
|
+
if (isOutputTruncated(error) && batch.length > 1) {
|
|
5279
|
+
return retryTruncatedSplit(
|
|
5280
|
+
provider,
|
|
5281
|
+
params,
|
|
5282
|
+
batch,
|
|
5283
|
+
accepted,
|
|
5284
|
+
integrityMismatches,
|
|
5285
|
+
providerFailures,
|
|
5286
|
+
reviewFlags
|
|
5287
|
+
);
|
|
5288
|
+
}
|
|
5289
|
+
for (const entry of batch) {
|
|
5290
|
+
providerFailures.push(entry.key);
|
|
5291
|
+
}
|
|
5292
|
+
return { notices: [subBatchFailedNotice(batch.length, error)], usage: void 0 };
|
|
5293
|
+
}
|
|
5294
|
+
async function retryTruncatedSplit(provider, params, batch, accepted, integrityMismatches, providerFailures, reviewFlags) {
|
|
5295
|
+
const notices = [];
|
|
5296
|
+
let usage;
|
|
5297
|
+
for (const half of chunk(batch, Math.ceil(batch.length / 2))) {
|
|
5298
|
+
const sub = await runSubBatch(
|
|
5299
|
+
provider,
|
|
5300
|
+
params,
|
|
5301
|
+
half,
|
|
5302
|
+
accepted,
|
|
5303
|
+
integrityMismatches,
|
|
5304
|
+
providerFailures,
|
|
5305
|
+
reviewFlags
|
|
5306
|
+
);
|
|
5307
|
+
notices.push(...sub.notices);
|
|
5308
|
+
usage = combineUsage(usage, sub.usage);
|
|
5309
|
+
}
|
|
5310
|
+
return { notices, usage };
|
|
5311
|
+
}
|
|
4479
5312
|
function foldEntryResult(entry, result, adapter, accepted, integrityMismatches, providerFailures) {
|
|
4480
5313
|
const value = result.values.get(entry.key);
|
|
4481
5314
|
if (value === void 0) {
|
|
@@ -4533,6 +5366,26 @@ async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
|
4533
5366
|
} catch {
|
|
4534
5367
|
}
|
|
4535
5368
|
}
|
|
5369
|
+
async function createRunCacheState(input, config, cwd, dryRun, fs) {
|
|
5370
|
+
if (dryRun || input.cache === false) {
|
|
5371
|
+
return void 0;
|
|
5372
|
+
}
|
|
5373
|
+
return {
|
|
5374
|
+
memory: await readTranslationMemory(cacheFilePath(cwd), fs),
|
|
5375
|
+
fingerprint: computeFingerprint(config),
|
|
5376
|
+
additions: /* @__PURE__ */ new Map()
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
async function recordCacheAdditions(cwd, cache, fs) {
|
|
5380
|
+
if (cache === void 0 || cache.additions.size === 0) {
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
try {
|
|
5384
|
+
const merged = applyAdditions(cache.memory, cache.fingerprint, cache.additions);
|
|
5385
|
+
await writeTranslationMemory(cacheFilePath(cwd), merged, fs);
|
|
5386
|
+
} catch {
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
4536
5389
|
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4537
5390
|
return {
|
|
4538
5391
|
source: context.source.resource,
|
|
@@ -4551,7 +5404,9 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
4551
5404
|
generatePlurals: context.generatePlurals,
|
|
4552
5405
|
maxBatchSize: context.maxBatchSize,
|
|
4553
5406
|
fs: context.fs,
|
|
4554
|
-
budget: context.budget
|
|
5407
|
+
budget: context.budget,
|
|
5408
|
+
...context.cache !== void 0 ? { cache: { snapshot: context.cache.memory, fingerprint: context.cache.fingerprint } } : {},
|
|
5409
|
+
...context.onProgress !== void 0 ? { onProgress: context.onProgress } : {}
|
|
4555
5410
|
};
|
|
4556
5411
|
}
|
|
4557
5412
|
async function runDryLocale(context, targetLocale, lock) {
|
|
@@ -4559,16 +5414,29 @@ async function runDryLocale(context, targetLocale, lock) {
|
|
|
4559
5414
|
return (await runLocale(params)).summary;
|
|
4560
5415
|
}
|
|
4561
5416
|
async function runLiveLocale(context, targetLocale) {
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
5417
|
+
const lockOptions = {
|
|
5418
|
+
...context.onLockWait !== void 0 ? { onWait: context.onLockWait } : {},
|
|
5419
|
+
...context.lockAcquireTimeoutMs !== void 0 ? { acquireTimeoutMs: context.lockAcquireTimeoutMs } : {}
|
|
5420
|
+
};
|
|
5421
|
+
return withLocaleWriteLock(
|
|
5422
|
+
context.cwd,
|
|
5423
|
+
targetLocale,
|
|
5424
|
+
context.fs,
|
|
5425
|
+
async () => {
|
|
5426
|
+
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
5427
|
+
const params = buildLocaleRunParams(context, targetLocale, baselineFor(lock, targetLocale));
|
|
5428
|
+
const result = await runLocale(params);
|
|
5429
|
+
await updateLockFileLocale(context.cwd, context.fs, targetLocale, {
|
|
5430
|
+
mode: "replace",
|
|
5431
|
+
entries: result.lockEntries
|
|
5432
|
+
});
|
|
5433
|
+
if (context.cache !== void 0 && result.cacheAdditions.length > 0) {
|
|
5434
|
+
context.cache.additions.set(targetLocale, additionsToRecord(result.cacheAdditions));
|
|
5435
|
+
}
|
|
5436
|
+
return result.summary;
|
|
5437
|
+
},
|
|
5438
|
+
lockOptions
|
|
5439
|
+
);
|
|
4572
5440
|
}
|
|
4573
5441
|
async function runOneLocale(targetLocale, run) {
|
|
4574
5442
|
try {
|
|
@@ -4580,27 +5448,88 @@ async function runOneLocale(targetLocale, run) {
|
|
|
4580
5448
|
return failureSummary(targetLocale, error);
|
|
4581
5449
|
}
|
|
4582
5450
|
}
|
|
4583
|
-
async function
|
|
5451
|
+
async function runLocaleAt(context, targetLocales, localeIndex, runOne, results) {
|
|
5452
|
+
const targetLocale = targetLocales[localeIndex];
|
|
5453
|
+
if (targetLocale === void 0) {
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5456
|
+
context.onProgress?.({
|
|
5457
|
+
type: "locale-started",
|
|
5458
|
+
locale: targetLocale,
|
|
5459
|
+
localeIndex,
|
|
5460
|
+
totalLocales: targetLocales.length
|
|
5461
|
+
});
|
|
5462
|
+
const summary = await runOneLocale(targetLocale, () => runOne(targetLocale));
|
|
5463
|
+
results[localeIndex] = summary;
|
|
5464
|
+
context.onProgress?.({
|
|
5465
|
+
type: "locale-finished",
|
|
5466
|
+
locale: targetLocale,
|
|
5467
|
+
translated: summary.translated.length
|
|
5468
|
+
});
|
|
5469
|
+
}
|
|
5470
|
+
async function runLocalesWithProgress(context, targetLocales, runOne, concurrency) {
|
|
5471
|
+
const totalLocales = targetLocales.length;
|
|
5472
|
+
const results = new Array(totalLocales);
|
|
5473
|
+
let nextIndex = 0;
|
|
5474
|
+
async function worker() {
|
|
5475
|
+
while (nextIndex < totalLocales) {
|
|
5476
|
+
const localeIndex = nextIndex;
|
|
5477
|
+
nextIndex += 1;
|
|
5478
|
+
await runLocaleAt(context, targetLocales, localeIndex, runOne, results);
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
const workerCount = Math.min(concurrency, totalLocales);
|
|
5482
|
+
const workers = [];
|
|
5483
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
5484
|
+
workers.push(worker());
|
|
5485
|
+
}
|
|
5486
|
+
await Promise.all(workers);
|
|
5487
|
+
return results.filter((summary) => summary !== void 0);
|
|
5488
|
+
}
|
|
5489
|
+
async function runAllLocalesDry(context, targetLocales, concurrency) {
|
|
4584
5490
|
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
5491
|
+
return runLocalesWithProgress(
|
|
5492
|
+
context,
|
|
5493
|
+
targetLocales,
|
|
5494
|
+
(targetLocale) => runDryLocale(context, targetLocale, lock),
|
|
5495
|
+
concurrency
|
|
5496
|
+
);
|
|
5497
|
+
}
|
|
5498
|
+
async function runAllLocalesLive(context, targetLocales, concurrency) {
|
|
5499
|
+
return runLocalesWithProgress(
|
|
5500
|
+
context,
|
|
5501
|
+
targetLocales,
|
|
5502
|
+
(targetLocale) => runLiveLocale(context, targetLocale),
|
|
5503
|
+
concurrency
|
|
5504
|
+
);
|
|
5505
|
+
}
|
|
5506
|
+
function resolveConcurrency(value) {
|
|
5507
|
+
if (value === void 0) {
|
|
5508
|
+
return 1;
|
|
5509
|
+
}
|
|
5510
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5511
|
+
throw new SdkError(
|
|
5512
|
+
"CONCURRENCY_INVALID",
|
|
5513
|
+
`The concurrency option must be an integer of at least 1, got ${value}.`
|
|
4589
5514
|
);
|
|
4590
5515
|
}
|
|
4591
|
-
return
|
|
5516
|
+
return value;
|
|
4592
5517
|
}
|
|
4593
|
-
|
|
4594
|
-
const
|
|
4595
|
-
|
|
4596
|
-
|
|
5518
|
+
function resolveRunConcurrency(value, dryRun, config) {
|
|
5519
|
+
const concurrency = resolveConcurrency(value);
|
|
5520
|
+
if (!dryRun && concurrency > 1 && config.maxTokens !== void 0) {
|
|
5521
|
+
throw new SdkError(
|
|
5522
|
+
"CONCURRENCY_BUDGET_CONFLICT",
|
|
5523
|
+
"A token budget (maxTokens) and concurrency greater than 1 cannot be combined on a live run: concurrent locales would overshoot the budget nondeterministically. Set concurrency to 1, remove maxTokens, or use --dry-run."
|
|
5524
|
+
);
|
|
4597
5525
|
}
|
|
4598
|
-
return
|
|
5526
|
+
return concurrency;
|
|
4599
5527
|
}
|
|
4600
5528
|
async function translate2(input, deps = {}) {
|
|
4601
5529
|
const config = input.config;
|
|
4602
5530
|
const cwd = input.cwd ?? process.cwd();
|
|
4603
5531
|
const dryRun = input.dryRun ?? false;
|
|
5532
|
+
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
4604
5533
|
const prune = input.prune ?? config.prune ?? false;
|
|
4605
5534
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
4606
5535
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
@@ -4612,6 +5541,7 @@ async function translate2(input, deps = {}) {
|
|
|
4612
5541
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4613
5542
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
4614
5543
|
const source = await readSource(config, cwd, fs, adapter);
|
|
5544
|
+
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
4615
5545
|
const context = {
|
|
4616
5546
|
source,
|
|
4617
5547
|
adapter,
|
|
@@ -4622,10 +5552,15 @@ async function translate2(input, deps = {}) {
|
|
|
4622
5552
|
generatePlurals,
|
|
4623
5553
|
maxBatchSize,
|
|
4624
5554
|
fs,
|
|
4625
|
-
budget
|
|
5555
|
+
budget,
|
|
5556
|
+
cache,
|
|
5557
|
+
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5558
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
5559
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
4626
5560
|
};
|
|
4627
|
-
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
4628
|
-
|
|
5561
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales, concurrency) : await runAllLocalesLive(context, config.targetLocales, concurrency);
|
|
5562
|
+
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
5563
|
+
const { succeeded, partial, failed } = partition(summaries);
|
|
4629
5564
|
const usage = summaries.reduce(
|
|
4630
5565
|
(total, summary2) => combineUsage(total, summary2.usage),
|
|
4631
5566
|
void 0
|
|
@@ -4635,10 +5570,12 @@ async function translate2(input, deps = {}) {
|
|
|
4635
5570
|
dryRun,
|
|
4636
5571
|
locales: summaries,
|
|
4637
5572
|
succeeded,
|
|
5573
|
+
partial,
|
|
4638
5574
|
failed,
|
|
4639
5575
|
...usage !== void 0 ? { usage } : {},
|
|
4640
5576
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4641
5577
|
};
|
|
5578
|
+
await recordCacheAdditions(cwd, cache, fs);
|
|
4642
5579
|
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4643
5580
|
return summary;
|
|
4644
5581
|
}
|
|
@@ -4654,8 +5591,12 @@ var INSTRUCTIONS_LINES = [
|
|
|
4654
5591
|
"How to use this workbook",
|
|
4655
5592
|
"",
|
|
4656
5593
|
"1. There is one sheet per language. Open the sheet named for the language you are translating.",
|
|
5594
|
+
" Do not rename, delete, or reorder the language tabs. verbatra matches each tab to a language by",
|
|
5595
|
+
" its exact name; a renamed or missing tab is reported and its translations are not imported.",
|
|
4657
5596
|
"2. Fill ONLY the 'Translation' column. Leave every other column unchanged.",
|
|
4658
|
-
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty
|
|
5597
|
+
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty",
|
|
5598
|
+
" string. A cell that contains only spaces is treated the same as an empty cell.",
|
|
5599
|
+
" To deliberately clear an existing value (set it to empty), type exactly [[CLEAR]] in the cell.",
|
|
4659
5600
|
"4. Keep placeholders exactly as they appear. A token such as {name} or {count} must stay verbatim,",
|
|
4660
5601
|
" in your translation, with the same spelling. Do not translate or remove it.",
|
|
4661
5602
|
"5. For ICU messages (for example {count, plural, one {# item} other {# items}}), keep the ICU",
|
|
@@ -4820,6 +5761,26 @@ function buildInstructionsSheet(workbook) {
|
|
|
4820
5761
|
}
|
|
4821
5762
|
sheet.getRow(1).font = { bold: true };
|
|
4822
5763
|
}
|
|
5764
|
+
var WORKBOOK_PROTECTION_XML = '<workbookProtection lockStructure="1" lockWindows="0"/>';
|
|
5765
|
+
function spliceWorkbookProtection(xml) {
|
|
5766
|
+
const anchor = xml.includes("<bookViews") ? "<bookViews" : "<sheets";
|
|
5767
|
+
return xml.replace(anchor, `${WORKBOOK_PROTECTION_XML}${anchor}`);
|
|
5768
|
+
}
|
|
5769
|
+
async function protectWorkbookStructure(bytes) {
|
|
5770
|
+
let zip;
|
|
5771
|
+
try {
|
|
5772
|
+
zip = await JSZip__default.default.loadAsync(bytes);
|
|
5773
|
+
} catch {
|
|
5774
|
+
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5775
|
+
}
|
|
5776
|
+
const workbookPart = zip.file("xl/workbook.xml");
|
|
5777
|
+
if (workbookPart === null) {
|
|
5778
|
+
return bytes;
|
|
5779
|
+
}
|
|
5780
|
+
const xml = await workbookPart.async("string");
|
|
5781
|
+
zip.file("xl/workbook.xml", spliceWorkbookProtection(xml));
|
|
5782
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
5783
|
+
}
|
|
4823
5784
|
async function buildWorkbook(model) {
|
|
4824
5785
|
assertNoWorksheetNameCollisions(model.sheets);
|
|
4825
5786
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
@@ -4827,13 +5788,15 @@ async function buildWorkbook(model) {
|
|
|
4827
5788
|
for (const sheet of model.sheets) {
|
|
4828
5789
|
await buildDataSheet(workbook, sheet);
|
|
4829
5790
|
}
|
|
5791
|
+
let serialized;
|
|
4830
5792
|
try {
|
|
4831
5793
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
4832
|
-
|
|
4833
|
-
return Uint8Array.prototype.slice.call(view);
|
|
5794
|
+
serialized = buffer;
|
|
4834
5795
|
} catch {
|
|
4835
5796
|
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
4836
5797
|
}
|
|
5798
|
+
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5799
|
+
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
4837
5800
|
}
|
|
4838
5801
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
4839
5802
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
@@ -4953,26 +5916,24 @@ function assertHeader(sheet) {
|
|
|
4953
5916
|
);
|
|
4954
5917
|
}
|
|
4955
5918
|
}
|
|
4956
|
-
|
|
5919
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5920
|
+
function parseRow(row) {
|
|
4957
5921
|
const candidate = {
|
|
4958
5922
|
key: cellString(row.getCell(COLUMN.key)),
|
|
4959
5923
|
source: cellString(row.getCell(COLUMN.source)),
|
|
4960
5924
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
4961
5925
|
status: cellString(row.getCell(COLUMN.status)),
|
|
4962
5926
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
4963
|
-
translation: cellString(row.getCell(COLUMN.translation)),
|
|
5927
|
+
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
4964
5928
|
context: cellString(row.getCell(COLUMN.context)),
|
|
4965
5929
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
4966
5930
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
4967
5931
|
};
|
|
4968
5932
|
const result = rowSchema.safeParse(candidate);
|
|
4969
5933
|
if (!result.success) {
|
|
4970
|
-
|
|
4971
|
-
"WORKBOOK_INVALID",
|
|
4972
|
-
`The sheet "${sheet.name}" has a row with a missing key or an unrecognized status.`
|
|
4973
|
-
);
|
|
5934
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
4974
5935
|
}
|
|
4975
|
-
return result.data;
|
|
5936
|
+
return { ok: true, row: result.data };
|
|
4976
5937
|
}
|
|
4977
5938
|
function readDataSheet(sheet, limits) {
|
|
4978
5939
|
assertHeader(sheet);
|
|
@@ -4983,6 +5944,9 @@ function readDataSheet(sheet, limits) {
|
|
|
4983
5944
|
);
|
|
4984
5945
|
}
|
|
4985
5946
|
const rows = [];
|
|
5947
|
+
const malformed = [];
|
|
5948
|
+
const duplicates = [];
|
|
5949
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
4986
5950
|
for (let rowNumber = HEADER_ROW + 1; rowNumber <= sheet.rowCount; rowNumber += 1) {
|
|
4987
5951
|
const row = sheet.getRow(rowNumber);
|
|
4988
5952
|
if (row.cellCount > limits.maxCellsPerRow) {
|
|
@@ -4994,9 +5958,19 @@ function readDataSheet(sheet, limits) {
|
|
|
4994
5958
|
if (cellString(row.getCell(COLUMN.key)) === "") {
|
|
4995
5959
|
continue;
|
|
4996
5960
|
}
|
|
4997
|
-
|
|
5961
|
+
const outcome = parseRow(row);
|
|
5962
|
+
if (!outcome.ok) {
|
|
5963
|
+
malformed.push({ locale: sheet.name, row: rowNumber, column: outcome.column });
|
|
5964
|
+
continue;
|
|
5965
|
+
}
|
|
5966
|
+
if (seenKeys.has(outcome.row.key)) {
|
|
5967
|
+
duplicates.push({ locale: sheet.name, key: outcome.row.key, row: rowNumber });
|
|
5968
|
+
continue;
|
|
5969
|
+
}
|
|
5970
|
+
seenKeys.add(outcome.row.key);
|
|
5971
|
+
rows.push(outcome.row);
|
|
4998
5972
|
}
|
|
4999
|
-
return { locale: sheet.name, rows };
|
|
5973
|
+
return { sheet: { locale: sheet.name, rows }, malformed, duplicates };
|
|
5000
5974
|
}
|
|
5001
5975
|
async function loadWorkbook(bytes) {
|
|
5002
5976
|
const workbook = new ExcelJS__default.default.Workbook();
|
|
@@ -5019,13 +5993,18 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
5019
5993
|
);
|
|
5020
5994
|
}
|
|
5021
5995
|
const sheets = [];
|
|
5996
|
+
const malformedRows = [];
|
|
5997
|
+
const duplicateKeys = [];
|
|
5022
5998
|
for (const sheet of workbook.worksheets) {
|
|
5023
5999
|
if (sheet.name === INSTRUCTIONS_SHEET_NAME) {
|
|
5024
6000
|
continue;
|
|
5025
6001
|
}
|
|
5026
|
-
|
|
6002
|
+
const read = readDataSheet(sheet, limits);
|
|
6003
|
+
sheets.push(read.sheet);
|
|
6004
|
+
malformedRows.push(...read.malformed);
|
|
6005
|
+
duplicateKeys.push(...read.duplicates);
|
|
5027
6006
|
}
|
|
5028
|
-
return { sheets };
|
|
6007
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
5029
6008
|
}
|
|
5030
6009
|
|
|
5031
6010
|
// src/flow/workbook/export-workbook.ts
|
|
@@ -5126,6 +6105,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
5126
6105
|
}
|
|
5127
6106
|
|
|
5128
6107
|
// src/flow/workbook/import-locale.ts
|
|
6108
|
+
var CLEAR_SENTINEL = "[[CLEAR]]";
|
|
5129
6109
|
var UnknownKeyError = class extends Error {
|
|
5130
6110
|
key;
|
|
5131
6111
|
constructor(key) {
|
|
@@ -5154,9 +6134,20 @@ function trackBlankDrift(row, params, buckets) {
|
|
|
5154
6134
|
buckets.blankDrifted.add(row.key);
|
|
5155
6135
|
}
|
|
5156
6136
|
}
|
|
6137
|
+
function classifyClear(row, sourceEntry, buckets) {
|
|
6138
|
+
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
6139
|
+
buckets.mismatches.push(row.key);
|
|
6140
|
+
buckets.withheld.add(row.key);
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6143
|
+
buckets.accepted.set(row.key, { value: "", source: sourceEntry });
|
|
6144
|
+
}
|
|
5157
6145
|
function classifyRows(params, buckets) {
|
|
5158
6146
|
for (const row of params.sheet.rows) {
|
|
5159
6147
|
if (row.translation === "") {
|
|
6148
|
+
if (row.status === "changed") {
|
|
6149
|
+
buckets.unfilled.push(row.key);
|
|
6150
|
+
}
|
|
5160
6151
|
trackBlankDrift(row, params, buckets);
|
|
5161
6152
|
continue;
|
|
5162
6153
|
}
|
|
@@ -5167,6 +6158,10 @@ function classifyRows(params, buckets) {
|
|
|
5167
6158
|
if (sourceEntry === void 0) {
|
|
5168
6159
|
continue;
|
|
5169
6160
|
}
|
|
6161
|
+
if (row.translation === CLEAR_SENTINEL) {
|
|
6162
|
+
classifyClear(row, sourceEntry, buckets);
|
|
6163
|
+
continue;
|
|
6164
|
+
}
|
|
5170
6165
|
const reason = judge(row, sourceEntry, params.adapter);
|
|
5171
6166
|
if (reason === void 0) {
|
|
5172
6167
|
buckets.accepted.set(row.key, { value: row.translation, source: sourceEntry });
|
|
@@ -5188,25 +6183,39 @@ function importLocale(params) {
|
|
|
5188
6183
|
accepted: /* @__PURE__ */ new Map(),
|
|
5189
6184
|
mismatches: [],
|
|
5190
6185
|
withheld: /* @__PURE__ */ new Set(),
|
|
5191
|
-
blankDrifted: /* @__PURE__ */ new Set()
|
|
6186
|
+
blankDrifted: /* @__PURE__ */ new Set(),
|
|
6187
|
+
unfilled: []
|
|
5192
6188
|
};
|
|
5193
6189
|
classifyRows(params, buckets);
|
|
5194
6190
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
5195
6191
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
6192
|
+
const translated = [...buckets.accepted.keys()].sort();
|
|
6193
|
+
const integrityMismatches = [...buckets.mismatches].sort();
|
|
5196
6194
|
const summary = {
|
|
5197
6195
|
locale: params.sheet.locale,
|
|
5198
|
-
status:
|
|
5199
|
-
|
|
6196
|
+
status: deriveLocaleStatus({
|
|
6197
|
+
translated,
|
|
6198
|
+
cacheHits: [],
|
|
6199
|
+
generated: [],
|
|
6200
|
+
integrityMismatches,
|
|
6201
|
+
providerFailures: [],
|
|
6202
|
+
budgetWithheld: []
|
|
6203
|
+
}),
|
|
6204
|
+
translated,
|
|
5200
6205
|
unchanged: diff2.unchanged,
|
|
5201
6206
|
orphaned: diff2.orphaned,
|
|
5202
6207
|
pruned: [],
|
|
5203
6208
|
invalidIcuSource,
|
|
5204
|
-
|
|
6209
|
+
cacheHits: [],
|
|
6210
|
+
integrityMismatches,
|
|
5205
6211
|
providerFailures: [],
|
|
5206
6212
|
budgetWithheld: [],
|
|
5207
6213
|
generated: [],
|
|
5208
6214
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
5209
|
-
needsReview: []
|
|
6215
|
+
needsReview: [],
|
|
6216
|
+
unfilled: [...new Set(buckets.unfilled)].sort(),
|
|
6217
|
+
malformedRows: params.malformedRows,
|
|
6218
|
+
duplicateKeys: params.duplicateKeys
|
|
5210
6219
|
};
|
|
5211
6220
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
5212
6221
|
}
|
|
@@ -5233,6 +6242,19 @@ function mergeAccepted(target, accepted) {
|
|
|
5233
6242
|
}
|
|
5234
6243
|
return merged;
|
|
5235
6244
|
}
|
|
6245
|
+
function sheetCacheAdditions(accepted) {
|
|
6246
|
+
const record = {};
|
|
6247
|
+
for (const [, { value, source }] of accepted) {
|
|
6248
|
+
record[contentHash(source)] = value;
|
|
6249
|
+
}
|
|
6250
|
+
return record;
|
|
6251
|
+
}
|
|
6252
|
+
function collectSheetAdditions(byLocale, locale, additions) {
|
|
6253
|
+
if (Object.keys(additions).length === 0) {
|
|
6254
|
+
return;
|
|
6255
|
+
}
|
|
6256
|
+
byLocale.set(locale, { ...byLocale.get(locale), ...additions });
|
|
6257
|
+
}
|
|
5236
6258
|
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
5237
6259
|
const entries = {};
|
|
5238
6260
|
for (const key of merged.keys()) {
|
|
@@ -5249,11 +6271,20 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
5249
6271
|
}
|
|
5250
6272
|
return entries;
|
|
5251
6273
|
}
|
|
6274
|
+
var MissingSheetError = class extends Error {
|
|
6275
|
+
code = "WORKBOOK_SHEET_MISSING";
|
|
6276
|
+
constructor(locale) {
|
|
6277
|
+
super(
|
|
6278
|
+
`The workbook has no sheet (tab) for the configured target locale "${locale}". The tab may have been renamed, deleted, or reordered out of the workbook.`
|
|
6279
|
+
);
|
|
6280
|
+
this.name = "MissingSheetError";
|
|
6281
|
+
}
|
|
6282
|
+
};
|
|
5252
6283
|
async function runSheet(ctx, sheet, lock) {
|
|
5253
6284
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
5254
6285
|
throw new SdkError(
|
|
5255
6286
|
"CONFIG_INVALID",
|
|
5256
|
-
`The workbook has a sheet
|
|
6287
|
+
`The workbook has a sheet named "${sheet.locale}", which is not a configured target locale. It may be a renamed, added, or reordered tab; leave every language tab named exactly as exported.`
|
|
5257
6288
|
);
|
|
5258
6289
|
}
|
|
5259
6290
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -5264,10 +6295,12 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5264
6295
|
target,
|
|
5265
6296
|
baseline,
|
|
5266
6297
|
adapter: ctx.adapter,
|
|
5267
|
-
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys
|
|
6298
|
+
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6299
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6300
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
5268
6301
|
});
|
|
5269
6302
|
if (ctx.dryRun) {
|
|
5270
|
-
return { summary, lockEntries: {} };
|
|
6303
|
+
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
5271
6304
|
}
|
|
5272
6305
|
const merged = mergeAccepted(target, accepted);
|
|
5273
6306
|
if (accepted.size > 0) {
|
|
@@ -5282,7 +6315,11 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5282
6315
|
path
|
|
5283
6316
|
);
|
|
5284
6317
|
}
|
|
5285
|
-
return {
|
|
6318
|
+
return {
|
|
6319
|
+
summary,
|
|
6320
|
+
lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted),
|
|
6321
|
+
cacheAdditions: sheetCacheAdditions(accepted)
|
|
6322
|
+
};
|
|
5286
6323
|
}
|
|
5287
6324
|
async function importWorkbook(input, deps = {}) {
|
|
5288
6325
|
const config = input.config;
|
|
@@ -5307,9 +6344,12 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5307
6344
|
fs,
|
|
5308
6345
|
source: source.resource,
|
|
5309
6346
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
5310
|
-
dryRun
|
|
6347
|
+
dryRun,
|
|
6348
|
+
malformedRows: data.malformedRows,
|
|
6349
|
+
duplicateKeys: data.duplicateKeys
|
|
5311
6350
|
};
|
|
5312
6351
|
const summaries = [];
|
|
6352
|
+
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
5313
6353
|
for (const sheet of data.sheets) {
|
|
5314
6354
|
try {
|
|
5315
6355
|
let summary;
|
|
@@ -5322,6 +6362,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5322
6362
|
mode: "replace",
|
|
5323
6363
|
entries: result.lockEntries
|
|
5324
6364
|
});
|
|
6365
|
+
collectSheetAdditions(cacheAdditions, sheet.locale, result.cacheAdditions);
|
|
5325
6366
|
return result.summary;
|
|
5326
6367
|
});
|
|
5327
6368
|
}
|
|
@@ -5330,8 +6371,17 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5330
6371
|
summaries.push(failureSummary(sheet.locale, error));
|
|
5331
6372
|
}
|
|
5332
6373
|
}
|
|
5333
|
-
const
|
|
5334
|
-
|
|
6374
|
+
const presentLocales = new Set(data.sheets.map((sheet) => sheet.locale));
|
|
6375
|
+
for (const locale of config.targetLocales) {
|
|
6376
|
+
if (!presentLocales.has(locale)) {
|
|
6377
|
+
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
if (!dryRun) {
|
|
6381
|
+
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6382
|
+
}
|
|
6383
|
+
const { succeeded, partial, failed } = partition(summaries);
|
|
6384
|
+
return { dryRun, locales: summaries, succeeded, partial, failed };
|
|
5335
6385
|
}
|
|
5336
6386
|
|
|
5337
6387
|
// src/lock/load-lock-file.ts
|
|
@@ -5389,7 +6439,15 @@ async function watch(input, deps = {}) {
|
|
|
5389
6439
|
);
|
|
5390
6440
|
}
|
|
5391
6441
|
const runTranslate = deps.runTranslate ?? defaultRunTranslate(deps);
|
|
5392
|
-
const runInput = {
|
|
6442
|
+
const runInput = {
|
|
6443
|
+
config: input.config,
|
|
6444
|
+
cwd,
|
|
6445
|
+
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
6446
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6447
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
6448
|
+
...input.concurrency !== void 0 ? { concurrency: input.concurrency } : {},
|
|
6449
|
+
...input.cache !== void 0 ? { cache: input.cache } : {}
|
|
6450
|
+
};
|
|
5393
6451
|
let state = "idle";
|
|
5394
6452
|
let pending = false;
|
|
5395
6453
|
let stopped = false;
|
|
@@ -5456,6 +6514,7 @@ async function watch(input, deps = {}) {
|
|
|
5456
6514
|
return { stop };
|
|
5457
6515
|
}
|
|
5458
6516
|
|
|
6517
|
+
exports.CACHE_FILE_NAME = CACHE_FILE_NAME;
|
|
5459
6518
|
exports.DEFAULT_WORKBOOK_PATH = DEFAULT_WORKBOOK_PATH;
|
|
5460
6519
|
exports.LOCK_FILE_NAME = LOCK_FILE_NAME;
|
|
5461
6520
|
exports.SdkError = SdkError;
|