@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.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
1
|
import { dirname, resolve, join, basename, extname } from 'path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
import { cosmiconfig } from 'cosmiconfig';
|
|
4
5
|
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
5
6
|
import { randomUUID } from 'crypto';
|
|
6
7
|
import { rm, access, mkdir, open, writeFile, rename } from 'fs/promises';
|
|
7
|
-
import { z } from 'zod';
|
|
8
8
|
import Anthropic from '@anthropic-ai/sdk';
|
|
9
9
|
import * as deepl from 'deepl-node';
|
|
10
10
|
import { createRequire } from 'module';
|
|
@@ -15,10 +15,100 @@ import { parse, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
|
15
15
|
import { XMLSerializer, DOMParser } from '@xmldom/xmldom';
|
|
16
16
|
import { stringify, parse as parse$1 } from 'yaml';
|
|
17
17
|
import ExcelJS from 'exceljs';
|
|
18
|
-
import { Readable } from 'stream';
|
|
19
18
|
import JSZip from 'jszip';
|
|
19
|
+
import { Readable } from 'stream';
|
|
20
20
|
import { watch as watch$1 } from 'chokidar';
|
|
21
21
|
|
|
22
|
+
// src/cache/translation-memory.ts
|
|
23
|
+
var CACHE_FILE_NAME = "verbatra.cache.json";
|
|
24
|
+
var CURRENT_VERSION = 1;
|
|
25
|
+
var EMPTY_MEMORY = { version: CURRENT_VERSION, entries: {} };
|
|
26
|
+
var MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
|
|
27
|
+
var translationMemorySchema = z.object({
|
|
28
|
+
version: z.number().int().positive(),
|
|
29
|
+
entries: z.record(z.string(), z.record(z.string(), z.record(z.string(), z.string())))
|
|
30
|
+
});
|
|
31
|
+
function cacheFilePath(cwd) {
|
|
32
|
+
return resolve(cwd, CACHE_FILE_NAME);
|
|
33
|
+
}
|
|
34
|
+
async function readTranslationMemory(path, fs) {
|
|
35
|
+
let read;
|
|
36
|
+
try {
|
|
37
|
+
read = await fs.readFileBounded(path, MAX_CACHE_FILE_BYTES);
|
|
38
|
+
} catch {
|
|
39
|
+
return EMPTY_MEMORY;
|
|
40
|
+
}
|
|
41
|
+
if (read.kind !== "ok") {
|
|
42
|
+
return EMPTY_MEMORY;
|
|
43
|
+
}
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(read.content);
|
|
47
|
+
} catch {
|
|
48
|
+
return EMPTY_MEMORY;
|
|
49
|
+
}
|
|
50
|
+
const result = translationMemorySchema.safeParse(parsed);
|
|
51
|
+
if (!result.success || result.data.version !== CURRENT_VERSION) {
|
|
52
|
+
return EMPTY_MEMORY;
|
|
53
|
+
}
|
|
54
|
+
return result.data;
|
|
55
|
+
}
|
|
56
|
+
function lookupMemory(memory, fingerprint, locale, contentHash2) {
|
|
57
|
+
return memory.entries[fingerprint]?.[locale]?.[contentHash2];
|
|
58
|
+
}
|
|
59
|
+
function applyAdditions(base, fingerprint, additionsByLocale) {
|
|
60
|
+
if (additionsByLocale.size === 0) {
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
63
|
+
const fingerprintEntries = {};
|
|
64
|
+
for (const [locale, hashes] of Object.entries(base.entries[fingerprint] ?? {})) {
|
|
65
|
+
fingerprintEntries[locale] = { ...hashes };
|
|
66
|
+
}
|
|
67
|
+
for (const [locale, hashes] of additionsByLocale) {
|
|
68
|
+
fingerprintEntries[locale] = { ...fingerprintEntries[locale], ...hashes };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
version: base.version,
|
|
72
|
+
entries: { ...base.entries, [fingerprint]: fingerprintEntries }
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function additionsToRecord(additions) {
|
|
76
|
+
const record = {};
|
|
77
|
+
for (const addition of additions) {
|
|
78
|
+
record[addition.contentHash] = addition.value;
|
|
79
|
+
}
|
|
80
|
+
return record;
|
|
81
|
+
}
|
|
82
|
+
function sortEntries(record) {
|
|
83
|
+
return Object.entries(record).sort(([a], [b]) => a < b ? -1 : 1);
|
|
84
|
+
}
|
|
85
|
+
function serialize(memory) {
|
|
86
|
+
const entries = {};
|
|
87
|
+
for (const [fingerprint, locales] of sortEntries(memory.entries)) {
|
|
88
|
+
const localeMap = {};
|
|
89
|
+
for (const [locale, hashes] of sortEntries(locales)) {
|
|
90
|
+
localeMap[locale] = Object.fromEntries(sortEntries(hashes));
|
|
91
|
+
}
|
|
92
|
+
entries[fingerprint] = localeMap;
|
|
93
|
+
}
|
|
94
|
+
return `${JSON.stringify({ version: memory.version, entries }, null, 2)}
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
async function writeTranslationMemory(path, memory, fs) {
|
|
98
|
+
await fs.writeFile(path, serialize(memory));
|
|
99
|
+
}
|
|
100
|
+
async function feedTranslationMemory(cwd, fs, fingerprint, additionsByLocale) {
|
|
101
|
+
if (additionsByLocale.size === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const path = cacheFilePath(cwd);
|
|
106
|
+
const memory = await readTranslationMemory(path, fs);
|
|
107
|
+
await writeTranslationMemory(path, applyAdditions(memory, fingerprint, additionsByLocale), fs);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
22
112
|
// src/config/define-config.ts
|
|
23
113
|
function defineConfig(config) {
|
|
24
114
|
return config;
|
|
@@ -212,7 +302,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
212
302
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
213
303
|
var FNV_PRIME = 1099511628211n;
|
|
214
304
|
var U64_MASK = (1n << 64n) - 1n;
|
|
215
|
-
function
|
|
305
|
+
function stableStringHash(input) {
|
|
216
306
|
let hash = FNV_OFFSET_BASIS;
|
|
217
307
|
for (let index = 0; index < input.length; index += 1) {
|
|
218
308
|
hash ^= BigInt(input.charCodeAt(index));
|
|
@@ -233,7 +323,7 @@ function canonicalize(entry) {
|
|
|
233
323
|
]);
|
|
234
324
|
}
|
|
235
325
|
function contentHash(entry) {
|
|
236
|
-
return
|
|
326
|
+
return stableStringHash(canonicalize(entry));
|
|
237
327
|
}
|
|
238
328
|
function sorted(keys) {
|
|
239
329
|
return [...keys].sort();
|
|
@@ -278,7 +368,8 @@ var SUPPORTED_FORMATS = [
|
|
|
278
368
|
"ngx-translate-json",
|
|
279
369
|
"xliff",
|
|
280
370
|
"yaml",
|
|
281
|
-
"arb"
|
|
371
|
+
"arb",
|
|
372
|
+
"properties"
|
|
282
373
|
];
|
|
283
374
|
var supportedFormatSchema = z.enum(SUPPORTED_FORMATS);
|
|
284
375
|
var translationEntrySchema = z.object({
|
|
@@ -329,77 +420,76 @@ function checkPlaceholders(source, translated) {
|
|
|
329
420
|
reordered
|
|
330
421
|
};
|
|
331
422
|
}
|
|
332
|
-
var
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
var
|
|
337
|
-
var
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
var AUTH_FAILED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
344
|
-
"AuthenticationError",
|
|
345
|
-
"PermissionDeniedError",
|
|
346
|
-
"AuthorizationError"
|
|
347
|
-
]);
|
|
348
|
-
var TIMEOUT_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
349
|
-
"APIConnectionTimeoutError",
|
|
350
|
-
"ConnectionError"
|
|
351
|
-
]);
|
|
352
|
-
var ABORT_ERROR_CLASS_NAMES = /* @__PURE__ */ new Set(["APIUserAbortError"]);
|
|
353
|
-
function getErrorStatus(error) {
|
|
354
|
-
if (typeof error !== "object" || error === null || !("status" in error)) {
|
|
355
|
-
return void 0;
|
|
423
|
+
var MIN_SOURCE_LENGTH = 8;
|
|
424
|
+
var MAX_LENGTH_MULTIPLE = 12;
|
|
425
|
+
var MAX_REPEAT_UNIT_LENGTH = 16;
|
|
426
|
+
var MIN_CONSECUTIVE_REPEATS = 8;
|
|
427
|
+
var REPEAT_COVERAGE_FRACTION = 0.5;
|
|
428
|
+
var MAX_SCAN_LENGTH = 8192;
|
|
429
|
+
function matchesUnit(text, a, b, unit) {
|
|
430
|
+
for (let i = 0; i < unit; i++) {
|
|
431
|
+
if (text[a + i] !== text[b + i]) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
356
434
|
}
|
|
357
|
-
|
|
358
|
-
return typeof status === "number" ? status : void 0;
|
|
435
|
+
return true;
|
|
359
436
|
}
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
437
|
+
function countConsecutiveCopies(text, start, unit, scanLength) {
|
|
438
|
+
let copies = 1;
|
|
439
|
+
let next = start + unit;
|
|
440
|
+
while (next + unit <= scanLength && matchesUnit(text, start, next, unit)) {
|
|
441
|
+
copies++;
|
|
442
|
+
next += unit;
|
|
443
|
+
}
|
|
444
|
+
return copies;
|
|
445
|
+
}
|
|
446
|
+
function hasDominantRun(text, scanLength, unit, coverage) {
|
|
447
|
+
const limit = scanLength - unit;
|
|
448
|
+
let start = 0;
|
|
449
|
+
while (start <= limit) {
|
|
450
|
+
const copies = countConsecutiveCopies(text, start, unit, scanLength);
|
|
451
|
+
if (copies >= MIN_CONSECUTIVE_REPEATS && copies * unit >= coverage) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
start += copies * unit;
|
|
363
455
|
}
|
|
364
|
-
return
|
|
456
|
+
return false;
|
|
365
457
|
}
|
|
366
|
-
function
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
const className = readClassName(error);
|
|
378
|
-
if (className !== void 0) {
|
|
379
|
-
if (RATE_LIMITED_CLASS_NAMES.has(className)) {
|
|
380
|
-
return "RATE_LIMITED";
|
|
381
|
-
}
|
|
382
|
-
if (AUTH_FAILED_CLASS_NAMES.has(className)) {
|
|
383
|
-
return "AUTH_FAILED";
|
|
384
|
-
}
|
|
385
|
-
if (TIMEOUT_CLASS_NAMES.has(className)) {
|
|
386
|
-
return "TIMEOUT";
|
|
458
|
+
function hasRunawayRepetition(text) {
|
|
459
|
+
const scanLength = Math.min(text.length, MAX_SCAN_LENGTH);
|
|
460
|
+
const coverage = scanLength * REPEAT_COVERAGE_FRACTION;
|
|
461
|
+
const maxUnit = Math.min(
|
|
462
|
+
MAX_REPEAT_UNIT_LENGTH,
|
|
463
|
+
Math.floor(scanLength / MIN_CONSECUTIVE_REPEATS)
|
|
464
|
+
);
|
|
465
|
+
for (let unit = 1; unit <= maxUnit; unit++) {
|
|
466
|
+
if (hasDominantRun(text, scanLength, unit, coverage)) {
|
|
467
|
+
return true;
|
|
387
468
|
}
|
|
388
469
|
}
|
|
389
|
-
return
|
|
470
|
+
return false;
|
|
390
471
|
}
|
|
391
|
-
function
|
|
392
|
-
|
|
393
|
-
|
|
472
|
+
function assessValueDegeneracy(sourceValue, candidateValue) {
|
|
473
|
+
const source = sourceValue.trim();
|
|
474
|
+
const candidate = candidateValue.trim();
|
|
475
|
+
if (source.length >= MIN_SOURCE_LENGTH && candidate.length >= source.length * MAX_LENGTH_MULTIPLE) {
|
|
476
|
+
return { degenerate: true };
|
|
394
477
|
}
|
|
395
|
-
|
|
396
|
-
return className !== void 0 && ABORT_ERROR_CLASS_NAMES.has(className);
|
|
478
|
+
return { degenerate: hasRunawayRepetition(candidate) };
|
|
397
479
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
480
|
+
var LOCALE_TOKEN = "{locale}";
|
|
481
|
+
function localeFilePath(cwd, pattern, locale) {
|
|
482
|
+
return resolve(cwd, pattern.replaceAll(LOCALE_TOKEN, locale));
|
|
483
|
+
}
|
|
484
|
+
function checkBatchIntegrity(inputs, extract, compare) {
|
|
485
|
+
const integrity = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const { key, sourceValue, translatedValue } of inputs) {
|
|
487
|
+
integrity.set(
|
|
488
|
+
key,
|
|
489
|
+
compare !== void 0 ? compare(sourceValue, translatedValue) : checkPlaceholders(extract(sourceValue), extract(translatedValue))
|
|
490
|
+
);
|
|
401
491
|
}
|
|
402
|
-
return
|
|
492
|
+
return integrity;
|
|
403
493
|
}
|
|
404
494
|
var REDACTED = "[REDACTED]";
|
|
405
495
|
var KEY_PATTERNS = [
|
|
@@ -437,38 +527,6 @@ var ProviderError = class extends Error {
|
|
|
437
527
|
this.code = code;
|
|
438
528
|
}
|
|
439
529
|
};
|
|
440
|
-
var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
441
|
-
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
442
|
-
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
443
|
-
var AUTH_FAILED_MESSAGE = "The translation provider rejected the request credentials.";
|
|
444
|
-
var MESSAGE_BY_CODE = {
|
|
445
|
-
RATE_LIMITED: RATE_LIMITED_MESSAGE,
|
|
446
|
-
TIMEOUT: TIMEOUT_MESSAGE,
|
|
447
|
-
AUTH_FAILED: AUTH_FAILED_MESSAGE,
|
|
448
|
-
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
449
|
-
};
|
|
450
|
-
async function guardProviderCall(call, signal) {
|
|
451
|
-
signal?.throwIfAborted();
|
|
452
|
-
try {
|
|
453
|
-
return await call();
|
|
454
|
-
} catch (error) {
|
|
455
|
-
if (isAbortError(error, signal)) {
|
|
456
|
-
throw error;
|
|
457
|
-
}
|
|
458
|
-
const code = classifyProviderError(error);
|
|
459
|
-
throw new ProviderError(code, MESSAGE_BY_CODE[code]);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function checkBatchIntegrity(inputs, extract, compare) {
|
|
463
|
-
const integrity = /* @__PURE__ */ new Map();
|
|
464
|
-
for (const { key, sourceValue, translatedValue } of inputs) {
|
|
465
|
-
integrity.set(
|
|
466
|
-
key,
|
|
467
|
-
compare !== void 0 ? compare(sourceValue, translatedValue) : checkPlaceholders(extract(sourceValue), extract(translatedValue))
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
return integrity;
|
|
471
|
-
}
|
|
472
530
|
var requestDataSchema = z.object({
|
|
473
531
|
sourceLocale: z.string().min(1),
|
|
474
532
|
targetLocale: z.string().min(1),
|
|
@@ -711,6 +769,121 @@ function assertNotTruncated(truncated) {
|
|
|
711
769
|
throw new ProviderError("OUTPUT_TRUNCATED", OUTPUT_TRUNCATED_MESSAGE);
|
|
712
770
|
}
|
|
713
771
|
}
|
|
772
|
+
var RATE_LIMITED_STATUS = 429;
|
|
773
|
+
var TIMEOUT_STATUS = 408;
|
|
774
|
+
var AUTH_FAILED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
775
|
+
var RATE_LIMITED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
776
|
+
"RateLimitError",
|
|
777
|
+
"TooManyRequestsError"
|
|
778
|
+
]);
|
|
779
|
+
var AUTH_FAILED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
780
|
+
"AuthenticationError",
|
|
781
|
+
"PermissionDeniedError",
|
|
782
|
+
"AuthorizationError"
|
|
783
|
+
]);
|
|
784
|
+
var TIMEOUT_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
785
|
+
"APIConnectionTimeoutError",
|
|
786
|
+
"ConnectionError"
|
|
787
|
+
]);
|
|
788
|
+
var ABORT_ERROR_CLASS_NAMES = /* @__PURE__ */ new Set(["APIUserAbortError"]);
|
|
789
|
+
function getErrorStatus(error) {
|
|
790
|
+
if (typeof error !== "object" || error === null || !("status" in error)) {
|
|
791
|
+
return void 0;
|
|
792
|
+
}
|
|
793
|
+
const status = error.status;
|
|
794
|
+
return typeof status === "number" ? status : void 0;
|
|
795
|
+
}
|
|
796
|
+
function readClassName(error) {
|
|
797
|
+
if (typeof error !== "object" || error === null) {
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
return error.constructor?.name;
|
|
801
|
+
}
|
|
802
|
+
function classifyProviderError(error) {
|
|
803
|
+
const status = getErrorStatus(error);
|
|
804
|
+
if (status === RATE_LIMITED_STATUS) {
|
|
805
|
+
return "RATE_LIMITED";
|
|
806
|
+
}
|
|
807
|
+
if (status !== void 0 && AUTH_FAILED_STATUSES.has(status)) {
|
|
808
|
+
return "AUTH_FAILED";
|
|
809
|
+
}
|
|
810
|
+
if (status === TIMEOUT_STATUS) {
|
|
811
|
+
return "TIMEOUT";
|
|
812
|
+
}
|
|
813
|
+
const className = readClassName(error);
|
|
814
|
+
if (className !== void 0) {
|
|
815
|
+
if (RATE_LIMITED_CLASS_NAMES.has(className)) {
|
|
816
|
+
return "RATE_LIMITED";
|
|
817
|
+
}
|
|
818
|
+
if (AUTH_FAILED_CLASS_NAMES.has(className)) {
|
|
819
|
+
return "AUTH_FAILED";
|
|
820
|
+
}
|
|
821
|
+
if (TIMEOUT_CLASS_NAMES.has(className)) {
|
|
822
|
+
return "TIMEOUT";
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return "PROVIDER_ERROR";
|
|
826
|
+
}
|
|
827
|
+
function isAbortShapedError(error) {
|
|
828
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
const className = readClassName(error);
|
|
832
|
+
return className !== void 0 && ABORT_ERROR_CLASS_NAMES.has(className);
|
|
833
|
+
}
|
|
834
|
+
function isAbortError(error, signal) {
|
|
835
|
+
if (signal !== void 0) {
|
|
836
|
+
return signal.aborted === true && isAbortShapedError(error);
|
|
837
|
+
}
|
|
838
|
+
return isAbortShapedError(error);
|
|
839
|
+
}
|
|
840
|
+
var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
841
|
+
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
842
|
+
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
843
|
+
var AUTH_FAILED_MESSAGE = "The translation provider rejected the request credentials.";
|
|
844
|
+
var MESSAGE_BY_CODE = {
|
|
845
|
+
RATE_LIMITED: RATE_LIMITED_MESSAGE,
|
|
846
|
+
TIMEOUT: TIMEOUT_MESSAGE,
|
|
847
|
+
AUTH_FAILED: AUTH_FAILED_MESSAGE,
|
|
848
|
+
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
849
|
+
};
|
|
850
|
+
async function guardProviderCall(call, signal) {
|
|
851
|
+
signal?.throwIfAborted();
|
|
852
|
+
try {
|
|
853
|
+
return await call();
|
|
854
|
+
} catch (error) {
|
|
855
|
+
if (isAbortError(error, signal)) {
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
858
|
+
const code = classifyProviderError(error);
|
|
859
|
+
throw new ProviderError(code, MESSAGE_BY_CODE[code]);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
863
|
+
function requestTimedOutMessage(timeoutMs) {
|
|
864
|
+
return `The translation provider request exceeded the ${timeoutMs} ms request timeout.`;
|
|
865
|
+
}
|
|
866
|
+
function combineSignals(caller, timeout) {
|
|
867
|
+
return caller === void 0 ? timeout : AbortSignal.any([caller, timeout]);
|
|
868
|
+
}
|
|
869
|
+
async function withRequestTimeout(timeoutMs, callerSignal, call) {
|
|
870
|
+
const timeoutController = new AbortController();
|
|
871
|
+
const signal = combineSignals(callerSignal, timeoutController.signal);
|
|
872
|
+
let timer;
|
|
873
|
+
const timedOut = new Promise((_, reject) => {
|
|
874
|
+
timer = setTimeout(() => {
|
|
875
|
+
timeoutController.abort();
|
|
876
|
+
reject(new ProviderError("TIMEOUT", requestTimedOutMessage(timeoutMs)));
|
|
877
|
+
}, timeoutMs);
|
|
878
|
+
});
|
|
879
|
+
try {
|
|
880
|
+
return await Promise.race([guardProviderCall(() => call(signal), signal), timedOut]);
|
|
881
|
+
} finally {
|
|
882
|
+
if (timer !== void 0) {
|
|
883
|
+
clearTimeout(timer);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
714
887
|
var PROVIDER_ENV = {
|
|
715
888
|
anthropic: "ANTHROPIC_API_KEY",
|
|
716
889
|
openai: "OPENAI_API_KEY",
|
|
@@ -765,7 +938,13 @@ function createDefaultClient() {
|
|
|
765
938
|
}
|
|
766
939
|
var anthropicConfigSchema = z.object({
|
|
767
940
|
model: z.string().min(1),
|
|
768
|
-
maxTokens: z.number().int().positive()
|
|
941
|
+
maxTokens: z.number().int().positive(),
|
|
942
|
+
/**
|
|
943
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
944
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
945
|
+
* server cannot hold a locale's write lock open forever.
|
|
946
|
+
*/
|
|
947
|
+
requestTimeoutMs: z.number().int().positive().optional()
|
|
769
948
|
});
|
|
770
949
|
var SUBMIT_TOOL_NAME = "submit_translations";
|
|
771
950
|
var SYSTEM_RULES = [
|
|
@@ -825,10 +1004,11 @@ function createAnthropicProvider(config, deps = {}) {
|
|
|
825
1004
|
};
|
|
826
1005
|
}
|
|
827
1006
|
function createMechanism(client, config) {
|
|
1007
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
828
1008
|
return {
|
|
829
1009
|
translate: async ({ payloadJson, signal }) => {
|
|
830
1010
|
const body = buildRequest(config, payloadJson);
|
|
831
|
-
const message = await callClient(client, body, signal);
|
|
1011
|
+
const message = await callClient(client, body, timeoutMs, signal);
|
|
832
1012
|
assertNotTruncated(message.stop_reason === "max_tokens");
|
|
833
1013
|
const raw = requireToolInput(message.content);
|
|
834
1014
|
const usage = toUsage(message.usage);
|
|
@@ -836,10 +1016,11 @@ function createMechanism(client, config) {
|
|
|
836
1016
|
}
|
|
837
1017
|
};
|
|
838
1018
|
}
|
|
839
|
-
function callClient(client, body, signal) {
|
|
840
|
-
return
|
|
841
|
-
|
|
842
|
-
signal
|
|
1019
|
+
function callClient(client, body, timeoutMs, signal) {
|
|
1020
|
+
return withRequestTimeout(
|
|
1021
|
+
timeoutMs,
|
|
1022
|
+
signal,
|
|
1023
|
+
(requestSignal) => client.messages.create(body, { signal: requestSignal })
|
|
843
1024
|
);
|
|
844
1025
|
}
|
|
845
1026
|
function toUsage(usage) {
|
|
@@ -853,7 +1034,14 @@ function toUsage(usage) {
|
|
|
853
1034
|
return { inputTokens: input_tokens, outputTokens: output_tokens };
|
|
854
1035
|
}
|
|
855
1036
|
var deepLConfigSchema = z.object({
|
|
856
|
-
glossaryId: z.string().min(1).optional()
|
|
1037
|
+
glossaryId: z.string().min(1).optional(),
|
|
1038
|
+
/**
|
|
1039
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1040
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
1041
|
+
* server cannot hold a locale's write lock open forever. deepl-node cannot cancel an in-flight
|
|
1042
|
+
* request, so on timeout verbatra's await is released but the underlying request is left to settle.
|
|
1043
|
+
*/
|
|
1044
|
+
requestTimeoutMs: z.number().int().positive().optional()
|
|
857
1045
|
});
|
|
858
1046
|
var DEEPL_LOGGER = "deepl";
|
|
859
1047
|
function resolveDeeplLoglevel(requireFn = createRequire(import.meta.url)) {
|
|
@@ -1024,6 +1212,7 @@ async function translate(bundle, config, request) {
|
|
|
1024
1212
|
options,
|
|
1025
1213
|
request.extractPlaceholders,
|
|
1026
1214
|
request.comparePlaceholders,
|
|
1215
|
+
config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
1027
1216
|
request.signal
|
|
1028
1217
|
);
|
|
1029
1218
|
if (unprotectable.length > 0) {
|
|
@@ -1058,7 +1247,7 @@ function buildReviewFlags2(protectable, data, values, integrity, glossary) {
|
|
|
1058
1247
|
}
|
|
1059
1248
|
return reviewFlags;
|
|
1060
1249
|
}
|
|
1061
|
-
async function translateProtectable(client, data, protectable, options, extract, compare, signal) {
|
|
1250
|
+
async function translateProtectable(client, data, protectable, options, extract, compare, timeoutMs, signal) {
|
|
1062
1251
|
if (protectable.length === 0) {
|
|
1063
1252
|
return { values: /* @__PURE__ */ new Map(), integrity: /* @__PURE__ */ new Map() };
|
|
1064
1253
|
}
|
|
@@ -1069,29 +1258,45 @@ async function translateProtectable(client, data, protectable, options, extract,
|
|
|
1069
1258
|
data.sourceLocale,
|
|
1070
1259
|
data.targetLocale,
|
|
1071
1260
|
options,
|
|
1261
|
+
timeoutMs,
|
|
1072
1262
|
signal
|
|
1073
1263
|
);
|
|
1074
1264
|
const { values, integrityInputs } = zipResults(protectable, results);
|
|
1075
1265
|
const integrity = checkBatchIntegrity(integrityInputs, extract, compare);
|
|
1076
1266
|
return { values, integrity };
|
|
1077
1267
|
}
|
|
1078
|
-
function callClient2(client, texts, sourceLang, targetLang, options, signal) {
|
|
1079
|
-
return
|
|
1080
|
-
|
|
1081
|
-
signal
|
|
1268
|
+
function callClient2(client, texts, sourceLang, targetLang, options, timeoutMs, signal) {
|
|
1269
|
+
return withRequestTimeout(
|
|
1270
|
+
timeoutMs,
|
|
1271
|
+
signal,
|
|
1272
|
+
() => client.translateText(texts, sourceLang, targetLang, options)
|
|
1082
1273
|
);
|
|
1083
1274
|
}
|
|
1084
|
-
async function callClientChunked(client, texts, sourceLang, targetLang, options, signal) {
|
|
1275
|
+
async function callClientChunked(client, texts, sourceLang, targetLang, options, timeoutMs, signal) {
|
|
1085
1276
|
const results = [];
|
|
1086
1277
|
for (const chunk2 of chunkTextsForDeepL(texts)) {
|
|
1087
|
-
const chunkResults = await callClient2(
|
|
1278
|
+
const chunkResults = await callClient2(
|
|
1279
|
+
client,
|
|
1280
|
+
chunk2,
|
|
1281
|
+
sourceLang,
|
|
1282
|
+
targetLang,
|
|
1283
|
+
options,
|
|
1284
|
+
timeoutMs,
|
|
1285
|
+
signal
|
|
1286
|
+
);
|
|
1088
1287
|
results.push(...chunkResults);
|
|
1089
1288
|
}
|
|
1090
1289
|
return results;
|
|
1091
1290
|
}
|
|
1092
1291
|
var geminiConfigSchema = z.object({
|
|
1093
1292
|
model: z.string().min(1),
|
|
1094
|
-
maxOutputTokens: z.number().int().positive()
|
|
1293
|
+
maxOutputTokens: z.number().int().positive(),
|
|
1294
|
+
/**
|
|
1295
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1296
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
1297
|
+
* server cannot hold a locale's write lock open forever.
|
|
1298
|
+
*/
|
|
1299
|
+
requestTimeoutMs: z.number().int().positive().optional()
|
|
1095
1300
|
});
|
|
1096
1301
|
var DEFAULT_GEMINI_RETRY = { attempts: 3, baseDelayMs: 250 };
|
|
1097
1302
|
function isRetryableStatus(error) {
|
|
@@ -1106,14 +1311,14 @@ function throwAbort(signal) {
|
|
|
1106
1311
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1107
1312
|
}
|
|
1108
1313
|
function delay(ms, signal) {
|
|
1109
|
-
return new Promise((
|
|
1314
|
+
return new Promise((resolve10) => {
|
|
1110
1315
|
const onAbort = () => {
|
|
1111
1316
|
clearTimeout(timer);
|
|
1112
|
-
|
|
1317
|
+
resolve10();
|
|
1113
1318
|
};
|
|
1114
1319
|
const timer = setTimeout(() => {
|
|
1115
1320
|
signal?.removeEventListener("abort", onAbort);
|
|
1116
|
-
|
|
1321
|
+
resolve10();
|
|
1117
1322
|
}, ms);
|
|
1118
1323
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1119
1324
|
});
|
|
@@ -1281,20 +1486,27 @@ function createGeminiProvider(config, deps = {}) {
|
|
|
1281
1486
|
};
|
|
1282
1487
|
}
|
|
1283
1488
|
function createMechanism2(client, config) {
|
|
1489
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1284
1490
|
return {
|
|
1285
1491
|
translate: async ({ payloadJson, signal }) => {
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1492
|
+
const response = await withRequestTimeout(
|
|
1493
|
+
timeoutMs,
|
|
1494
|
+
signal,
|
|
1495
|
+
(requestSignal) => client.models.generateContent(buildGeminiRequest(config, payloadJson, requestSignal))
|
|
1496
|
+
);
|
|
1288
1497
|
return extractGeminiResult(response);
|
|
1289
1498
|
}
|
|
1290
1499
|
};
|
|
1291
1500
|
}
|
|
1292
|
-
function callClient3(client, request, signal) {
|
|
1293
|
-
return guardProviderCall(() => client.models.generateContent(request), signal);
|
|
1294
|
-
}
|
|
1295
1501
|
var openAiConfigSchema = z.object({
|
|
1296
1502
|
model: z.string().min(1),
|
|
1297
|
-
maxOutputTokens: z.number().int().positive()
|
|
1503
|
+
maxOutputTokens: z.number().int().positive(),
|
|
1504
|
+
/**
|
|
1505
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1506
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive
|
|
1507
|
+
* server cannot hold a locale's write lock open forever.
|
|
1508
|
+
*/
|
|
1509
|
+
requestTimeoutMs: z.number().int().positive().optional()
|
|
1298
1510
|
});
|
|
1299
1511
|
function createDefaultClient4() {
|
|
1300
1512
|
const sdk = new OpenAI({ apiKey: requireOpenAiKey(), logLevel: "off" });
|
|
@@ -1449,18 +1661,20 @@ function createOpenAiProvider(config, deps = {}) {
|
|
|
1449
1661
|
};
|
|
1450
1662
|
}
|
|
1451
1663
|
function createMechanism3(client, config) {
|
|
1664
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1452
1665
|
return {
|
|
1453
1666
|
translate: async ({ payloadJson, signal }) => {
|
|
1454
1667
|
const body = buildOpenAiRequest(config, payloadJson);
|
|
1455
|
-
const completion = await
|
|
1668
|
+
const completion = await callClient3(client, body, timeoutMs, signal);
|
|
1456
1669
|
return extractOpenAiResult(completion);
|
|
1457
1670
|
}
|
|
1458
1671
|
};
|
|
1459
1672
|
}
|
|
1460
|
-
function
|
|
1461
|
-
return
|
|
1462
|
-
|
|
1463
|
-
signal
|
|
1673
|
+
function callClient3(client, body, timeoutMs, signal) {
|
|
1674
|
+
return withRequestTimeout(
|
|
1675
|
+
timeoutMs,
|
|
1676
|
+
signal,
|
|
1677
|
+
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal })
|
|
1464
1678
|
);
|
|
1465
1679
|
}
|
|
1466
1680
|
var HOSTED_PROVIDER_ENV_VARS = new Set(
|
|
@@ -1483,7 +1697,13 @@ var openAiCompatibleConfigSchema = z.object({
|
|
|
1483
1697
|
maxOutputTokens: z.number().int().positive(),
|
|
1484
1698
|
apiKeyEnvVar: z.string().min(1).refine(isNotHostedProviderEnvVar, {
|
|
1485
1699
|
message: "apiKeyEnvVar must not name a hosted provider's environment variable."
|
|
1486
|
-
}).optional()
|
|
1700
|
+
}).optional(),
|
|
1701
|
+
/**
|
|
1702
|
+
* Optional verbatra-imposed per-request timeout in milliseconds. A positive integer; when absent,
|
|
1703
|
+
* the shared default request timeout applies. Bounds each outbound request so a hung-but-alive local
|
|
1704
|
+
* server (the reported stuck-LM-Studio case) cannot hold a locale's write lock open forever.
|
|
1705
|
+
*/
|
|
1706
|
+
requestTimeoutMs: z.number().int().positive().optional()
|
|
1487
1707
|
});
|
|
1488
1708
|
function createDefaultClient5(config) {
|
|
1489
1709
|
const sdk = new OpenAI({
|
|
@@ -1515,18 +1735,20 @@ function createOpenAiCompatibleProvider(config, deps = {}) {
|
|
|
1515
1735
|
};
|
|
1516
1736
|
}
|
|
1517
1737
|
function createMechanism4(client, config) {
|
|
1738
|
+
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1518
1739
|
return {
|
|
1519
1740
|
translate: async ({ payloadJson, signal }) => {
|
|
1520
1741
|
const body = buildOpenAiRequest(config, payloadJson, "strict-schema", "max_tokens");
|
|
1521
|
-
const completion = await
|
|
1742
|
+
const completion = await callClient4(client, body, timeoutMs, signal);
|
|
1522
1743
|
return extractOpenAiResult(completion, true);
|
|
1523
1744
|
}
|
|
1524
1745
|
};
|
|
1525
1746
|
}
|
|
1526
|
-
function
|
|
1527
|
-
return
|
|
1528
|
-
|
|
1529
|
-
signal
|
|
1747
|
+
function callClient4(client, body, timeoutMs, signal) {
|
|
1748
|
+
return withRequestTimeout(
|
|
1749
|
+
timeoutMs,
|
|
1750
|
+
signal,
|
|
1751
|
+
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal })
|
|
1530
1752
|
);
|
|
1531
1753
|
}
|
|
1532
1754
|
var SCAFFOLD_MODELS = {
|
|
@@ -1736,6 +1958,8 @@ var LOCAL_DIR_NAME = ".verbatra-local";
|
|
|
1736
1958
|
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
1737
1959
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
1738
1960
|
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
1961
|
+
var WAIT_NOTICE_INTERVAL_MS = 1e3;
|
|
1962
|
+
var MAX_LOCK_PAYLOAD_BYTES = 64 * 1024;
|
|
1739
1963
|
function lockPath(cwd, stem) {
|
|
1740
1964
|
return resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
1741
1965
|
}
|
|
@@ -1753,7 +1977,47 @@ function sleep(ms) {
|
|
|
1753
1977
|
function lockPayload() {
|
|
1754
1978
|
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1755
1979
|
}
|
|
1756
|
-
|
|
1980
|
+
function parseHolder(read) {
|
|
1981
|
+
if (read.kind !== "ok") {
|
|
1982
|
+
return void 0;
|
|
1983
|
+
}
|
|
1984
|
+
let parsed;
|
|
1985
|
+
try {
|
|
1986
|
+
parsed = JSON.parse(read.content);
|
|
1987
|
+
} catch {
|
|
1988
|
+
return void 0;
|
|
1989
|
+
}
|
|
1990
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
1991
|
+
return void 0;
|
|
1992
|
+
}
|
|
1993
|
+
const record = parsed;
|
|
1994
|
+
const holder = {};
|
|
1995
|
+
if (typeof record.pid === "number") {
|
|
1996
|
+
holder.pid = record.pid;
|
|
1997
|
+
}
|
|
1998
|
+
if (typeof record.acquiredAt === "string") {
|
|
1999
|
+
holder.acquiredAt = record.acquiredAt;
|
|
2000
|
+
}
|
|
2001
|
+
return holder;
|
|
2002
|
+
}
|
|
2003
|
+
function makeWaitNotifier(path, fs, onWait, start) {
|
|
2004
|
+
let holder;
|
|
2005
|
+
let holderRead = false;
|
|
2006
|
+
let lastEmit;
|
|
2007
|
+
return async () => {
|
|
2008
|
+
if (!holderRead) {
|
|
2009
|
+
holderRead = true;
|
|
2010
|
+
holder = parseHolder(await fs.readFileBounded(path, MAX_LOCK_PAYLOAD_BYTES));
|
|
2011
|
+
}
|
|
2012
|
+
const elapsedMs = Date.now() - start;
|
|
2013
|
+
if (lastEmit !== void 0 && elapsedMs - lastEmit < WAIT_NOTICE_INTERVAL_MS) {
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
lastEmit = elapsedMs;
|
|
2017
|
+
onWait({ lockPath: path, elapsedMs, ...holder !== void 0 ? { holder } : {} });
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
async function acquireLock(path, fs, pollIntervalMs, deadline, notify) {
|
|
1757
2021
|
for (; ; ) {
|
|
1758
2022
|
if (await fs.createExclusive(path, lockPayload())) {
|
|
1759
2023
|
return;
|
|
@@ -1764,6 +2028,9 @@ async function acquireLock(path, fs, pollIntervalMs, deadline) {
|
|
|
1764
2028
|
`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.`
|
|
1765
2029
|
);
|
|
1766
2030
|
}
|
|
2031
|
+
if (notify !== void 0) {
|
|
2032
|
+
await notify();
|
|
2033
|
+
}
|
|
1767
2034
|
const jitter = Math.random() * pollIntervalMs;
|
|
1768
2035
|
await sleep(pollIntervalMs + jitter);
|
|
1769
2036
|
}
|
|
@@ -1771,7 +2038,9 @@ async function acquireLock(path, fs, pollIntervalMs, deadline) {
|
|
|
1771
2038
|
async function withFileLock(path, fs, fn, options) {
|
|
1772
2039
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1773
2040
|
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
1774
|
-
|
|
2041
|
+
const start = Date.now();
|
|
2042
|
+
const notify = options.onWait !== void 0 ? makeWaitNotifier(path, fs, options.onWait, start) : void 0;
|
|
2043
|
+
await acquireLock(path, fs, pollIntervalMs, start + acquireTimeoutMs, notify);
|
|
1775
2044
|
try {
|
|
1776
2045
|
return await fn();
|
|
1777
2046
|
} finally {
|
|
@@ -1787,8 +2056,8 @@ async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
|
1787
2056
|
|
|
1788
2057
|
// src/lock/lock-file.ts
|
|
1789
2058
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1790
|
-
var
|
|
1791
|
-
var EMPTY_LOCK = { version:
|
|
2059
|
+
var CURRENT_VERSION2 = 1;
|
|
2060
|
+
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
1792
2061
|
var MAX_LOCK_FILE_BYTES = 16 * 1024 * 1024;
|
|
1793
2062
|
var lockFileSchema = z.object({
|
|
1794
2063
|
version: z.number().int().positive(),
|
|
@@ -1817,10 +2086,10 @@ function parseLockFileRead(read, path) {
|
|
|
1817
2086
|
if (!result.success) {
|
|
1818
2087
|
throw new SdkError("LOCK_FILE_INVALID", `The lock-file at ${path} has an unexpected shape.`);
|
|
1819
2088
|
}
|
|
1820
|
-
if (result.data.version !==
|
|
2089
|
+
if (result.data.version !== CURRENT_VERSION2) {
|
|
1821
2090
|
throw new SdkError(
|
|
1822
2091
|
"LOCK_FILE_INVALID",
|
|
1823
|
-
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${
|
|
2092
|
+
`The lock-file at ${path} has version ${result.data.version}, but this version of verbatra supports version ${CURRENT_VERSION2}.`
|
|
1824
2093
|
);
|
|
1825
2094
|
}
|
|
1826
2095
|
return result.data;
|
|
@@ -2610,7 +2879,7 @@ function createTreeFileAdapter(options) {
|
|
|
2610
2879
|
extensions,
|
|
2611
2880
|
sniff,
|
|
2612
2881
|
parse: parse3,
|
|
2613
|
-
serialize,
|
|
2882
|
+
serialize: serialize2,
|
|
2614
2883
|
deriveEntry,
|
|
2615
2884
|
extractPlaceholders,
|
|
2616
2885
|
computeInvalidIcuKeys,
|
|
@@ -2645,7 +2914,7 @@ function createTreeFileAdapter(options) {
|
|
|
2645
2914
|
},
|
|
2646
2915
|
async write(resource, filePath) {
|
|
2647
2916
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
2648
|
-
await atomicWriteFile(filePath,
|
|
2917
|
+
await atomicWriteFile(filePath, serialize2(tree));
|
|
2649
2918
|
}
|
|
2650
2919
|
};
|
|
2651
2920
|
}
|
|
@@ -2864,42 +3133,429 @@ async function detectStyle(filePath) {
|
|
|
2864
3133
|
if (outcome.kind !== "ok") {
|
|
2865
3134
|
return "nested";
|
|
2866
3135
|
}
|
|
2867
|
-
parsed = JSON.parse(outcome.content);
|
|
2868
|
-
} catch {
|
|
2869
|
-
return "nested";
|
|
3136
|
+
parsed = JSON.parse(outcome.content);
|
|
3137
|
+
} catch {
|
|
3138
|
+
return "nested";
|
|
3139
|
+
}
|
|
3140
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3141
|
+
return "nested";
|
|
3142
|
+
}
|
|
3143
|
+
for (const value of Object.values(parsed)) {
|
|
3144
|
+
if (typeof value === "object" && value !== null) {
|
|
3145
|
+
return "nested";
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
return "flat";
|
|
3149
|
+
}
|
|
3150
|
+
function buildFlatTree(entries) {
|
|
3151
|
+
const out = /* @__PURE__ */ new Map();
|
|
3152
|
+
for (const [key, entry] of entries) {
|
|
3153
|
+
out.set(key, entry.value);
|
|
3154
|
+
}
|
|
3155
|
+
return out;
|
|
3156
|
+
}
|
|
3157
|
+
async function buildNgxWriteTree(entries, filePath) {
|
|
3158
|
+
const style = await detectStyle(filePath);
|
|
3159
|
+
return style === "flat" ? buildFlatTree(entries) : unflattenEntries(entries);
|
|
3160
|
+
}
|
|
3161
|
+
function createNgxTranslateJsonAdapter() {
|
|
3162
|
+
return createJsonFileAdapter({
|
|
3163
|
+
format: "ngx-translate-json",
|
|
3164
|
+
extractPlaceholders: extractDoubleBracePlaceholders,
|
|
3165
|
+
deriveEntry: (_key, value) => ({
|
|
3166
|
+
placeholders: extractDoubleBracePlaceholders(value),
|
|
3167
|
+
isPlural: false
|
|
3168
|
+
}),
|
|
3169
|
+
validateTree: assertNotMixed,
|
|
3170
|
+
buildWriteTree: buildNgxWriteTree,
|
|
3171
|
+
keyMode: "path-notation"
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
3175
|
+
try {
|
|
3176
|
+
return parseEntries(content, namespace);
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
function createFlatFileAdapter(options) {
|
|
3182
|
+
const {
|
|
3183
|
+
format,
|
|
3184
|
+
extensions,
|
|
3185
|
+
sniff,
|
|
3186
|
+
parseEntries,
|
|
3187
|
+
serializeEntries,
|
|
3188
|
+
extractPlaceholders,
|
|
3189
|
+
validateMessage,
|
|
3190
|
+
computeInvalidIcuKeys
|
|
3191
|
+
} = options;
|
|
3192
|
+
return {
|
|
3193
|
+
format,
|
|
3194
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
3195
|
+
extractPlaceholders,
|
|
3196
|
+
validateMessage: validateMessage ?? (() => true),
|
|
3197
|
+
async read(filePath, locale) {
|
|
3198
|
+
const content = await readFileContent(filePath);
|
|
3199
|
+
const namespace = namespaceOf(filePath);
|
|
3200
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
3201
|
+
const resource = { locale, namespace, format, entries };
|
|
3202
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3203
|
+
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3204
|
+
},
|
|
3205
|
+
async write(resource, filePath) {
|
|
3206
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
3207
|
+
await atomicWriteFile(filePath, data);
|
|
3208
|
+
}
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordinal", "choice"]);
|
|
3212
|
+
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3213
|
+
function matchingBraces(value) {
|
|
3214
|
+
const close = new Int32Array(value.length).fill(-1);
|
|
3215
|
+
const open3 = [];
|
|
3216
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
3217
|
+
const char = value[i];
|
|
3218
|
+
if (char === "{") {
|
|
3219
|
+
open3.push(i);
|
|
3220
|
+
} else if (char === "}") {
|
|
3221
|
+
const start = open3.pop();
|
|
3222
|
+
if (start !== void 0) {
|
|
3223
|
+
close[start] = i;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
return close;
|
|
3228
|
+
}
|
|
3229
|
+
function findNameEnd(value, from, limit) {
|
|
3230
|
+
for (let i = from; i < limit; i += 1) {
|
|
3231
|
+
const char = value[i];
|
|
3232
|
+
if (char === ",") {
|
|
3233
|
+
return { comma: i, hasBrace: false };
|
|
3234
|
+
}
|
|
3235
|
+
if (char === "{" || char === "}") {
|
|
3236
|
+
return { comma: -1, hasBrace: true };
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
return { comma: -1, hasBrace: false };
|
|
3240
|
+
}
|
|
3241
|
+
function parseArgumentAt(value, open3, close) {
|
|
3242
|
+
const { comma, hasBrace } = findNameEnd(value, open3 + 1, close);
|
|
3243
|
+
if (hasBrace) {
|
|
3244
|
+
return null;
|
|
3245
|
+
}
|
|
3246
|
+
const nameEnd = comma === -1 ? close : comma;
|
|
3247
|
+
const name = value.slice(open3 + 1, nameEnd).trim();
|
|
3248
|
+
if (!ARGUMENT_NAME.test(name)) {
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
if (comma === -1) {
|
|
3252
|
+
return { name, type: null, styleStart: -1 };
|
|
3253
|
+
}
|
|
3254
|
+
const secondComma = value.indexOf(",", comma + 1);
|
|
3255
|
+
if (secondComma === -1 || secondComma >= close) {
|
|
3256
|
+
return { name, type: value.slice(comma + 1, close).trim(), styleStart: -1 };
|
|
3257
|
+
}
|
|
3258
|
+
return { name, type: value.slice(comma + 1, secondComma).trim(), styleStart: secondComma + 1 };
|
|
3259
|
+
}
|
|
3260
|
+
function canonicalToken(value, arg, close) {
|
|
3261
|
+
if (arg.type === null) {
|
|
3262
|
+
return `{${arg.name}}`;
|
|
3263
|
+
}
|
|
3264
|
+
if (arg.styleStart === -1) {
|
|
3265
|
+
return `{${arg.name},${arg.type}}`;
|
|
3266
|
+
}
|
|
3267
|
+
return `{${arg.name},${arg.type},${value.slice(arg.styleStart, close).trim()}}`;
|
|
3268
|
+
}
|
|
3269
|
+
function emitArgument(value, close, arg, closeIndex, out) {
|
|
3270
|
+
if (arg.type !== null && SUBMESSAGE_TYPES.has(arg.type)) {
|
|
3271
|
+
out.push(`{${arg.name},${arg.type}}`);
|
|
3272
|
+
if (arg.styleStart !== -1) {
|
|
3273
|
+
scanRange(value, close, arg.styleStart, closeIndex, false, out);
|
|
3274
|
+
}
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
out.push(canonicalToken(value, arg, closeIndex));
|
|
3278
|
+
}
|
|
3279
|
+
function scanRange(value, close, start, end, topLevel, out) {
|
|
3280
|
+
let i = start;
|
|
3281
|
+
while (i < end) {
|
|
3282
|
+
if (value[i] !== "{") {
|
|
3283
|
+
i += 1;
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
if (topLevel && value[i + 1] === "{") {
|
|
3287
|
+
i += 2;
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
const closeIndex = close[i] ?? -1;
|
|
3291
|
+
if (closeIndex === -1 || closeIndex >= end) {
|
|
3292
|
+
i += 1;
|
|
3293
|
+
continue;
|
|
3294
|
+
}
|
|
3295
|
+
const arg = parseArgumentAt(value, i, closeIndex);
|
|
3296
|
+
if (arg === null) {
|
|
3297
|
+
i += 1;
|
|
3298
|
+
continue;
|
|
3299
|
+
}
|
|
3300
|
+
emitArgument(value, close, arg, closeIndex, out);
|
|
3301
|
+
i = closeIndex + 1;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function extractPropertiesPlaceholders(value) {
|
|
3305
|
+
const out = [];
|
|
3306
|
+
scanRange(value, matchingBraces(value), 0, value.length, true, out);
|
|
3307
|
+
return out;
|
|
3308
|
+
}
|
|
3309
|
+
var UNICODE_ESCAPE = /^[0-9a-fA-F]{4}$/;
|
|
3310
|
+
var LEADING_WHITESPACE = /^[ \t\f]+/;
|
|
3311
|
+
var TRAILING_TERMINATOR = /(?:\r\n|\r|\n)$/;
|
|
3312
|
+
function isPropertiesWhitespace(char) {
|
|
3313
|
+
return char === " " || char === " " || char === "\f";
|
|
3314
|
+
}
|
|
3315
|
+
function splitPhysicalLines(content) {
|
|
3316
|
+
if (content === "") {
|
|
3317
|
+
return [];
|
|
3318
|
+
}
|
|
3319
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
3320
|
+
if (TRAILING_TERMINATOR.test(content)) {
|
|
3321
|
+
lines.pop();
|
|
3322
|
+
}
|
|
3323
|
+
return lines;
|
|
3324
|
+
}
|
|
3325
|
+
function countTrailingBackslashes(line) {
|
|
3326
|
+
let count = 0;
|
|
3327
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i -= 1) {
|
|
3328
|
+
count += 1;
|
|
3329
|
+
}
|
|
3330
|
+
return count;
|
|
3331
|
+
}
|
|
3332
|
+
function isContinued(line) {
|
|
3333
|
+
return countTrailingBackslashes(line) % 2 === 1;
|
|
3334
|
+
}
|
|
3335
|
+
function isBlankLine(line) {
|
|
3336
|
+
return line.trim() === "";
|
|
3337
|
+
}
|
|
3338
|
+
function isCommentLine(line) {
|
|
3339
|
+
const trimmed = line.replace(LEADING_WHITESPACE, "");
|
|
3340
|
+
return trimmed.startsWith("#") || trimmed.startsWith("!");
|
|
3341
|
+
}
|
|
3342
|
+
function joinContinuation(lines, start) {
|
|
3343
|
+
const pieces = [];
|
|
3344
|
+
let index = start;
|
|
3345
|
+
let current = lines[start] ?? "";
|
|
3346
|
+
while (isContinued(current) && index + 1 < lines.length) {
|
|
3347
|
+
pieces.push(current.slice(0, -1));
|
|
3348
|
+
index += 1;
|
|
3349
|
+
current = (lines[index] ?? "").replace(LEADING_WHITESPACE, "");
|
|
3350
|
+
}
|
|
3351
|
+
if (isContinued(current)) {
|
|
3352
|
+
current = current.slice(0, -1);
|
|
3353
|
+
}
|
|
3354
|
+
pieces.push(current);
|
|
3355
|
+
return { logical: pieces.join(""), nextIndex: index + 1 };
|
|
3356
|
+
}
|
|
3357
|
+
function decodeSimpleEscape(char) {
|
|
3358
|
+
switch (char) {
|
|
3359
|
+
case "t":
|
|
3360
|
+
return " ";
|
|
3361
|
+
case "n":
|
|
3362
|
+
return "\n";
|
|
3363
|
+
case "r":
|
|
3364
|
+
return "\r";
|
|
3365
|
+
case "f":
|
|
3366
|
+
return "\f";
|
|
3367
|
+
default:
|
|
3368
|
+
return char;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
function decodeUnicodeEscape(raw, at) {
|
|
3372
|
+
const hex = raw.slice(at, at + 4);
|
|
3373
|
+
if (!UNICODE_ESCAPE.test(hex)) {
|
|
3374
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file has a malformed unicode escape.");
|
|
3375
|
+
}
|
|
3376
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
3377
|
+
}
|
|
3378
|
+
function decodeEscapes(raw) {
|
|
3379
|
+
let out = "";
|
|
3380
|
+
let i = 0;
|
|
3381
|
+
while (i < raw.length) {
|
|
3382
|
+
const char = raw[i];
|
|
3383
|
+
if (char !== "\\") {
|
|
3384
|
+
out += char;
|
|
3385
|
+
i += 1;
|
|
3386
|
+
continue;
|
|
3387
|
+
}
|
|
3388
|
+
const next = raw[i + 1];
|
|
3389
|
+
if (next === void 0) {
|
|
3390
|
+
break;
|
|
3391
|
+
}
|
|
3392
|
+
if (next === "u") {
|
|
3393
|
+
out += decodeUnicodeEscape(raw, i + 2);
|
|
3394
|
+
i += 6;
|
|
3395
|
+
continue;
|
|
3396
|
+
}
|
|
3397
|
+
out += decodeSimpleEscape(next);
|
|
3398
|
+
i += 2;
|
|
3399
|
+
}
|
|
3400
|
+
return out;
|
|
3401
|
+
}
|
|
3402
|
+
function keyEndIndex(logical, from) {
|
|
3403
|
+
let i = from;
|
|
3404
|
+
while (i < logical.length) {
|
|
3405
|
+
const char = logical[i];
|
|
3406
|
+
if (char === "\\") {
|
|
3407
|
+
i += 2;
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
if (char === void 0 || isPropertiesWhitespace(char) || char === "=" || char === ":") {
|
|
3411
|
+
break;
|
|
3412
|
+
}
|
|
3413
|
+
i += 1;
|
|
3414
|
+
}
|
|
3415
|
+
return i;
|
|
3416
|
+
}
|
|
3417
|
+
function skipWhitespace(logical, from) {
|
|
3418
|
+
let i = from;
|
|
3419
|
+
while (i < logical.length) {
|
|
3420
|
+
const char = logical[i];
|
|
3421
|
+
if (char === void 0 || !isPropertiesWhitespace(char)) {
|
|
3422
|
+
break;
|
|
3423
|
+
}
|
|
3424
|
+
i += 1;
|
|
3425
|
+
}
|
|
3426
|
+
return i;
|
|
3427
|
+
}
|
|
3428
|
+
function parseEntryLine(logical) {
|
|
3429
|
+
const keyStart = skipWhitespace(logical, 0);
|
|
3430
|
+
const keyEnd = keyEndIndex(logical, keyStart);
|
|
3431
|
+
const afterKey = skipWhitespace(logical, keyEnd);
|
|
3432
|
+
const separator = logical[afterKey];
|
|
3433
|
+
const valueStart = separator === "=" || separator === ":" ? skipWhitespace(logical, afterKey + 1) : afterKey;
|
|
3434
|
+
return {
|
|
3435
|
+
key: decodeEscapes(logical.slice(keyStart, keyEnd)),
|
|
3436
|
+
value: decodeEscapes(logical.slice(valueStart))
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
function parseItems(content) {
|
|
3440
|
+
const lines = splitPhysicalLines(content);
|
|
3441
|
+
const items = [];
|
|
3442
|
+
let i = 0;
|
|
3443
|
+
while (i < lines.length) {
|
|
3444
|
+
const line = lines[i] ?? "";
|
|
3445
|
+
if (isBlankLine(line) || isCommentLine(line)) {
|
|
3446
|
+
items.push({ kind: "raw", text: line });
|
|
3447
|
+
i += 1;
|
|
3448
|
+
continue;
|
|
3449
|
+
}
|
|
3450
|
+
const { logical, nextIndex } = joinContinuation(lines, i);
|
|
3451
|
+
items.push({ kind: "entry", ...parseEntryLine(logical) });
|
|
3452
|
+
i = nextIndex;
|
|
3453
|
+
}
|
|
3454
|
+
return items;
|
|
3455
|
+
}
|
|
3456
|
+
function parsePropertiesEntries(content, namespace) {
|
|
3457
|
+
const map = /* @__PURE__ */ new Map();
|
|
3458
|
+
for (const item of parseItems(content)) {
|
|
3459
|
+
if (item.kind === "entry") {
|
|
3460
|
+
map.set(item.key, {
|
|
3461
|
+
key: item.key,
|
|
3462
|
+
namespace,
|
|
3463
|
+
value: item.value,
|
|
3464
|
+
placeholders: extractPropertiesPlaceholders(item.value),
|
|
3465
|
+
isPlural: false
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
return map;
|
|
3470
|
+
}
|
|
3471
|
+
function unicodeEscape(code) {
|
|
3472
|
+
return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3473
|
+
}
|
|
3474
|
+
function escapeChar(char, isFirst, isKey) {
|
|
3475
|
+
switch (char) {
|
|
3476
|
+
case "\\":
|
|
3477
|
+
return "\\\\";
|
|
3478
|
+
case " ":
|
|
3479
|
+
return "\\t";
|
|
3480
|
+
case "\n":
|
|
3481
|
+
return "\\n";
|
|
3482
|
+
case "\r":
|
|
3483
|
+
return "\\r";
|
|
3484
|
+
case "\f":
|
|
3485
|
+
return "\\f";
|
|
3486
|
+
case " ":
|
|
3487
|
+
return isKey || isFirst ? "\\ " : " ";
|
|
3488
|
+
}
|
|
3489
|
+
if (isKey && (char === "=" || char === ":" || char === "#" || char === "!")) {
|
|
3490
|
+
return `\\${char}`;
|
|
3491
|
+
}
|
|
3492
|
+
const code = char.charCodeAt(0);
|
|
3493
|
+
return code < 32 || code > 126 ? unicodeEscape(code) : char;
|
|
3494
|
+
}
|
|
3495
|
+
function escapeString(input, isKey) {
|
|
3496
|
+
let out = "";
|
|
3497
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
3498
|
+
const char = input[i];
|
|
3499
|
+
if (char !== void 0) {
|
|
3500
|
+
out += escapeChar(char, i === 0, isKey);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
return out;
|
|
3504
|
+
}
|
|
3505
|
+
function formatEntry(key, value) {
|
|
3506
|
+
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3507
|
+
}
|
|
3508
|
+
function isFileNotFound(error) {
|
|
3509
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3510
|
+
}
|
|
3511
|
+
async function readStructure(filePath) {
|
|
3512
|
+
let outcome;
|
|
3513
|
+
try {
|
|
3514
|
+
outcome = await readBounded2(filePath);
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
if (isFileNotFound(error)) {
|
|
3517
|
+
return [];
|
|
3518
|
+
}
|
|
3519
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination file could not be read.");
|
|
2870
3520
|
}
|
|
2871
|
-
if (
|
|
2872
|
-
|
|
3521
|
+
if (outcome.kind === "not-a-file") {
|
|
3522
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
2873
3523
|
}
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
return "nested";
|
|
2877
|
-
}
|
|
3524
|
+
if (outcome.kind === "too-large") {
|
|
3525
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
2878
3526
|
}
|
|
2879
|
-
return
|
|
3527
|
+
return parseItems(outcome.content);
|
|
2880
3528
|
}
|
|
2881
|
-
function
|
|
2882
|
-
const
|
|
3529
|
+
async function serializePropertiesEntries(entries, filePath) {
|
|
3530
|
+
const structure = await readStructure(filePath);
|
|
3531
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3532
|
+
const lines = [];
|
|
3533
|
+
for (const item of structure) {
|
|
3534
|
+
if (item.kind === "raw") {
|
|
3535
|
+
lines.push(item.text);
|
|
3536
|
+
continue;
|
|
3537
|
+
}
|
|
3538
|
+
const entry = entries.get(item.key);
|
|
3539
|
+
if (entry !== void 0 && !emitted.has(item.key)) {
|
|
3540
|
+
lines.push(formatEntry(item.key, entry.value));
|
|
3541
|
+
emitted.add(item.key);
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
2883
3544
|
for (const [key, entry] of entries) {
|
|
2884
|
-
|
|
3545
|
+
if (!emitted.has(key)) {
|
|
3546
|
+
lines.push(formatEntry(key, entry.value));
|
|
3547
|
+
}
|
|
2885
3548
|
}
|
|
2886
|
-
return
|
|
2887
|
-
|
|
2888
|
-
async function buildNgxWriteTree(entries, filePath) {
|
|
2889
|
-
const style = await detectStyle(filePath);
|
|
2890
|
-
return style === "flat" ? buildFlatTree(entries) : unflattenEntries(entries);
|
|
3549
|
+
return lines.length === 0 ? "" : `${lines.join("\n")}
|
|
3550
|
+
`;
|
|
2891
3551
|
}
|
|
2892
|
-
function
|
|
2893
|
-
return
|
|
2894
|
-
format: "
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
}),
|
|
2900
|
-
validateTree: assertNotMixed,
|
|
2901
|
-
buildWriteTree: buildNgxWriteTree,
|
|
2902
|
-
keyMode: "path-notation"
|
|
3552
|
+
function createPropertiesAdapter() {
|
|
3553
|
+
return createFlatFileAdapter({
|
|
3554
|
+
format: "properties",
|
|
3555
|
+
extensions: [".properties"],
|
|
3556
|
+
parseEntries: parsePropertiesEntries,
|
|
3557
|
+
serializeEntries: serializePropertiesEntries,
|
|
3558
|
+
extractPlaceholders: extractPropertiesPlaceholders
|
|
2903
3559
|
});
|
|
2904
3560
|
}
|
|
2905
3561
|
var AdapterRegistry = class {
|
|
@@ -2979,43 +3635,6 @@ function createVueI18nJsonAdapter() {
|
|
|
2979
3635
|
})
|
|
2980
3636
|
});
|
|
2981
3637
|
}
|
|
2982
|
-
function toEntries2(content, namespace, parseEntries) {
|
|
2983
|
-
try {
|
|
2984
|
-
return parseEntries(content, namespace);
|
|
2985
|
-
} catch (error) {
|
|
2986
|
-
rethrowStructured(error, "The file could not be parsed.");
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
function createFlatFileAdapter(options) {
|
|
2990
|
-
const {
|
|
2991
|
-
format,
|
|
2992
|
-
extensions,
|
|
2993
|
-
sniff,
|
|
2994
|
-
parseEntries,
|
|
2995
|
-
serializeEntries,
|
|
2996
|
-
extractPlaceholders,
|
|
2997
|
-
validateMessage,
|
|
2998
|
-
computeInvalidIcuKeys
|
|
2999
|
-
} = options;
|
|
3000
|
-
return {
|
|
3001
|
-
format,
|
|
3002
|
-
canHandle: buildCanHandle(extensions, sniff),
|
|
3003
|
-
extractPlaceholders,
|
|
3004
|
-
validateMessage: validateMessage ?? (() => true),
|
|
3005
|
-
async read(filePath, locale) {
|
|
3006
|
-
const content = await readFileContent(filePath);
|
|
3007
|
-
const namespace = namespaceOf(filePath);
|
|
3008
|
-
const entries = toEntries2(content, namespace, parseEntries);
|
|
3009
|
-
const resource = { locale, namespace, format, entries };
|
|
3010
|
-
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
3011
|
-
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3012
|
-
},
|
|
3013
|
-
async write(resource, filePath) {
|
|
3014
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3015
|
-
await atomicWriteFile(filePath, data);
|
|
3016
|
-
}
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
3638
|
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
3020
3639
|
function extractXliffPlaceholders(value) {
|
|
3021
3640
|
const result = [];
|
|
@@ -3326,7 +3945,7 @@ function createYamlAdapter() {
|
|
|
3326
3945
|
});
|
|
3327
3946
|
}
|
|
3328
3947
|
function createDefaultRegistry() {
|
|
3329
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
3948
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter()).register(createPropertiesAdapter());
|
|
3330
3949
|
}
|
|
3331
3950
|
|
|
3332
3951
|
// src/selection/select-adapter.ts
|
|
@@ -3447,6 +4066,28 @@ async function diff(input, deps = {}) {
|
|
|
3447
4066
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3448
4067
|
}
|
|
3449
4068
|
|
|
4069
|
+
// src/cache/fingerprint.ts
|
|
4070
|
+
function fingerprintModel(provider) {
|
|
4071
|
+
const options = provider.options;
|
|
4072
|
+
const model = options.model;
|
|
4073
|
+
return typeof model === "string" ? model : null;
|
|
4074
|
+
}
|
|
4075
|
+
function sortGlossary(glossary) {
|
|
4076
|
+
if (glossary === void 0) {
|
|
4077
|
+
return {};
|
|
4078
|
+
}
|
|
4079
|
+
return Object.fromEntries(Object.entries(glossary).sort(([a], [b]) => a < b ? -1 : 1));
|
|
4080
|
+
}
|
|
4081
|
+
function computeFingerprint(config) {
|
|
4082
|
+
const canonical = JSON.stringify({
|
|
4083
|
+
provider: config.provider.id,
|
|
4084
|
+
model: fingerprintModel(config.provider),
|
|
4085
|
+
tone: config.tone ?? null,
|
|
4086
|
+
glossary: sortGlossary(config.glossary)
|
|
4087
|
+
});
|
|
4088
|
+
return stableStringHash(canonical);
|
|
4089
|
+
}
|
|
4090
|
+
|
|
3450
4091
|
// src/flow/integrity-gate.ts
|
|
3451
4092
|
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3452
4093
|
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
@@ -3456,6 +4097,9 @@ function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
|
3456
4097
|
if (!adapter.validateMessage(candidateValue)) {
|
|
3457
4098
|
return { accepted: false, reason: "icu" };
|
|
3458
4099
|
}
|
|
4100
|
+
if (assessValueDegeneracy(sourceEntry.value, candidateValue).degenerate) {
|
|
4101
|
+
return { accepted: false, reason: "degenerate" };
|
|
4102
|
+
}
|
|
3459
4103
|
return { accepted: true };
|
|
3460
4104
|
}
|
|
3461
4105
|
|
|
@@ -3494,6 +4138,12 @@ async function editEntry(input, deps = {}) {
|
|
|
3494
4138
|
mode: "merge",
|
|
3495
4139
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3496
4140
|
});
|
|
4141
|
+
await feedTranslationMemory(
|
|
4142
|
+
cwd,
|
|
4143
|
+
fs,
|
|
4144
|
+
computeFingerprint(config),
|
|
4145
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: input.value }]])
|
|
4146
|
+
);
|
|
3497
4147
|
return { accepted: true, value: input.value };
|
|
3498
4148
|
});
|
|
3499
4149
|
}
|
|
@@ -3711,13 +4361,19 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
3711
4361
|
mode: "merge",
|
|
3712
4362
|
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3713
4363
|
});
|
|
4364
|
+
await feedTranslationMemory(
|
|
4365
|
+
cwd,
|
|
4366
|
+
fs,
|
|
4367
|
+
computeFingerprint(config),
|
|
4368
|
+
/* @__PURE__ */ new Map([[locale, { [contentHash(sourceEntry)]: value }]])
|
|
4369
|
+
);
|
|
3714
4370
|
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3715
4371
|
return { accepted: true, value, reviewReasons };
|
|
3716
4372
|
});
|
|
3717
4373
|
}
|
|
3718
4374
|
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3719
4375
|
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3720
|
-
var
|
|
4376
|
+
var CURRENT_VERSION3 = 1;
|
|
3721
4377
|
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3722
4378
|
var reviewReasonCodeSchema = z.enum([
|
|
3723
4379
|
"LENGTH_RATIO_OUTLIER",
|
|
@@ -3743,7 +4399,7 @@ var runBudgetSchema = z.object({
|
|
|
3743
4399
|
});
|
|
3744
4400
|
var runStatusLocaleSchema = z.object({
|
|
3745
4401
|
locale: z.string(),
|
|
3746
|
-
status: z.enum(["succeeded", "failed"]),
|
|
4402
|
+
status: z.enum(["succeeded", "partial", "failed"]),
|
|
3747
4403
|
needsReview: z.array(needsReviewEntrySchema),
|
|
3748
4404
|
usage: usageSummarySchema.optional()
|
|
3749
4405
|
});
|
|
@@ -3767,7 +4423,7 @@ function toRunStatusLocale(locale) {
|
|
|
3767
4423
|
}
|
|
3768
4424
|
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3769
4425
|
return {
|
|
3770
|
-
version:
|
|
4426
|
+
version: CURRENT_VERSION3,
|
|
3771
4427
|
generatedAt,
|
|
3772
4428
|
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3773
4429
|
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
@@ -3800,7 +4456,7 @@ async function readRunStatusFile(path, fs) {
|
|
|
3800
4456
|
return void 0;
|
|
3801
4457
|
}
|
|
3802
4458
|
const result = runStatusFileSchema.safeParse(parsed);
|
|
3803
|
-
if (!result.success || result.data.version !==
|
|
4459
|
+
if (!result.success || result.data.version !== CURRENT_VERSION3) {
|
|
3804
4460
|
return void 0;
|
|
3805
4461
|
}
|
|
3806
4462
|
return fromParsed(result.data);
|
|
@@ -3879,19 +4535,34 @@ function failureSummary(locale, error) {
|
|
|
3879
4535
|
orphaned: [],
|
|
3880
4536
|
pruned: [],
|
|
3881
4537
|
invalidIcuSource: [],
|
|
4538
|
+
cacheHits: [],
|
|
3882
4539
|
integrityMismatches: [],
|
|
3883
4540
|
providerFailures: [],
|
|
3884
4541
|
budgetWithheld: [],
|
|
3885
4542
|
generated: [],
|
|
3886
4543
|
notices: [],
|
|
3887
4544
|
needsReview: [],
|
|
4545
|
+
unfilled: [],
|
|
4546
|
+
malformedRows: [],
|
|
4547
|
+
duplicateKeys: [],
|
|
3888
4548
|
error: describeError(error)
|
|
3889
4549
|
};
|
|
3890
4550
|
}
|
|
4551
|
+
function deriveLocaleStatus(parts) {
|
|
4552
|
+
const withheld = parts.integrityMismatches.length > 0 || parts.providerFailures.length > 0 || parts.budgetWithheld.length > 0;
|
|
4553
|
+
if (!withheld) {
|
|
4554
|
+
return "succeeded";
|
|
4555
|
+
}
|
|
4556
|
+
const accepted = parts.translated.length > 0 || parts.cacheHits.length > 0 || parts.generated.length > 0;
|
|
4557
|
+
return accepted ? "partial" : "failed";
|
|
4558
|
+
}
|
|
3891
4559
|
function partition(locales) {
|
|
3892
|
-
const
|
|
3893
|
-
|
|
3894
|
-
|
|
4560
|
+
const namesWith = (status) => locales.filter((s) => s.status === status).map((s) => s.locale);
|
|
4561
|
+
return {
|
|
4562
|
+
succeeded: namesWith("succeeded"),
|
|
4563
|
+
partial: namesWith("partial"),
|
|
4564
|
+
failed: namesWith("failed")
|
|
4565
|
+
};
|
|
3895
4566
|
}
|
|
3896
4567
|
|
|
3897
4568
|
// src/flow/batching.ts
|
|
@@ -4202,6 +4873,97 @@ function foldGenerationItem(item, result, adapter, accepted, withheld, providerF
|
|
|
4202
4873
|
}
|
|
4203
4874
|
|
|
4204
4875
|
// src/flow/locale-run.ts
|
|
4876
|
+
function partitionCacheHits(params, toTranslate) {
|
|
4877
|
+
const cache = params.cache;
|
|
4878
|
+
const hits = /* @__PURE__ */ new Map();
|
|
4879
|
+
if (cache === void 0) {
|
|
4880
|
+
return { hits, misses: toTranslate };
|
|
4881
|
+
}
|
|
4882
|
+
const misses = [];
|
|
4883
|
+
for (const key of toTranslate) {
|
|
4884
|
+
const source = params.source.entries.get(key);
|
|
4885
|
+
if (source === void 0) {
|
|
4886
|
+
continue;
|
|
4887
|
+
}
|
|
4888
|
+
const cached = lookupMemory(
|
|
4889
|
+
cache.snapshot,
|
|
4890
|
+
cache.fingerprint,
|
|
4891
|
+
params.targetLocale,
|
|
4892
|
+
contentHash(source)
|
|
4893
|
+
);
|
|
4894
|
+
if (cached !== void 0 && gateCandidateValue(source, cached, params.adapter).accepted) {
|
|
4895
|
+
hits.set(key, { value: cached, source });
|
|
4896
|
+
} else {
|
|
4897
|
+
misses.push(key);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
return { hits, misses };
|
|
4901
|
+
}
|
|
4902
|
+
function collectCacheAdditions(params, accepted, cacheHitKeys) {
|
|
4903
|
+
if (params.cache === void 0) {
|
|
4904
|
+
return [];
|
|
4905
|
+
}
|
|
4906
|
+
const additions = [];
|
|
4907
|
+
for (const [key, entry] of accepted) {
|
|
4908
|
+
if (!cacheHitKeys.has(key)) {
|
|
4909
|
+
additions.push({ contentHash: contentHash(entry.source), value: entry.value });
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
return additions;
|
|
4913
|
+
}
|
|
4914
|
+
function groupMissesByContent(params, misses) {
|
|
4915
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4916
|
+
for (const key of misses) {
|
|
4917
|
+
const source = params.source.entries.get(key);
|
|
4918
|
+
if (source === void 0) {
|
|
4919
|
+
continue;
|
|
4920
|
+
}
|
|
4921
|
+
const existing = byHash.get(contentHash(source));
|
|
4922
|
+
if (existing === void 0) {
|
|
4923
|
+
byHash.set(contentHash(source), { representative: key, duplicates: [] });
|
|
4924
|
+
} else {
|
|
4925
|
+
existing.duplicates.push(key);
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
return [...byHash.values()];
|
|
4929
|
+
}
|
|
4930
|
+
function fanOutContentDuplicates(params, groups, outcome) {
|
|
4931
|
+
for (const group of groups) {
|
|
4932
|
+
if (group.duplicates.length > 0) {
|
|
4933
|
+
applyGroupOutcome(params, group, outcome);
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
function applyGroupOutcome(params, group, outcome) {
|
|
4938
|
+
const acceptedRepresentative = outcome.accepted.get(group.representative);
|
|
4939
|
+
if (acceptedRepresentative !== void 0) {
|
|
4940
|
+
fanOutAccepted(params, group, acceptedRepresentative, outcome);
|
|
4941
|
+
return;
|
|
4942
|
+
}
|
|
4943
|
+
withheldBucketFor(group.representative, outcome).push(...group.duplicates);
|
|
4944
|
+
}
|
|
4945
|
+
function fanOutAccepted(params, group, acceptedRepresentative, outcome) {
|
|
4946
|
+
const flag = outcome.reviewFlags.get(group.representative);
|
|
4947
|
+
for (const key of group.duplicates) {
|
|
4948
|
+
const source = params.source.entries.get(key);
|
|
4949
|
+
if (source === void 0) {
|
|
4950
|
+
continue;
|
|
4951
|
+
}
|
|
4952
|
+
outcome.accepted.set(key, { value: acceptedRepresentative.value, source });
|
|
4953
|
+
if (flag !== void 0) {
|
|
4954
|
+
outcome.reviewFlags.set(key, flag);
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
function withheldBucketFor(representative, outcome) {
|
|
4959
|
+
if (outcome.integrityMismatches.includes(representative)) {
|
|
4960
|
+
return outcome.integrityMismatches;
|
|
4961
|
+
}
|
|
4962
|
+
if (outcome.budgetWithheld.includes(representative)) {
|
|
4963
|
+
return outcome.budgetWithheld;
|
|
4964
|
+
}
|
|
4965
|
+
return outcome.providerFailures;
|
|
4966
|
+
}
|
|
4205
4967
|
function buildRequest3(params, entries) {
|
|
4206
4968
|
return {
|
|
4207
4969
|
sourceLocale: params.sourceLocale,
|
|
@@ -4244,6 +5006,7 @@ async function runLocale(params) {
|
|
|
4244
5006
|
orphaned,
|
|
4245
5007
|
invalidIcuSource,
|
|
4246
5008
|
translated: toTranslate,
|
|
5009
|
+
cacheHits: [],
|
|
4247
5010
|
generated: [],
|
|
4248
5011
|
integrityMismatches: [],
|
|
4249
5012
|
providerFailures: [],
|
|
@@ -4251,12 +5014,16 @@ async function runLocale(params) {
|
|
|
4251
5014
|
pruned,
|
|
4252
5015
|
notices: sdkNotices
|
|
4253
5016
|
}),
|
|
4254
|
-
lockEntries: {}
|
|
5017
|
+
lockEntries: {},
|
|
5018
|
+
cacheAdditions: []
|
|
4255
5019
|
};
|
|
4256
5020
|
}
|
|
4257
|
-
const
|
|
5021
|
+
const partition2 = partitionCacheHits(params, toTranslate);
|
|
5022
|
+
const cacheHitKeys = new Set(partition2.hits.keys());
|
|
5023
|
+
const missGroups = groupMissesByContent(params, partition2.misses);
|
|
5024
|
+
const entries = missGroups.map((group) => params.source.entries.get(group.representative)).filter((entry) => entry !== void 0);
|
|
4258
5025
|
const startedStopped = params.budget.stopped;
|
|
4259
|
-
const accepted =
|
|
5026
|
+
const accepted = new Map(partition2.hits);
|
|
4260
5027
|
const integrityMismatches = [];
|
|
4261
5028
|
const providerFailures = [];
|
|
4262
5029
|
const budgetWithheld = [];
|
|
@@ -4271,6 +5038,13 @@ async function runLocale(params) {
|
|
|
4271
5038
|
budgetWithheld,
|
|
4272
5039
|
reviewFlags
|
|
4273
5040
|
);
|
|
5041
|
+
fanOutContentDuplicates(params, missGroups, {
|
|
5042
|
+
accepted,
|
|
5043
|
+
integrityMismatches,
|
|
5044
|
+
providerFailures,
|
|
5045
|
+
budgetWithheld,
|
|
5046
|
+
reviewFlags
|
|
5047
|
+
});
|
|
4274
5048
|
const merged = new Map(target.entries);
|
|
4275
5049
|
for (const key of pruned) {
|
|
4276
5050
|
merged.delete(key);
|
|
@@ -4317,7 +5091,8 @@ async function runLocale(params) {
|
|
|
4317
5091
|
unchanged: diff2.unchanged,
|
|
4318
5092
|
orphaned,
|
|
4319
5093
|
invalidIcuSource,
|
|
4320
|
-
translated: [...accepted.keys()],
|
|
5094
|
+
translated: [...accepted.keys()].filter((key) => !cacheHitKeys.has(key)),
|
|
5095
|
+
cacheHits: [...cacheHitKeys].sort(),
|
|
4321
5096
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
4322
5097
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
4323
5098
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
@@ -4327,7 +5102,8 @@ async function runLocale(params) {
|
|
|
4327
5102
|
needsReview: needsReviewFor(accepted.keys(), reviewFlags),
|
|
4328
5103
|
...localeUsage !== void 0 ? { usage: localeUsage } : {}
|
|
4329
5104
|
}),
|
|
4330
|
-
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted)
|
|
5105
|
+
lockEntries: computeLockEntries(params, merged, withheld, generation.accepted),
|
|
5106
|
+
cacheAdditions: collectCacheAdditions(params, accepted, cacheHitKeys)
|
|
4331
5107
|
};
|
|
4332
5108
|
}
|
|
4333
5109
|
var NO_GENERATION_RESULT = {
|
|
@@ -4372,18 +5148,22 @@ function pluralNoticeFor(params, merged) {
|
|
|
4372
5148
|
function baseSummary(parts) {
|
|
4373
5149
|
return {
|
|
4374
5150
|
locale: parts.locale,
|
|
4375
|
-
status:
|
|
5151
|
+
status: deriveLocaleStatus(parts),
|
|
4376
5152
|
translated: parts.translated,
|
|
4377
5153
|
unchanged: parts.unchanged,
|
|
4378
5154
|
orphaned: parts.orphaned,
|
|
4379
5155
|
pruned: parts.pruned,
|
|
4380
5156
|
invalidIcuSource: parts.invalidIcuSource,
|
|
5157
|
+
cacheHits: parts.cacheHits,
|
|
4381
5158
|
integrityMismatches: parts.integrityMismatches,
|
|
4382
5159
|
providerFailures: parts.providerFailures,
|
|
4383
5160
|
budgetWithheld: parts.budgetWithheld,
|
|
4384
5161
|
generated: parts.generated,
|
|
4385
5162
|
notices: parts.notices,
|
|
4386
5163
|
needsReview: parts.needsReview ?? [],
|
|
5164
|
+
unfilled: [],
|
|
5165
|
+
malformedRows: [],
|
|
5166
|
+
duplicateKeys: [],
|
|
4387
5167
|
...parts.usage !== void 0 ? { usage: parts.usage } : {}
|
|
4388
5168
|
};
|
|
4389
5169
|
}
|
|
@@ -4401,7 +5181,16 @@ async function translateAndCheck(provider, params, entries, accepted, integrityM
|
|
|
4401
5181
|
const notices = [];
|
|
4402
5182
|
const usage = createUsageAccumulator();
|
|
4403
5183
|
let tripped = false;
|
|
4404
|
-
|
|
5184
|
+
const batches = chunk(entries, params.maxBatchSize);
|
|
5185
|
+
let batchIndex = 0;
|
|
5186
|
+
for (const batch of batches) {
|
|
5187
|
+
batchIndex += 1;
|
|
5188
|
+
params.onProgress?.({
|
|
5189
|
+
type: "sub-batch",
|
|
5190
|
+
locale: params.targetLocale,
|
|
5191
|
+
batchIndex,
|
|
5192
|
+
totalBatches: batches.length
|
|
5193
|
+
});
|
|
4405
5194
|
if (params.budget.stopped) {
|
|
4406
5195
|
for (const entry of batch) {
|
|
4407
5196
|
budgetWithheld.push(entry.key);
|
|
@@ -4431,10 +5220,16 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
4431
5220
|
try {
|
|
4432
5221
|
result = await provider.translateBatch(buildRequest3(params, batch));
|
|
4433
5222
|
} catch (error) {
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
5223
|
+
return handleSubBatchFailure(
|
|
5224
|
+
error,
|
|
5225
|
+
provider,
|
|
5226
|
+
params,
|
|
5227
|
+
batch,
|
|
5228
|
+
accepted,
|
|
5229
|
+
integrityMismatches,
|
|
5230
|
+
providerFailures,
|
|
5231
|
+
reviewFlags
|
|
5232
|
+
);
|
|
4438
5233
|
}
|
|
4439
5234
|
for (const entry of batch) {
|
|
4440
5235
|
foldEntryResult(entry, result, params.adapter, accepted, integrityMismatches, providerFailures);
|
|
@@ -4446,6 +5241,44 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
4446
5241
|
}
|
|
4447
5242
|
return { notices: readNotices(result), usage: result.usage };
|
|
4448
5243
|
}
|
|
5244
|
+
function isOutputTruncated(error) {
|
|
5245
|
+
return error instanceof ProviderError && error.code === "OUTPUT_TRUNCATED";
|
|
5246
|
+
}
|
|
5247
|
+
async function handleSubBatchFailure(error, provider, params, batch, accepted, integrityMismatches, providerFailures, reviewFlags) {
|
|
5248
|
+
if (isOutputTruncated(error) && batch.length > 1) {
|
|
5249
|
+
return retryTruncatedSplit(
|
|
5250
|
+
provider,
|
|
5251
|
+
params,
|
|
5252
|
+
batch,
|
|
5253
|
+
accepted,
|
|
5254
|
+
integrityMismatches,
|
|
5255
|
+
providerFailures,
|
|
5256
|
+
reviewFlags
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
for (const entry of batch) {
|
|
5260
|
+
providerFailures.push(entry.key);
|
|
5261
|
+
}
|
|
5262
|
+
return { notices: [subBatchFailedNotice(batch.length, error)], usage: void 0 };
|
|
5263
|
+
}
|
|
5264
|
+
async function retryTruncatedSplit(provider, params, batch, accepted, integrityMismatches, providerFailures, reviewFlags) {
|
|
5265
|
+
const notices = [];
|
|
5266
|
+
let usage;
|
|
5267
|
+
for (const half of chunk(batch, Math.ceil(batch.length / 2))) {
|
|
5268
|
+
const sub = await runSubBatch(
|
|
5269
|
+
provider,
|
|
5270
|
+
params,
|
|
5271
|
+
half,
|
|
5272
|
+
accepted,
|
|
5273
|
+
integrityMismatches,
|
|
5274
|
+
providerFailures,
|
|
5275
|
+
reviewFlags
|
|
5276
|
+
);
|
|
5277
|
+
notices.push(...sub.notices);
|
|
5278
|
+
usage = combineUsage(usage, sub.usage);
|
|
5279
|
+
}
|
|
5280
|
+
return { notices, usage };
|
|
5281
|
+
}
|
|
4449
5282
|
function foldEntryResult(entry, result, adapter, accepted, integrityMismatches, providerFailures) {
|
|
4450
5283
|
const value = result.values.get(entry.key);
|
|
4451
5284
|
if (value === void 0) {
|
|
@@ -4503,6 +5336,26 @@ async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
|
4503
5336
|
} catch {
|
|
4504
5337
|
}
|
|
4505
5338
|
}
|
|
5339
|
+
async function createRunCacheState(input, config, cwd, dryRun, fs) {
|
|
5340
|
+
if (dryRun || input.cache === false) {
|
|
5341
|
+
return void 0;
|
|
5342
|
+
}
|
|
5343
|
+
return {
|
|
5344
|
+
memory: await readTranslationMemory(cacheFilePath(cwd), fs),
|
|
5345
|
+
fingerprint: computeFingerprint(config),
|
|
5346
|
+
additions: /* @__PURE__ */ new Map()
|
|
5347
|
+
};
|
|
5348
|
+
}
|
|
5349
|
+
async function recordCacheAdditions(cwd, cache, fs) {
|
|
5350
|
+
if (cache === void 0 || cache.additions.size === 0) {
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
try {
|
|
5354
|
+
const merged = applyAdditions(cache.memory, cache.fingerprint, cache.additions);
|
|
5355
|
+
await writeTranslationMemory(cacheFilePath(cwd), merged, fs);
|
|
5356
|
+
} catch {
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
4506
5359
|
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4507
5360
|
return {
|
|
4508
5361
|
source: context.source.resource,
|
|
@@ -4521,7 +5374,9 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
4521
5374
|
generatePlurals: context.generatePlurals,
|
|
4522
5375
|
maxBatchSize: context.maxBatchSize,
|
|
4523
5376
|
fs: context.fs,
|
|
4524
|
-
budget: context.budget
|
|
5377
|
+
budget: context.budget,
|
|
5378
|
+
...context.cache !== void 0 ? { cache: { snapshot: context.cache.memory, fingerprint: context.cache.fingerprint } } : {},
|
|
5379
|
+
...context.onProgress !== void 0 ? { onProgress: context.onProgress } : {}
|
|
4525
5380
|
};
|
|
4526
5381
|
}
|
|
4527
5382
|
async function runDryLocale(context, targetLocale, lock) {
|
|
@@ -4529,16 +5384,29 @@ async function runDryLocale(context, targetLocale, lock) {
|
|
|
4529
5384
|
return (await runLocale(params)).summary;
|
|
4530
5385
|
}
|
|
4531
5386
|
async function runLiveLocale(context, targetLocale) {
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
5387
|
+
const lockOptions = {
|
|
5388
|
+
...context.onLockWait !== void 0 ? { onWait: context.onLockWait } : {},
|
|
5389
|
+
...context.lockAcquireTimeoutMs !== void 0 ? { acquireTimeoutMs: context.lockAcquireTimeoutMs } : {}
|
|
5390
|
+
};
|
|
5391
|
+
return withLocaleWriteLock(
|
|
5392
|
+
context.cwd,
|
|
5393
|
+
targetLocale,
|
|
5394
|
+
context.fs,
|
|
5395
|
+
async () => {
|
|
5396
|
+
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
5397
|
+
const params = buildLocaleRunParams(context, targetLocale, baselineFor(lock, targetLocale));
|
|
5398
|
+
const result = await runLocale(params);
|
|
5399
|
+
await updateLockFileLocale(context.cwd, context.fs, targetLocale, {
|
|
5400
|
+
mode: "replace",
|
|
5401
|
+
entries: result.lockEntries
|
|
5402
|
+
});
|
|
5403
|
+
if (context.cache !== void 0 && result.cacheAdditions.length > 0) {
|
|
5404
|
+
context.cache.additions.set(targetLocale, additionsToRecord(result.cacheAdditions));
|
|
5405
|
+
}
|
|
5406
|
+
return result.summary;
|
|
5407
|
+
},
|
|
5408
|
+
lockOptions
|
|
5409
|
+
);
|
|
4542
5410
|
}
|
|
4543
5411
|
async function runOneLocale(targetLocale, run) {
|
|
4544
5412
|
try {
|
|
@@ -4550,27 +5418,88 @@ async function runOneLocale(targetLocale, run) {
|
|
|
4550
5418
|
return failureSummary(targetLocale, error);
|
|
4551
5419
|
}
|
|
4552
5420
|
}
|
|
4553
|
-
async function
|
|
5421
|
+
async function runLocaleAt(context, targetLocales, localeIndex, runOne, results) {
|
|
5422
|
+
const targetLocale = targetLocales[localeIndex];
|
|
5423
|
+
if (targetLocale === void 0) {
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
5426
|
+
context.onProgress?.({
|
|
5427
|
+
type: "locale-started",
|
|
5428
|
+
locale: targetLocale,
|
|
5429
|
+
localeIndex,
|
|
5430
|
+
totalLocales: targetLocales.length
|
|
5431
|
+
});
|
|
5432
|
+
const summary = await runOneLocale(targetLocale, () => runOne(targetLocale));
|
|
5433
|
+
results[localeIndex] = summary;
|
|
5434
|
+
context.onProgress?.({
|
|
5435
|
+
type: "locale-finished",
|
|
5436
|
+
locale: targetLocale,
|
|
5437
|
+
translated: summary.translated.length
|
|
5438
|
+
});
|
|
5439
|
+
}
|
|
5440
|
+
async function runLocalesWithProgress(context, targetLocales, runOne, concurrency) {
|
|
5441
|
+
const totalLocales = targetLocales.length;
|
|
5442
|
+
const results = new Array(totalLocales);
|
|
5443
|
+
let nextIndex = 0;
|
|
5444
|
+
async function worker() {
|
|
5445
|
+
while (nextIndex < totalLocales) {
|
|
5446
|
+
const localeIndex = nextIndex;
|
|
5447
|
+
nextIndex += 1;
|
|
5448
|
+
await runLocaleAt(context, targetLocales, localeIndex, runOne, results);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
const workerCount = Math.min(concurrency, totalLocales);
|
|
5452
|
+
const workers = [];
|
|
5453
|
+
for (let index = 0; index < workerCount; index += 1) {
|
|
5454
|
+
workers.push(worker());
|
|
5455
|
+
}
|
|
5456
|
+
await Promise.all(workers);
|
|
5457
|
+
return results.filter((summary) => summary !== void 0);
|
|
5458
|
+
}
|
|
5459
|
+
async function runAllLocalesDry(context, targetLocales, concurrency) {
|
|
4554
5460
|
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
5461
|
+
return runLocalesWithProgress(
|
|
5462
|
+
context,
|
|
5463
|
+
targetLocales,
|
|
5464
|
+
(targetLocale) => runDryLocale(context, targetLocale, lock),
|
|
5465
|
+
concurrency
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
async function runAllLocalesLive(context, targetLocales, concurrency) {
|
|
5469
|
+
return runLocalesWithProgress(
|
|
5470
|
+
context,
|
|
5471
|
+
targetLocales,
|
|
5472
|
+
(targetLocale) => runLiveLocale(context, targetLocale),
|
|
5473
|
+
concurrency
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
function resolveConcurrency(value) {
|
|
5477
|
+
if (value === void 0) {
|
|
5478
|
+
return 1;
|
|
5479
|
+
}
|
|
5480
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5481
|
+
throw new SdkError(
|
|
5482
|
+
"CONCURRENCY_INVALID",
|
|
5483
|
+
`The concurrency option must be an integer of at least 1, got ${value}.`
|
|
4559
5484
|
);
|
|
4560
5485
|
}
|
|
4561
|
-
return
|
|
5486
|
+
return value;
|
|
4562
5487
|
}
|
|
4563
|
-
|
|
4564
|
-
const
|
|
4565
|
-
|
|
4566
|
-
|
|
5488
|
+
function resolveRunConcurrency(value, dryRun, config) {
|
|
5489
|
+
const concurrency = resolveConcurrency(value);
|
|
5490
|
+
if (!dryRun && concurrency > 1 && config.maxTokens !== void 0) {
|
|
5491
|
+
throw new SdkError(
|
|
5492
|
+
"CONCURRENCY_BUDGET_CONFLICT",
|
|
5493
|
+
"A token budget (maxTokens) and concurrency greater than 1 cannot be combined on a live run: concurrent locales would overshoot the budget nondeterministically. Set concurrency to 1, remove maxTokens, or use --dry-run."
|
|
5494
|
+
);
|
|
4567
5495
|
}
|
|
4568
|
-
return
|
|
5496
|
+
return concurrency;
|
|
4569
5497
|
}
|
|
4570
5498
|
async function translate2(input, deps = {}) {
|
|
4571
5499
|
const config = input.config;
|
|
4572
5500
|
const cwd = input.cwd ?? process.cwd();
|
|
4573
5501
|
const dryRun = input.dryRun ?? false;
|
|
5502
|
+
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
4574
5503
|
const prune = input.prune ?? config.prune ?? false;
|
|
4575
5504
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
4576
5505
|
const maxBatchSize = config.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
@@ -4582,6 +5511,7 @@ async function translate2(input, deps = {}) {
|
|
|
4582
5511
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4583
5512
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
4584
5513
|
const source = await readSource(config, cwd, fs, adapter);
|
|
5514
|
+
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
4585
5515
|
const context = {
|
|
4586
5516
|
source,
|
|
4587
5517
|
adapter,
|
|
@@ -4592,10 +5522,15 @@ async function translate2(input, deps = {}) {
|
|
|
4592
5522
|
generatePlurals,
|
|
4593
5523
|
maxBatchSize,
|
|
4594
5524
|
fs,
|
|
4595
|
-
budget
|
|
5525
|
+
budget,
|
|
5526
|
+
cache,
|
|
5527
|
+
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
5528
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
5529
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
4596
5530
|
};
|
|
4597
|
-
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
4598
|
-
|
|
5531
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales, concurrency) : await runAllLocalesLive(context, config.targetLocales, concurrency);
|
|
5532
|
+
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
5533
|
+
const { succeeded, partial, failed } = partition(summaries);
|
|
4599
5534
|
const usage = summaries.reduce(
|
|
4600
5535
|
(total, summary2) => combineUsage(total, summary2.usage),
|
|
4601
5536
|
void 0
|
|
@@ -4605,10 +5540,12 @@ async function translate2(input, deps = {}) {
|
|
|
4605
5540
|
dryRun,
|
|
4606
5541
|
locales: summaries,
|
|
4607
5542
|
succeeded,
|
|
5543
|
+
partial,
|
|
4608
5544
|
failed,
|
|
4609
5545
|
...usage !== void 0 ? { usage } : {},
|
|
4610
5546
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
4611
5547
|
};
|
|
5548
|
+
await recordCacheAdditions(cwd, cache, fs);
|
|
4612
5549
|
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4613
5550
|
return summary;
|
|
4614
5551
|
}
|
|
@@ -4624,8 +5561,12 @@ var INSTRUCTIONS_LINES = [
|
|
|
4624
5561
|
"How to use this workbook",
|
|
4625
5562
|
"",
|
|
4626
5563
|
"1. There is one sheet per language. Open the sheet named for the language you are translating.",
|
|
5564
|
+
" Do not rename, delete, or reorder the language tabs. verbatra matches each tab to a language by",
|
|
5565
|
+
" its exact name; a renamed or missing tab is reported and its translations are not imported.",
|
|
4627
5566
|
"2. Fill ONLY the 'Translation' column. Leave every other column unchanged.",
|
|
4628
|
-
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty
|
|
5567
|
+
"3. An empty 'Translation' cell means 'not translated yet'. It is skipped, never written as an empty",
|
|
5568
|
+
" string. A cell that contains only spaces is treated the same as an empty cell.",
|
|
5569
|
+
" To deliberately clear an existing value (set it to empty), type exactly [[CLEAR]] in the cell.",
|
|
4629
5570
|
"4. Keep placeholders exactly as they appear. A token such as {name} or {count} must stay verbatim,",
|
|
4630
5571
|
" in your translation, with the same spelling. Do not translate or remove it.",
|
|
4631
5572
|
"5. For ICU messages (for example {count, plural, one {# item} other {# items}}), keep the ICU",
|
|
@@ -4790,6 +5731,26 @@ function buildInstructionsSheet(workbook) {
|
|
|
4790
5731
|
}
|
|
4791
5732
|
sheet.getRow(1).font = { bold: true };
|
|
4792
5733
|
}
|
|
5734
|
+
var WORKBOOK_PROTECTION_XML = '<workbookProtection lockStructure="1" lockWindows="0"/>';
|
|
5735
|
+
function spliceWorkbookProtection(xml) {
|
|
5736
|
+
const anchor = xml.includes("<bookViews") ? "<bookViews" : "<sheets";
|
|
5737
|
+
return xml.replace(anchor, `${WORKBOOK_PROTECTION_XML}${anchor}`);
|
|
5738
|
+
}
|
|
5739
|
+
async function protectWorkbookStructure(bytes) {
|
|
5740
|
+
let zip;
|
|
5741
|
+
try {
|
|
5742
|
+
zip = await JSZip.loadAsync(bytes);
|
|
5743
|
+
} catch {
|
|
5744
|
+
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
5745
|
+
}
|
|
5746
|
+
const workbookPart = zip.file("xl/workbook.xml");
|
|
5747
|
+
if (workbookPart === null) {
|
|
5748
|
+
return bytes;
|
|
5749
|
+
}
|
|
5750
|
+
const xml = await workbookPart.async("string");
|
|
5751
|
+
zip.file("xl/workbook.xml", spliceWorkbookProtection(xml));
|
|
5752
|
+
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
|
|
5753
|
+
}
|
|
4793
5754
|
async function buildWorkbook(model) {
|
|
4794
5755
|
assertNoWorksheetNameCollisions(model.sheets);
|
|
4795
5756
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -4797,13 +5758,15 @@ async function buildWorkbook(model) {
|
|
|
4797
5758
|
for (const sheet of model.sheets) {
|
|
4798
5759
|
await buildDataSheet(workbook, sheet);
|
|
4799
5760
|
}
|
|
5761
|
+
let serialized;
|
|
4800
5762
|
try {
|
|
4801
5763
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
4802
|
-
|
|
4803
|
-
return Uint8Array.prototype.slice.call(view);
|
|
5764
|
+
serialized = buffer;
|
|
4804
5765
|
} catch {
|
|
4805
5766
|
throw new ExchangeError("WORKBOOK_INVALID", "The workbook could not be serialized.");
|
|
4806
5767
|
}
|
|
5768
|
+
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5769
|
+
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
4807
5770
|
}
|
|
4808
5771
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
4809
5772
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
@@ -4923,26 +5886,24 @@ function assertHeader(sheet) {
|
|
|
4923
5886
|
);
|
|
4924
5887
|
}
|
|
4925
5888
|
}
|
|
4926
|
-
|
|
5889
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5890
|
+
function parseRow(row) {
|
|
4927
5891
|
const candidate = {
|
|
4928
5892
|
key: cellString(row.getCell(COLUMN.key)),
|
|
4929
5893
|
source: cellString(row.getCell(COLUMN.source)),
|
|
4930
5894
|
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
4931
5895
|
status: cellString(row.getCell(COLUMN.status)),
|
|
4932
5896
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
4933
|
-
translation: cellString(row.getCell(COLUMN.translation)),
|
|
5897
|
+
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
4934
5898
|
context: cellString(row.getCell(COLUMN.context)),
|
|
4935
5899
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
4936
5900
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
4937
5901
|
};
|
|
4938
5902
|
const result = rowSchema.safeParse(candidate);
|
|
4939
5903
|
if (!result.success) {
|
|
4940
|
-
|
|
4941
|
-
"WORKBOOK_INVALID",
|
|
4942
|
-
`The sheet "${sheet.name}" has a row with a missing key or an unrecognized status.`
|
|
4943
|
-
);
|
|
5904
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
4944
5905
|
}
|
|
4945
|
-
return result.data;
|
|
5906
|
+
return { ok: true, row: result.data };
|
|
4946
5907
|
}
|
|
4947
5908
|
function readDataSheet(sheet, limits) {
|
|
4948
5909
|
assertHeader(sheet);
|
|
@@ -4953,6 +5914,9 @@ function readDataSheet(sheet, limits) {
|
|
|
4953
5914
|
);
|
|
4954
5915
|
}
|
|
4955
5916
|
const rows = [];
|
|
5917
|
+
const malformed = [];
|
|
5918
|
+
const duplicates = [];
|
|
5919
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
4956
5920
|
for (let rowNumber = HEADER_ROW + 1; rowNumber <= sheet.rowCount; rowNumber += 1) {
|
|
4957
5921
|
const row = sheet.getRow(rowNumber);
|
|
4958
5922
|
if (row.cellCount > limits.maxCellsPerRow) {
|
|
@@ -4964,9 +5928,19 @@ function readDataSheet(sheet, limits) {
|
|
|
4964
5928
|
if (cellString(row.getCell(COLUMN.key)) === "") {
|
|
4965
5929
|
continue;
|
|
4966
5930
|
}
|
|
4967
|
-
|
|
5931
|
+
const outcome = parseRow(row);
|
|
5932
|
+
if (!outcome.ok) {
|
|
5933
|
+
malformed.push({ locale: sheet.name, row: rowNumber, column: outcome.column });
|
|
5934
|
+
continue;
|
|
5935
|
+
}
|
|
5936
|
+
if (seenKeys.has(outcome.row.key)) {
|
|
5937
|
+
duplicates.push({ locale: sheet.name, key: outcome.row.key, row: rowNumber });
|
|
5938
|
+
continue;
|
|
5939
|
+
}
|
|
5940
|
+
seenKeys.add(outcome.row.key);
|
|
5941
|
+
rows.push(outcome.row);
|
|
4968
5942
|
}
|
|
4969
|
-
return { locale: sheet.name, rows };
|
|
5943
|
+
return { sheet: { locale: sheet.name, rows }, malformed, duplicates };
|
|
4970
5944
|
}
|
|
4971
5945
|
async function loadWorkbook(bytes) {
|
|
4972
5946
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -4989,13 +5963,18 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
4989
5963
|
);
|
|
4990
5964
|
}
|
|
4991
5965
|
const sheets = [];
|
|
5966
|
+
const malformedRows = [];
|
|
5967
|
+
const duplicateKeys = [];
|
|
4992
5968
|
for (const sheet of workbook.worksheets) {
|
|
4993
5969
|
if (sheet.name === INSTRUCTIONS_SHEET_NAME) {
|
|
4994
5970
|
continue;
|
|
4995
5971
|
}
|
|
4996
|
-
|
|
5972
|
+
const read = readDataSheet(sheet, limits);
|
|
5973
|
+
sheets.push(read.sheet);
|
|
5974
|
+
malformedRows.push(...read.malformed);
|
|
5975
|
+
duplicateKeys.push(...read.duplicates);
|
|
4997
5976
|
}
|
|
4998
|
-
return { sheets };
|
|
5977
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
4999
5978
|
}
|
|
5000
5979
|
|
|
5001
5980
|
// src/flow/workbook/export-workbook.ts
|
|
@@ -5096,6 +6075,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
5096
6075
|
}
|
|
5097
6076
|
|
|
5098
6077
|
// src/flow/workbook/import-locale.ts
|
|
6078
|
+
var CLEAR_SENTINEL = "[[CLEAR]]";
|
|
5099
6079
|
var UnknownKeyError = class extends Error {
|
|
5100
6080
|
key;
|
|
5101
6081
|
constructor(key) {
|
|
@@ -5124,9 +6104,20 @@ function trackBlankDrift(row, params, buckets) {
|
|
|
5124
6104
|
buckets.blankDrifted.add(row.key);
|
|
5125
6105
|
}
|
|
5126
6106
|
}
|
|
6107
|
+
function classifyClear(row, sourceEntry, buckets) {
|
|
6108
|
+
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
6109
|
+
buckets.mismatches.push(row.key);
|
|
6110
|
+
buckets.withheld.add(row.key);
|
|
6111
|
+
return;
|
|
6112
|
+
}
|
|
6113
|
+
buckets.accepted.set(row.key, { value: "", source: sourceEntry });
|
|
6114
|
+
}
|
|
5127
6115
|
function classifyRows(params, buckets) {
|
|
5128
6116
|
for (const row of params.sheet.rows) {
|
|
5129
6117
|
if (row.translation === "") {
|
|
6118
|
+
if (row.status === "changed") {
|
|
6119
|
+
buckets.unfilled.push(row.key);
|
|
6120
|
+
}
|
|
5130
6121
|
trackBlankDrift(row, params, buckets);
|
|
5131
6122
|
continue;
|
|
5132
6123
|
}
|
|
@@ -5137,6 +6128,10 @@ function classifyRows(params, buckets) {
|
|
|
5137
6128
|
if (sourceEntry === void 0) {
|
|
5138
6129
|
continue;
|
|
5139
6130
|
}
|
|
6131
|
+
if (row.translation === CLEAR_SENTINEL) {
|
|
6132
|
+
classifyClear(row, sourceEntry, buckets);
|
|
6133
|
+
continue;
|
|
6134
|
+
}
|
|
5140
6135
|
const reason = judge(row, sourceEntry, params.adapter);
|
|
5141
6136
|
if (reason === void 0) {
|
|
5142
6137
|
buckets.accepted.set(row.key, { value: row.translation, source: sourceEntry });
|
|
@@ -5158,25 +6153,39 @@ function importLocale(params) {
|
|
|
5158
6153
|
accepted: /* @__PURE__ */ new Map(),
|
|
5159
6154
|
mismatches: [],
|
|
5160
6155
|
withheld: /* @__PURE__ */ new Set(),
|
|
5161
|
-
blankDrifted: /* @__PURE__ */ new Set()
|
|
6156
|
+
blankDrifted: /* @__PURE__ */ new Set(),
|
|
6157
|
+
unfilled: []
|
|
5162
6158
|
};
|
|
5163
6159
|
classifyRows(params, buckets);
|
|
5164
6160
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
5165
6161
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
6162
|
+
const translated = [...buckets.accepted.keys()].sort();
|
|
6163
|
+
const integrityMismatches = [...buckets.mismatches].sort();
|
|
5166
6164
|
const summary = {
|
|
5167
6165
|
locale: params.sheet.locale,
|
|
5168
|
-
status:
|
|
5169
|
-
|
|
6166
|
+
status: deriveLocaleStatus({
|
|
6167
|
+
translated,
|
|
6168
|
+
cacheHits: [],
|
|
6169
|
+
generated: [],
|
|
6170
|
+
integrityMismatches,
|
|
6171
|
+
providerFailures: [],
|
|
6172
|
+
budgetWithheld: []
|
|
6173
|
+
}),
|
|
6174
|
+
translated,
|
|
5170
6175
|
unchanged: diff2.unchanged,
|
|
5171
6176
|
orphaned: diff2.orphaned,
|
|
5172
6177
|
pruned: [],
|
|
5173
6178
|
invalidIcuSource,
|
|
5174
|
-
|
|
6179
|
+
cacheHits: [],
|
|
6180
|
+
integrityMismatches,
|
|
5175
6181
|
providerFailures: [],
|
|
5176
6182
|
budgetWithheld: [],
|
|
5177
6183
|
generated: [],
|
|
5178
6184
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
5179
|
-
needsReview: []
|
|
6185
|
+
needsReview: [],
|
|
6186
|
+
unfilled: [...new Set(buckets.unfilled)].sort(),
|
|
6187
|
+
malformedRows: params.malformedRows,
|
|
6188
|
+
duplicateKeys: params.duplicateKeys
|
|
5180
6189
|
};
|
|
5181
6190
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
5182
6191
|
}
|
|
@@ -5203,6 +6212,19 @@ function mergeAccepted(target, accepted) {
|
|
|
5203
6212
|
}
|
|
5204
6213
|
return merged;
|
|
5205
6214
|
}
|
|
6215
|
+
function sheetCacheAdditions(accepted) {
|
|
6216
|
+
const record = {};
|
|
6217
|
+
for (const [, { value, source }] of accepted) {
|
|
6218
|
+
record[contentHash(source)] = value;
|
|
6219
|
+
}
|
|
6220
|
+
return record;
|
|
6221
|
+
}
|
|
6222
|
+
function collectSheetAdditions(byLocale, locale, additions) {
|
|
6223
|
+
if (Object.keys(additions).length === 0) {
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
byLocale.set(locale, { ...byLocale.get(locale), ...additions });
|
|
6227
|
+
}
|
|
5206
6228
|
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
5207
6229
|
const entries = {};
|
|
5208
6230
|
for (const key of merged.keys()) {
|
|
@@ -5219,11 +6241,20 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
5219
6241
|
}
|
|
5220
6242
|
return entries;
|
|
5221
6243
|
}
|
|
6244
|
+
var MissingSheetError = class extends Error {
|
|
6245
|
+
code = "WORKBOOK_SHEET_MISSING";
|
|
6246
|
+
constructor(locale) {
|
|
6247
|
+
super(
|
|
6248
|
+
`The workbook has no sheet (tab) for the configured target locale "${locale}". The tab may have been renamed, deleted, or reordered out of the workbook.`
|
|
6249
|
+
);
|
|
6250
|
+
this.name = "MissingSheetError";
|
|
6251
|
+
}
|
|
6252
|
+
};
|
|
5222
6253
|
async function runSheet(ctx, sheet, lock) {
|
|
5223
6254
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
5224
6255
|
throw new SdkError(
|
|
5225
6256
|
"CONFIG_INVALID",
|
|
5226
|
-
`The workbook has a sheet
|
|
6257
|
+
`The workbook has a sheet named "${sheet.locale}", which is not a configured target locale. It may be a renamed, added, or reordered tab; leave every language tab named exactly as exported.`
|
|
5227
6258
|
);
|
|
5228
6259
|
}
|
|
5229
6260
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -5234,10 +6265,12 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5234
6265
|
target,
|
|
5235
6266
|
baseline,
|
|
5236
6267
|
adapter: ctx.adapter,
|
|
5237
|
-
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys
|
|
6268
|
+
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6269
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6270
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
5238
6271
|
});
|
|
5239
6272
|
if (ctx.dryRun) {
|
|
5240
|
-
return { summary, lockEntries: {} };
|
|
6273
|
+
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
5241
6274
|
}
|
|
5242
6275
|
const merged = mergeAccepted(target, accepted);
|
|
5243
6276
|
if (accepted.size > 0) {
|
|
@@ -5252,7 +6285,11 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
5252
6285
|
path
|
|
5253
6286
|
);
|
|
5254
6287
|
}
|
|
5255
|
-
return {
|
|
6288
|
+
return {
|
|
6289
|
+
summary,
|
|
6290
|
+
lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted),
|
|
6291
|
+
cacheAdditions: sheetCacheAdditions(accepted)
|
|
6292
|
+
};
|
|
5256
6293
|
}
|
|
5257
6294
|
async function importWorkbook(input, deps = {}) {
|
|
5258
6295
|
const config = input.config;
|
|
@@ -5277,9 +6314,12 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5277
6314
|
fs,
|
|
5278
6315
|
source: source.resource,
|
|
5279
6316
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
5280
|
-
dryRun
|
|
6317
|
+
dryRun,
|
|
6318
|
+
malformedRows: data.malformedRows,
|
|
6319
|
+
duplicateKeys: data.duplicateKeys
|
|
5281
6320
|
};
|
|
5282
6321
|
const summaries = [];
|
|
6322
|
+
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
5283
6323
|
for (const sheet of data.sheets) {
|
|
5284
6324
|
try {
|
|
5285
6325
|
let summary;
|
|
@@ -5292,6 +6332,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5292
6332
|
mode: "replace",
|
|
5293
6333
|
entries: result.lockEntries
|
|
5294
6334
|
});
|
|
6335
|
+
collectSheetAdditions(cacheAdditions, sheet.locale, result.cacheAdditions);
|
|
5295
6336
|
return result.summary;
|
|
5296
6337
|
});
|
|
5297
6338
|
}
|
|
@@ -5300,8 +6341,17 @@ async function importWorkbook(input, deps = {}) {
|
|
|
5300
6341
|
summaries.push(failureSummary(sheet.locale, error));
|
|
5301
6342
|
}
|
|
5302
6343
|
}
|
|
5303
|
-
const
|
|
5304
|
-
|
|
6344
|
+
const presentLocales = new Set(data.sheets.map((sheet) => sheet.locale));
|
|
6345
|
+
for (const locale of config.targetLocales) {
|
|
6346
|
+
if (!presentLocales.has(locale)) {
|
|
6347
|
+
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
if (!dryRun) {
|
|
6351
|
+
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6352
|
+
}
|
|
6353
|
+
const { succeeded, partial, failed } = partition(summaries);
|
|
6354
|
+
return { dryRun, locales: summaries, succeeded, partial, failed };
|
|
5305
6355
|
}
|
|
5306
6356
|
|
|
5307
6357
|
// src/lock/load-lock-file.ts
|
|
@@ -5359,7 +6409,15 @@ async function watch(input, deps = {}) {
|
|
|
5359
6409
|
);
|
|
5360
6410
|
}
|
|
5361
6411
|
const runTranslate = deps.runTranslate ?? defaultRunTranslate(deps);
|
|
5362
|
-
const runInput = {
|
|
6412
|
+
const runInput = {
|
|
6413
|
+
config: input.config,
|
|
6414
|
+
cwd,
|
|
6415
|
+
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
6416
|
+
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6417
|
+
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
6418
|
+
...input.concurrency !== void 0 ? { concurrency: input.concurrency } : {},
|
|
6419
|
+
...input.cache !== void 0 ? { cache: input.cache } : {}
|
|
6420
|
+
};
|
|
5363
6421
|
let state = "idle";
|
|
5364
6422
|
let pending = false;
|
|
5365
6423
|
let stopped = false;
|
|
@@ -5426,6 +6484,6 @@ async function watch(input, deps = {}) {
|
|
|
5426
6484
|
return { stop };
|
|
5427
6485
|
}
|
|
5428
6486
|
|
|
5429
|
-
export { DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
6487
|
+
export { CACHE_FILE_NAME, DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
5430
6488
|
//# sourceMappingURL=index.js.map
|
|
5431
6489
|
//# sourceMappingURL=index.js.map
|