@verbatra/sdk 0.5.0-next.4 → 0.5.0
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 +13 -1
- package/dist/index.cjs +794 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +462 -31
- package/dist/index.d.ts +462 -31
- package/dist/index.js +789 -195
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, resolve, join, basename, extname } from 'path';
|
|
|
3
3
|
import { cosmiconfig } from 'cosmiconfig';
|
|
4
4
|
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
|
-
import { access,
|
|
6
|
+
import { rm, access, mkdir, open, writeFile, rename } from 'fs/promises';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import Anthropic from '@anthropic-ai/sdk';
|
|
9
9
|
import * as deepl from 'deepl-node';
|
|
@@ -115,6 +115,24 @@ async function atomicWrite(path, data) {
|
|
|
115
115
|
throw error;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
async function createExclusive(path, data) {
|
|
119
|
+
await mkdir(dirname(path), { recursive: true });
|
|
120
|
+
let handle;
|
|
121
|
+
try {
|
|
122
|
+
handle = await open(path, "wx");
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error.code === "EEXIST") {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await handle.writeFile(data, "utf8");
|
|
131
|
+
} finally {
|
|
132
|
+
await handle.close();
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
118
136
|
var defaultFs = {
|
|
119
137
|
async fileExists(path) {
|
|
120
138
|
try {
|
|
@@ -127,7 +145,11 @@ var defaultFs = {
|
|
|
127
145
|
readFileBounded: (path, maxBytes) => readBounded(path, maxBytes),
|
|
128
146
|
readBytesBounded: (path, maxBytes) => readBoundedBytes(path, maxBytes),
|
|
129
147
|
writeFile: (path, data) => atomicWrite(path, data),
|
|
130
|
-
writeBytes: (path, data) => atomicWrite(path, data)
|
|
148
|
+
writeBytes: (path, data) => atomicWrite(path, data),
|
|
149
|
+
createExclusive: (path, data) => createExclusive(path, data),
|
|
150
|
+
deleteFile: async (path) => {
|
|
151
|
+
await rm(path, { force: true });
|
|
152
|
+
}
|
|
131
153
|
};
|
|
132
154
|
var MAX_GLOSSARY_FILE_BYTES = 1024 * 1024;
|
|
133
155
|
var glossaryRecordSchema = z.record(z.string(), z.string());
|
|
@@ -316,29 +338,18 @@ var TIMEOUT_STATUS = 408;
|
|
|
316
338
|
var AUTH_FAILED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
317
339
|
var RATE_LIMITED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
318
340
|
"RateLimitError",
|
|
319
|
-
// openai, @anthropic-ai/sdk
|
|
320
341
|
"TooManyRequestsError"
|
|
321
|
-
// deepl-node
|
|
322
342
|
]);
|
|
323
343
|
var AUTH_FAILED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
324
344
|
"AuthenticationError",
|
|
325
|
-
// openai, @anthropic-ai/sdk (401)
|
|
326
345
|
"PermissionDeniedError",
|
|
327
|
-
// openai, @anthropic-ai/sdk (403)
|
|
328
346
|
"AuthorizationError"
|
|
329
|
-
// deepl-node
|
|
330
347
|
]);
|
|
331
348
|
var TIMEOUT_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
332
349
|
"APIConnectionTimeoutError",
|
|
333
|
-
// openai, @anthropic-ai/sdk
|
|
334
|
-
// deepl-node has no distinct timeout class; ConnectionError is its only network-failure
|
|
335
|
-
// class, so it is folded into TIMEOUT rather than the PROVIDER_ERROR fallback.
|
|
336
350
|
"ConnectionError"
|
|
337
351
|
]);
|
|
338
|
-
var ABORT_ERROR_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
339
|
-
"APIUserAbortError"
|
|
340
|
-
// openai, @anthropic-ai/sdk
|
|
341
|
-
]);
|
|
352
|
+
var ABORT_ERROR_CLASS_NAMES = /* @__PURE__ */ new Set(["APIUserAbortError"]);
|
|
342
353
|
function getErrorStatus(error) {
|
|
343
354
|
if (typeof error !== "object" || error === null || !("status" in error)) {
|
|
344
355
|
return void 0;
|
|
@@ -392,7 +403,6 @@ function isAbortError(error, signal) {
|
|
|
392
403
|
}
|
|
393
404
|
var REDACTED = "[REDACTED]";
|
|
394
405
|
var KEY_PATTERNS = [
|
|
395
|
-
// The `\b` anchors `sk-` to a word start so hyphenated words like "risk-" or "task-" pass through.
|
|
396
406
|
/\bsk-[A-Za-z0-9_-]{8,}/g,
|
|
397
407
|
/AIza[0-9A-Za-z_-]{35}/g,
|
|
398
408
|
/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(?::fx)?/g
|
|
@@ -414,6 +424,10 @@ var ProviderError = class extends Error {
|
|
|
414
424
|
/** The stable {@link ProviderErrorCode} for this failure; branch on this, not the message. */
|
|
415
425
|
code;
|
|
416
426
|
/**
|
|
427
|
+
* Redacts the message as a defense-in-depth backstop before storing it. The redaction runs with an
|
|
428
|
+
* empty secret argument so the ANTHROPIC_API_KEY environment default is not re-applied, keeping
|
|
429
|
+
* this generic error decoupled from any one provider's environment variable.
|
|
430
|
+
*
|
|
417
431
|
* @param code - The stable failure code.
|
|
418
432
|
* @param message - A fixed, safe message; callers must never pass key, SDK, or request-derived text.
|
|
419
433
|
*/
|
|
@@ -981,9 +995,6 @@ function createDeepLProvider(config, deps = {}) {
|
|
|
981
995
|
return {
|
|
982
996
|
id: PROVIDER_ID2,
|
|
983
997
|
kind: "machine-translation",
|
|
984
|
-
// Honest capability signal: DeepL only ever applies a pre-created native glossary id, never a
|
|
985
|
-
// generic term map (see buildTranslateOptions). Without one configured, a supplied term map is
|
|
986
|
-
// silently ignored and surfaces only as a GLOSSARY_IGNORED notice.
|
|
987
998
|
supportsGlossary: validConfig.glossaryId !== void 0,
|
|
988
999
|
translateBatch: (request) => translate(bundle, validConfig, request)
|
|
989
1000
|
};
|
|
@@ -1095,14 +1106,14 @@ function throwAbort(signal) {
|
|
|
1095
1106
|
throw new DOMException("This operation was aborted.", "AbortError");
|
|
1096
1107
|
}
|
|
1097
1108
|
function delay(ms, signal) {
|
|
1098
|
-
return new Promise((
|
|
1109
|
+
return new Promise((resolve9) => {
|
|
1099
1110
|
const onAbort = () => {
|
|
1100
1111
|
clearTimeout(timer);
|
|
1101
|
-
|
|
1112
|
+
resolve9();
|
|
1102
1113
|
};
|
|
1103
1114
|
const timer = setTimeout(() => {
|
|
1104
1115
|
signal?.removeEventListener("abort", onAbort);
|
|
1105
|
-
|
|
1116
|
+
resolve9();
|
|
1106
1117
|
}, ms);
|
|
1107
1118
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1108
1119
|
});
|
|
@@ -1150,12 +1161,10 @@ var TYPE_MAP = {
|
|
|
1150
1161
|
object: "OBJECT"
|
|
1151
1162
|
};
|
|
1152
1163
|
var HANDLED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1153
|
-
// transformed or recursed
|
|
1154
1164
|
"type",
|
|
1155
1165
|
"required",
|
|
1156
1166
|
"properties",
|
|
1157
1167
|
"items",
|
|
1158
|
-
// deliberately dropped (not part of Google's Schema dialect)
|
|
1159
1168
|
"$schema",
|
|
1160
1169
|
"additionalProperties"
|
|
1161
1170
|
]);
|
|
@@ -1723,6 +1732,60 @@ async function loadConfig(options = {}) {
|
|
|
1723
1732
|
const { config } = await loadConfigWithMeta(options);
|
|
1724
1733
|
return config;
|
|
1725
1734
|
}
|
|
1735
|
+
var LOCAL_DIR_NAME = ".verbatra-local";
|
|
1736
|
+
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
1737
|
+
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
1738
|
+
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
1739
|
+
function lockPath(cwd, stem) {
|
|
1740
|
+
return resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
1741
|
+
}
|
|
1742
|
+
function localeLockPath(cwd, locale) {
|
|
1743
|
+
return lockPath(cwd, locale);
|
|
1744
|
+
}
|
|
1745
|
+
function lockFileGuardPath(cwd) {
|
|
1746
|
+
return lockPath(cwd, LOCK_FILE_GUARD_STEM);
|
|
1747
|
+
}
|
|
1748
|
+
function sleep(ms) {
|
|
1749
|
+
return new Promise((res) => {
|
|
1750
|
+
setTimeout(res, ms);
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
function lockPayload() {
|
|
1754
|
+
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1755
|
+
}
|
|
1756
|
+
async function acquireLock(path, fs, pollIntervalMs, deadline) {
|
|
1757
|
+
for (; ; ) {
|
|
1758
|
+
if (await fs.createExclusive(path, lockPayload())) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (Date.now() >= deadline) {
|
|
1762
|
+
throw new SdkError(
|
|
1763
|
+
"LOCK_CONTENDED",
|
|
1764
|
+
`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
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
const jitter = Math.random() * pollIntervalMs;
|
|
1768
|
+
await sleep(pollIntervalMs + jitter);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
async function withFileLock(path, fs, fn, options) {
|
|
1772
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1773
|
+
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
1774
|
+
await acquireLock(path, fs, pollIntervalMs, Date.now() + acquireTimeoutMs);
|
|
1775
|
+
try {
|
|
1776
|
+
return await fn();
|
|
1777
|
+
} finally {
|
|
1778
|
+
await fs.deleteFile(path);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
async function withLocaleWriteLock(cwd, locale, fs, fn, options = {}) {
|
|
1782
|
+
return withFileLock(localeLockPath(cwd, locale), fs, fn, options);
|
|
1783
|
+
}
|
|
1784
|
+
async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
1785
|
+
return withFileLock(lockFileGuardPath(cwd), fs, fn, options);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// src/lock/lock-file.ts
|
|
1726
1789
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1727
1790
|
var CURRENT_VERSION = 1;
|
|
1728
1791
|
var EMPTY_LOCK = { version: CURRENT_VERSION, locales: {} };
|
|
@@ -1734,8 +1797,7 @@ var lockFileSchema = z.object({
|
|
|
1734
1797
|
function lockFilePath(cwd) {
|
|
1735
1798
|
return resolve(cwd, LOCK_FILE_NAME);
|
|
1736
1799
|
}
|
|
1737
|
-
|
|
1738
|
-
const read = await fs.readFileBounded(path, MAX_LOCK_FILE_BYTES);
|
|
1800
|
+
function parseLockFileRead(read, path) {
|
|
1739
1801
|
if (read.kind === "missing") {
|
|
1740
1802
|
return EMPTY_LOCK;
|
|
1741
1803
|
}
|
|
@@ -1763,6 +1825,9 @@ async function readLockFile(path, fs) {
|
|
|
1763
1825
|
}
|
|
1764
1826
|
return result.data;
|
|
1765
1827
|
}
|
|
1828
|
+
async function readLockFile(path, fs) {
|
|
1829
|
+
return parseLockFileRead(await fs.readFileBounded(path, MAX_LOCK_FILE_BYTES), path);
|
|
1830
|
+
}
|
|
1766
1831
|
function baselineFor(lock, locale) {
|
|
1767
1832
|
return new Map(Object.entries(lock.locales[locale] ?? {}));
|
|
1768
1833
|
}
|
|
@@ -1778,14 +1843,30 @@ function byKey(a, b) {
|
|
|
1778
1843
|
function sortRecord(record) {
|
|
1779
1844
|
return Object.fromEntries(Object.entries(record).sort(byKey));
|
|
1780
1845
|
}
|
|
1781
|
-
|
|
1846
|
+
function serializeLockFile(lock) {
|
|
1782
1847
|
const locales = {};
|
|
1783
1848
|
for (const [locale, entries] of Object.entries(lock.locales).sort(byKey)) {
|
|
1784
1849
|
locales[locale] = sortRecord(entries);
|
|
1785
1850
|
}
|
|
1786
1851
|
const ordered = { version: lock.version, locales };
|
|
1787
|
-
|
|
1788
|
-
|
|
1852
|
+
return `${JSON.stringify(ordered, null, 2)}
|
|
1853
|
+
`;
|
|
1854
|
+
}
|
|
1855
|
+
function applyLockLocalePatch(currentEntries, patch) {
|
|
1856
|
+
if (patch.mode === "replace") {
|
|
1857
|
+
return patch.entries;
|
|
1858
|
+
}
|
|
1859
|
+
return { ...currentEntries, ...patch.entries };
|
|
1860
|
+
}
|
|
1861
|
+
async function updateLockFileLocale(cwd, fs, locale, patch) {
|
|
1862
|
+
return withLockFileGuard(cwd, fs, async () => {
|
|
1863
|
+
const path = lockFilePath(cwd);
|
|
1864
|
+
const lock = parseLockFileRead(await fs.readFileBounded(path, MAX_LOCK_FILE_BYTES), path);
|
|
1865
|
+
const nextEntries = applyLockLocalePatch(lock.locales[locale], patch);
|
|
1866
|
+
const next = updateLockLocale(lock, locale, nextEntries);
|
|
1867
|
+
await fs.writeFile(path, serializeLockFile(next));
|
|
1868
|
+
return next;
|
|
1869
|
+
});
|
|
1789
1870
|
}
|
|
1790
1871
|
var VALID_EMPTY = { placeholders: [], isPlural: false, valid: true };
|
|
1791
1872
|
var INVALID = { placeholders: [], isPlural: false, valid: false };
|
|
@@ -2047,10 +2128,68 @@ var AdapterError = class extends Error {
|
|
|
2047
2128
|
};
|
|
2048
2129
|
var MAX_DEPTH = 100;
|
|
2049
2130
|
var MAX_INPUT_BYTES = 16 * 1024 * 1024;
|
|
2050
|
-
var
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2131
|
+
var SENTINEL_ESCAPE = "\\u0001";
|
|
2132
|
+
function isJsonWhitespace(char) {
|
|
2133
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
2134
|
+
}
|
|
2135
|
+
function endOfStringToken(content, openQuote) {
|
|
2136
|
+
let index = openQuote + 1;
|
|
2137
|
+
while (index < content.length) {
|
|
2138
|
+
const char = content[index];
|
|
2139
|
+
if (char === "\\") {
|
|
2140
|
+
index += 2;
|
|
2141
|
+
} else if (char === '"') {
|
|
2142
|
+
return index + 1;
|
|
2143
|
+
} else {
|
|
2144
|
+
index += 1;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
return index;
|
|
2148
|
+
}
|
|
2149
|
+
function isKeyToken(content, end) {
|
|
2150
|
+
let index = end;
|
|
2151
|
+
while (isJsonWhitespace(content[index])) {
|
|
2152
|
+
index += 1;
|
|
2153
|
+
}
|
|
2154
|
+
return content[index] === ":";
|
|
2155
|
+
}
|
|
2156
|
+
function prefixKeyToken(token) {
|
|
2157
|
+
if (token.includes(SENTINEL_ESCAPE)) {
|
|
2158
|
+
throw new AdapterError(
|
|
2159
|
+
"INVALID_STRUCTURE",
|
|
2160
|
+
"A key contains a reserved control-character escape."
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
return `"${SENTINEL_ESCAPE}${token.slice(1)}`;
|
|
2164
|
+
}
|
|
2165
|
+
function prefixKeyTokens(content) {
|
|
2166
|
+
let out = "";
|
|
2167
|
+
let index = 0;
|
|
2168
|
+
while (index < content.length) {
|
|
2169
|
+
const openQuote = content.indexOf('"', index);
|
|
2170
|
+
if (openQuote === -1) {
|
|
2171
|
+
return out + content.slice(index);
|
|
2172
|
+
}
|
|
2173
|
+
const end = endOfStringToken(content, openQuote);
|
|
2174
|
+
const token = content.slice(openQuote, end);
|
|
2175
|
+
out += content.slice(index, openQuote);
|
|
2176
|
+
out += isKeyToken(content, end) ? prefixKeyToken(token) : token;
|
|
2177
|
+
index = end;
|
|
2178
|
+
}
|
|
2179
|
+
return out;
|
|
2180
|
+
}
|
|
2181
|
+
function childrenOf(node) {
|
|
2182
|
+
if (node instanceof Map) {
|
|
2183
|
+
return node.values();
|
|
2184
|
+
}
|
|
2185
|
+
if (Array.isArray(node)) {
|
|
2186
|
+
return node;
|
|
2187
|
+
}
|
|
2188
|
+
if (typeof node === "object" && node !== null) {
|
|
2189
|
+
return Object.values(node);
|
|
2190
|
+
}
|
|
2191
|
+
return null;
|
|
2192
|
+
}
|
|
2054
2193
|
function assertWithinDepth(value, max) {
|
|
2055
2194
|
const stack = [{ node: value, depth: 1 }];
|
|
2056
2195
|
while (stack.length > 0) {
|
|
@@ -2058,44 +2197,117 @@ function assertWithinDepth(value, max) {
|
|
|
2058
2197
|
if (top === void 0) {
|
|
2059
2198
|
break;
|
|
2060
2199
|
}
|
|
2061
|
-
const
|
|
2062
|
-
if (
|
|
2200
|
+
const children = childrenOf(top.node);
|
|
2201
|
+
if (children === null) {
|
|
2063
2202
|
continue;
|
|
2064
2203
|
}
|
|
2065
|
-
if (depth > max) {
|
|
2204
|
+
if (top.depth > max) {
|
|
2066
2205
|
throw new AdapterError("MAX_DEPTH_EXCEEDED", "The file nests objects too deeply.");
|
|
2067
2206
|
}
|
|
2068
|
-
for (const child of
|
|
2069
|
-
stack.push({ node: child, depth: depth + 1 });
|
|
2207
|
+
for (const child of children) {
|
|
2208
|
+
stack.push({ node: child, depth: top.depth + 1 });
|
|
2070
2209
|
}
|
|
2071
2210
|
}
|
|
2072
2211
|
}
|
|
2073
|
-
function
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
if (!result.success) {
|
|
2077
|
-
throw new AdapterError(
|
|
2078
|
-
"INVALID_STRUCTURE",
|
|
2079
|
-
"The file is not a valid object (expected nested objects of string, number, boolean, or null leaves)."
|
|
2080
|
-
);
|
|
2212
|
+
function toOrdered(node) {
|
|
2213
|
+
if (Array.isArray(node)) {
|
|
2214
|
+
return node.map((child) => toOrdered(child));
|
|
2081
2215
|
}
|
|
2082
|
-
|
|
2216
|
+
if (typeof node === "object" && node !== null) {
|
|
2217
|
+
const out = /* @__PURE__ */ new Map();
|
|
2218
|
+
for (const [key, child] of Object.entries(node)) {
|
|
2219
|
+
out.set(key.slice(1), toOrdered(child));
|
|
2220
|
+
}
|
|
2221
|
+
return out;
|
|
2222
|
+
}
|
|
2223
|
+
return node;
|
|
2083
2224
|
}
|
|
2084
|
-
function
|
|
2225
|
+
function parseOrderedJson(content) {
|
|
2226
|
+
const prefixed = prefixKeyTokens(content);
|
|
2085
2227
|
let parsed;
|
|
2086
2228
|
try {
|
|
2087
|
-
parsed = JSON.parse(
|
|
2229
|
+
parsed = JSON.parse(prefixed);
|
|
2088
2230
|
} catch {
|
|
2089
2231
|
throw new AdapterError("INVALID_JSON", "The file is not valid JSON.");
|
|
2090
2232
|
}
|
|
2091
|
-
|
|
2233
|
+
assertWithinDepth(parsed, MAX_DEPTH);
|
|
2234
|
+
return toOrdered(parsed);
|
|
2092
2235
|
}
|
|
2093
|
-
function
|
|
2094
|
-
|
|
2236
|
+
function printRecord(record, indent) {
|
|
2237
|
+
if (record.size === 0) {
|
|
2238
|
+
return "{}";
|
|
2239
|
+
}
|
|
2240
|
+
const childIndent = `${indent} `;
|
|
2241
|
+
const lines = [];
|
|
2242
|
+
for (const [key, child] of record) {
|
|
2243
|
+
lines.push(`${childIndent}${JSON.stringify(key)}: ${printValue(child, childIndent)}`);
|
|
2244
|
+
}
|
|
2245
|
+
return `{
|
|
2246
|
+
${lines.join(",\n")}
|
|
2247
|
+
${indent}}`;
|
|
2248
|
+
}
|
|
2249
|
+
function printArray(items, indent) {
|
|
2250
|
+
if (items.length === 0) {
|
|
2251
|
+
return "[]";
|
|
2252
|
+
}
|
|
2253
|
+
const childIndent = `${indent} `;
|
|
2254
|
+
const lines = items.map((item) => `${childIndent}${printValue(item, childIndent)}`);
|
|
2255
|
+
return `[
|
|
2256
|
+
${lines.join(",\n")}
|
|
2257
|
+
${indent}]`;
|
|
2258
|
+
}
|
|
2259
|
+
function printValue(value, indent) {
|
|
2260
|
+
if (value instanceof Map) {
|
|
2261
|
+
return printRecord(value, indent);
|
|
2262
|
+
}
|
|
2263
|
+
if (Array.isArray(value)) {
|
|
2264
|
+
return printArray(value, indent);
|
|
2265
|
+
}
|
|
2266
|
+
return JSON.stringify(value);
|
|
2267
|
+
}
|
|
2268
|
+
function serializeOrderedJson(value) {
|
|
2269
|
+
return `${printValue(value, "")}
|
|
2095
2270
|
`;
|
|
2096
2271
|
}
|
|
2272
|
+
var INVALID_STRUCTURE_MESSAGE = "The file is not a valid object (expected nested objects of string, number, boolean, or null leaves).";
|
|
2273
|
+
function isJsonLeaf(value) {
|
|
2274
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
|
|
2275
|
+
}
|
|
2276
|
+
function assertNodesValid(root) {
|
|
2277
|
+
const stack = [root];
|
|
2278
|
+
while (stack.length > 0) {
|
|
2279
|
+
const node = stack.pop();
|
|
2280
|
+
if (node === void 0) {
|
|
2281
|
+
break;
|
|
2282
|
+
}
|
|
2283
|
+
for (const [key, child] of node) {
|
|
2284
|
+
if (typeof key !== "string") {
|
|
2285
|
+
throw new AdapterError("INVALID_STRUCTURE", INVALID_STRUCTURE_MESSAGE);
|
|
2286
|
+
}
|
|
2287
|
+
if (child instanceof Map) {
|
|
2288
|
+
stack.push(child);
|
|
2289
|
+
} else if (!isJsonLeaf(child)) {
|
|
2290
|
+
throw new AdapterError("INVALID_STRUCTURE", INVALID_STRUCTURE_MESSAGE);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
function assertJsonRecord(value) {
|
|
2296
|
+
assertWithinDepth(value, MAX_DEPTH);
|
|
2297
|
+
if (!(value instanceof Map)) {
|
|
2298
|
+
throw new AdapterError("INVALID_STRUCTURE", INVALID_STRUCTURE_MESSAGE);
|
|
2299
|
+
}
|
|
2300
|
+
assertNodesValid(value);
|
|
2301
|
+
return value;
|
|
2302
|
+
}
|
|
2303
|
+
function parseJsonObject(content) {
|
|
2304
|
+
return assertJsonRecord(parseOrderedJson(content));
|
|
2305
|
+
}
|
|
2306
|
+
function serializeJsonTree(tree) {
|
|
2307
|
+
return serializeOrderedJson(tree);
|
|
2308
|
+
}
|
|
2097
2309
|
function isJsonNode(value) {
|
|
2098
|
-
return
|
|
2310
|
+
return value instanceof Map;
|
|
2099
2311
|
}
|
|
2100
2312
|
function namespaceOf(filePath) {
|
|
2101
2313
|
return basename(filePath, extname(filePath));
|
|
@@ -2297,7 +2509,7 @@ function addLeaf(ctx, segments, key, value) {
|
|
|
2297
2509
|
ctx.out.set(mapKey, { key: mapKey, namespace: ctx.namespace, value, placeholders, isPlural });
|
|
2298
2510
|
}
|
|
2299
2511
|
function addEntries(ctx, prefix, node) {
|
|
2300
|
-
for (const [key, value] of
|
|
2512
|
+
for (const [key, value] of node) {
|
|
2301
2513
|
const segments = [...prefix, key];
|
|
2302
2514
|
if (isJsonNode(value)) {
|
|
2303
2515
|
addEntries(ctx, segments, value);
|
|
@@ -2307,7 +2519,7 @@ function addEntries(ctx, prefix, node) {
|
|
|
2307
2519
|
}
|
|
2308
2520
|
}
|
|
2309
2521
|
function addPathEntries(node, prefix, namespace, derive, out, claimedPaths, excluded) {
|
|
2310
|
-
for (const [key, value] of
|
|
2522
|
+
for (const [key, value] of node) {
|
|
2311
2523
|
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
2312
2524
|
if (claimedPaths.has(path)) {
|
|
2313
2525
|
throw new AdapterError(
|
|
@@ -2337,17 +2549,14 @@ function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
|
|
|
2337
2549
|
addEntries(ctx, [], tree);
|
|
2338
2550
|
return { entries: out, excludedLeafPaths: ctx.excluded };
|
|
2339
2551
|
}
|
|
2340
|
-
function emptyNode() {
|
|
2341
|
-
return /* @__PURE__ */ Object.create(null);
|
|
2342
|
-
}
|
|
2343
2552
|
function descend(node, segment) {
|
|
2344
|
-
const next = node
|
|
2553
|
+
const next = node.get(segment);
|
|
2345
2554
|
if (next === void 0) {
|
|
2346
|
-
const created =
|
|
2347
|
-
node
|
|
2555
|
+
const created = /* @__PURE__ */ new Map();
|
|
2556
|
+
node.set(segment, created);
|
|
2348
2557
|
return created;
|
|
2349
2558
|
}
|
|
2350
|
-
if (
|
|
2559
|
+
if (next instanceof Map) {
|
|
2351
2560
|
return next;
|
|
2352
2561
|
}
|
|
2353
2562
|
throw new AdapterError("INVALID_STRUCTURE", "A leaf key collides with a nested key path.");
|
|
@@ -2361,13 +2570,13 @@ function setPath(root, segments, value) {
|
|
|
2361
2570
|
for (const segment of segments.slice(0, -1)) {
|
|
2362
2571
|
node = descend(node, segment);
|
|
2363
2572
|
}
|
|
2364
|
-
if (
|
|
2573
|
+
if (node.get(leaf) instanceof Map) {
|
|
2365
2574
|
throw new AdapterError("INVALID_STRUCTURE", "A leaf key collides with a nested key path.");
|
|
2366
2575
|
}
|
|
2367
|
-
node
|
|
2576
|
+
node.set(leaf, value);
|
|
2368
2577
|
}
|
|
2369
2578
|
function unflattenEntries(entries) {
|
|
2370
|
-
const root =
|
|
2579
|
+
const root = /* @__PURE__ */ new Map();
|
|
2371
2580
|
for (const [key, entry] of entries) {
|
|
2372
2581
|
setPath(root, decodeKeyToSegments(key), entry.value);
|
|
2373
2582
|
}
|
|
@@ -2444,13 +2653,8 @@ function isMetadataKey(key) {
|
|
|
2444
2653
|
return key.startsWith("@");
|
|
2445
2654
|
}
|
|
2446
2655
|
function parseArbObject(content) {
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
parsed = JSON.parse(content);
|
|
2450
|
-
} catch {
|
|
2451
|
-
throw new AdapterError("INVALID_JSON", "The file is not valid JSON.");
|
|
2452
|
-
}
|
|
2453
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2656
|
+
const parsed = parseOrderedJson(content);
|
|
2657
|
+
if (!(parsed instanceof Map)) {
|
|
2454
2658
|
throw new AdapterError(
|
|
2455
2659
|
"INVALID_STRUCTURE",
|
|
2456
2660
|
"The file is not a valid object (expected nested objects of string values)."
|
|
@@ -2459,10 +2663,10 @@ function parseArbObject(content) {
|
|
|
2459
2663
|
return parsed;
|
|
2460
2664
|
}
|
|
2461
2665
|
function stripArbMetadata(tree) {
|
|
2462
|
-
const out = /* @__PURE__ */
|
|
2463
|
-
for (const [key, value] of
|
|
2666
|
+
const out = /* @__PURE__ */ new Map();
|
|
2667
|
+
for (const [key, value] of tree) {
|
|
2464
2668
|
if (!isMetadataKey(key)) {
|
|
2465
|
-
out
|
|
2669
|
+
out.set(key, value);
|
|
2466
2670
|
}
|
|
2467
2671
|
}
|
|
2468
2672
|
return out;
|
|
@@ -2474,16 +2678,16 @@ function messageKeyForMetadata(key) {
|
|
|
2474
2678
|
return isMetadataKey(key) && !key.startsWith("@@") ? key.slice(1) : null;
|
|
2475
2679
|
}
|
|
2476
2680
|
function descriptionOf(value) {
|
|
2477
|
-
if (
|
|
2681
|
+
if (!(value instanceof Map)) {
|
|
2478
2682
|
return void 0;
|
|
2479
2683
|
}
|
|
2480
|
-
const description = value.description;
|
|
2684
|
+
const description = value.get("description");
|
|
2481
2685
|
return typeof description === "string" ? description : void 0;
|
|
2482
2686
|
}
|
|
2483
2687
|
function extractArbDescriptions(content) {
|
|
2484
2688
|
const tree = parseArbObject(content);
|
|
2485
2689
|
const out = /* @__PURE__ */ new Map();
|
|
2486
|
-
for (const [key, value] of
|
|
2690
|
+
for (const [key, value] of tree) {
|
|
2487
2691
|
const messageKey = messageKeyForMetadata(key);
|
|
2488
2692
|
if (messageKey === null) {
|
|
2489
2693
|
continue;
|
|
@@ -2515,25 +2719,25 @@ async function readDestinationPairs(filePath) {
|
|
|
2515
2719
|
}
|
|
2516
2720
|
throw error;
|
|
2517
2721
|
}
|
|
2518
|
-
return
|
|
2722
|
+
return [...parseArbObject(content)];
|
|
2519
2723
|
}
|
|
2520
2724
|
async function buildArbWriteTree(entries, filePath) {
|
|
2521
2725
|
const messages = messagesFromEntries(entries);
|
|
2522
2726
|
const pairs = await readDestinationPairs(filePath);
|
|
2523
|
-
const out = /* @__PURE__ */
|
|
2727
|
+
const out = /* @__PURE__ */ new Map();
|
|
2524
2728
|
const consumed = /* @__PURE__ */ new Set();
|
|
2525
2729
|
for (const [key, value] of pairs ?? []) {
|
|
2526
2730
|
const translated = isMetadataKey(key) ? void 0 : messages.get(key);
|
|
2527
2731
|
if (translated !== void 0) {
|
|
2528
2732
|
consumed.add(key);
|
|
2529
|
-
out
|
|
2733
|
+
out.set(key, translated);
|
|
2530
2734
|
} else if (isMetadataKey(key) || typeof value === "string") {
|
|
2531
|
-
out
|
|
2735
|
+
out.set(key, value);
|
|
2532
2736
|
}
|
|
2533
2737
|
}
|
|
2534
2738
|
for (const [key, value] of messages) {
|
|
2535
2739
|
if (!consumed.has(key)) {
|
|
2536
|
-
out
|
|
2740
|
+
out.set(key, value);
|
|
2537
2741
|
}
|
|
2538
2742
|
}
|
|
2539
2743
|
return out;
|
|
@@ -2622,7 +2826,7 @@ function createNextIntlJsonAdapter() {
|
|
|
2622
2826
|
});
|
|
2623
2827
|
}
|
|
2624
2828
|
function assertNoDottedNestedKey(tree) {
|
|
2625
|
-
for (const [key, value] of
|
|
2829
|
+
for (const [key, value] of tree) {
|
|
2626
2830
|
if (!isJsonNode(value)) {
|
|
2627
2831
|
continue;
|
|
2628
2832
|
}
|
|
@@ -2638,7 +2842,7 @@ function assertNoDottedNestedKey(tree) {
|
|
|
2638
2842
|
function assertNotMixed(tree) {
|
|
2639
2843
|
let hasNested = false;
|
|
2640
2844
|
let hasFlatDottedKey = false;
|
|
2641
|
-
for (const [key, value] of
|
|
2845
|
+
for (const [key, value] of tree) {
|
|
2642
2846
|
if (isJsonNode(value)) {
|
|
2643
2847
|
hasNested = true;
|
|
2644
2848
|
} else if (key.includes(".")) {
|
|
@@ -2675,9 +2879,9 @@ async function detectStyle(filePath) {
|
|
|
2675
2879
|
return "flat";
|
|
2676
2880
|
}
|
|
2677
2881
|
function buildFlatTree(entries) {
|
|
2678
|
-
const out = /* @__PURE__ */
|
|
2882
|
+
const out = /* @__PURE__ */ new Map();
|
|
2679
2883
|
for (const [key, entry] of entries) {
|
|
2680
|
-
out
|
|
2884
|
+
out.set(key, entry.value);
|
|
2681
2885
|
}
|
|
2682
2886
|
return out;
|
|
2683
2887
|
}
|
|
@@ -2695,7 +2899,6 @@ function createNgxTranslateJsonAdapter() {
|
|
|
2695
2899
|
}),
|
|
2696
2900
|
validateTree: assertNotMixed,
|
|
2697
2901
|
buildWriteTree: buildNgxWriteTree,
|
|
2698
|
-
// ngx-translate flat style uses dotted keys as path notation, not literal leaves.
|
|
2699
2902
|
keyMode: "path-notation"
|
|
2700
2903
|
});
|
|
2701
2904
|
}
|
|
@@ -2721,6 +2924,11 @@ var AdapterRegistry = class {
|
|
|
2721
2924
|
}
|
|
2722
2925
|
return { status: "resolved", adapter };
|
|
2723
2926
|
}
|
|
2927
|
+
/**
|
|
2928
|
+
* Resolve by asking every registered adapter's `canHandle`. Several adapters can claim one file
|
|
2929
|
+
* (all JSON adapters claim `.json`), so more than one match is reported as `ambiguous` rather
|
|
2930
|
+
* than guessed at.
|
|
2931
|
+
*/
|
|
2724
2932
|
resolveByDetection(filePath, sample) {
|
|
2725
2933
|
const matches = this.adapters.filter((adapter) => adapter.canHandle(filePath, sample));
|
|
2726
2934
|
const first = matches[0];
|
|
@@ -3075,14 +3283,34 @@ function createXliffAdapter() {
|
|
|
3075
3283
|
extractPlaceholders: extractXliffPlaceholders
|
|
3076
3284
|
});
|
|
3077
3285
|
}
|
|
3286
|
+
function normalizeKey(key) {
|
|
3287
|
+
if (typeof key === "object" && key !== null) {
|
|
3288
|
+
throw new AdapterError(
|
|
3289
|
+
"INVALID_STRUCTURE",
|
|
3290
|
+
"A mapping key is a map or sequence (expected scalar keys)."
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
return String(key);
|
|
3294
|
+
}
|
|
3295
|
+
function normalizeYamlTree(value) {
|
|
3296
|
+
if (!(value instanceof Map)) {
|
|
3297
|
+
return value;
|
|
3298
|
+
}
|
|
3299
|
+
const out = /* @__PURE__ */ new Map();
|
|
3300
|
+
for (const [key, child] of value) {
|
|
3301
|
+
out.set(normalizeKey(key), normalizeYamlTree(child));
|
|
3302
|
+
}
|
|
3303
|
+
return out;
|
|
3304
|
+
}
|
|
3078
3305
|
function parseYamlObject(content) {
|
|
3079
3306
|
let parsed;
|
|
3080
3307
|
try {
|
|
3081
|
-
parsed = parse$1(content, { maxAliasCount: 100 });
|
|
3308
|
+
parsed = parse$1(content, { mapAsMap: true, maxAliasCount: 100 });
|
|
3082
3309
|
} catch {
|
|
3083
3310
|
throw new AdapterError("INVALID_YAML", "The file is not valid YAML.");
|
|
3084
3311
|
}
|
|
3085
|
-
|
|
3312
|
+
assertWithinDepth(parsed, MAX_DEPTH);
|
|
3313
|
+
return assertJsonRecord(normalizeYamlTree(parsed));
|
|
3086
3314
|
}
|
|
3087
3315
|
function createYamlAdapter() {
|
|
3088
3316
|
return createTreeFileAdapter({
|
|
@@ -3113,6 +3341,15 @@ function selectAdapter(format, registry = createDefaultRegistry()) {
|
|
|
3113
3341
|
);
|
|
3114
3342
|
}
|
|
3115
3343
|
|
|
3344
|
+
// src/flow/read-target.ts
|
|
3345
|
+
async function readTargetResource(input) {
|
|
3346
|
+
const path = localeFilePath(input.cwd, input.filesPattern, input.locale);
|
|
3347
|
+
if (!await input.fs.fileExists(path)) {
|
|
3348
|
+
return { locale: input.locale, namespace: "", format: input.format, entries: /* @__PURE__ */ new Map() };
|
|
3349
|
+
}
|
|
3350
|
+
return (await input.adapter.read(path, input.locale)).resource;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3116
3353
|
// src/flow/select-locales.ts
|
|
3117
3354
|
function selectLocales(config, requested) {
|
|
3118
3355
|
if (requested === void 0) {
|
|
@@ -3153,11 +3390,14 @@ async function readSource(config, cwd, fs, adapter) {
|
|
|
3153
3390
|
|
|
3154
3391
|
// src/flow/diff-locales.ts
|
|
3155
3392
|
async function readTarget(cwd, config, adapter, fs, locale) {
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3393
|
+
return readTargetResource({
|
|
3394
|
+
cwd,
|
|
3395
|
+
filesPattern: config.files.pattern,
|
|
3396
|
+
format: config.format,
|
|
3397
|
+
locale,
|
|
3398
|
+
adapter,
|
|
3399
|
+
fs
|
|
3400
|
+
});
|
|
3161
3401
|
}
|
|
3162
3402
|
async function diffLocales(input, deps = {}) {
|
|
3163
3403
|
const config = input.config;
|
|
@@ -3207,6 +3447,166 @@ async function diff(input, deps = {}) {
|
|
|
3207
3447
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
3208
3448
|
}
|
|
3209
3449
|
|
|
3450
|
+
// src/flow/integrity-gate.ts
|
|
3451
|
+
function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
3452
|
+
const placeholderResult = adapter.comparePlaceholders?.(sourceEntry.value, candidateValue) ?? checkPlaceholders(sourceEntry.placeholders, adapter.extractPlaceholders(candidateValue));
|
|
3453
|
+
if (!placeholderResult.matches) {
|
|
3454
|
+
return { accepted: false, reason: "placeholder" };
|
|
3455
|
+
}
|
|
3456
|
+
if (!adapter.validateMessage(candidateValue)) {
|
|
3457
|
+
return { accepted: false, reason: "icu" };
|
|
3458
|
+
}
|
|
3459
|
+
return { accepted: true };
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
// src/flow/edit-entry.ts
|
|
3463
|
+
async function editEntry(input, deps = {}) {
|
|
3464
|
+
const config = input.config;
|
|
3465
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3466
|
+
const fs = deps.fs ?? defaultFs;
|
|
3467
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3468
|
+
const [locale] = selectLocales(config, [input.locale]);
|
|
3469
|
+
if (locale === void 0) {
|
|
3470
|
+
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
3471
|
+
}
|
|
3472
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
3473
|
+
const sourceEntry = source.resource.entries.get(input.key);
|
|
3474
|
+
if (sourceEntry === void 0) {
|
|
3475
|
+
throw new SdkError(
|
|
3476
|
+
"UNKNOWN_KEY",
|
|
3477
|
+
`The key "${input.key}" was not found in the source resource.`
|
|
3478
|
+
);
|
|
3479
|
+
}
|
|
3480
|
+
return withLocaleWriteLock(cwd, locale, fs, async () => {
|
|
3481
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
3482
|
+
const gate = gateCandidateValue(sourceEntry, input.value, adapter);
|
|
3483
|
+
if (!gate.accepted) {
|
|
3484
|
+
return { accepted: false, reason: gate.reason, value: input.value };
|
|
3485
|
+
}
|
|
3486
|
+
const merged = new Map(target.entries);
|
|
3487
|
+
merged.set(input.key, { ...sourceEntry, value: input.value, namespace: target.namespace });
|
|
3488
|
+
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
3489
|
+
await adapter.write(
|
|
3490
|
+
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
3491
|
+
path
|
|
3492
|
+
);
|
|
3493
|
+
await updateLockFileLocale(cwd, fs, locale, {
|
|
3494
|
+
mode: "merge",
|
|
3495
|
+
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3496
|
+
});
|
|
3497
|
+
return { accepted: true, value: input.value };
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
// src/flow/key-integrity.ts
|
|
3502
|
+
function checkEntryIntegrity(adapter, sourceEntry, targetEntry) {
|
|
3503
|
+
const result = adapter.comparePlaceholders?.(sourceEntry.value, targetEntry.value) ?? checkPlaceholders(sourceEntry.placeholders, targetEntry.placeholders);
|
|
3504
|
+
return {
|
|
3505
|
+
key: sourceEntry.key,
|
|
3506
|
+
hasPlaceholders: sourceEntry.placeholders.length > 0,
|
|
3507
|
+
matches: result.matches,
|
|
3508
|
+
missing: result.missing,
|
|
3509
|
+
extra: result.extra,
|
|
3510
|
+
icuValid: adapter.validateMessage(targetEntry.value)
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
function selectChangedKeys(changed, requested) {
|
|
3514
|
+
if (requested === void 0) {
|
|
3515
|
+
return changed;
|
|
3516
|
+
}
|
|
3517
|
+
const wanted = new Set(requested);
|
|
3518
|
+
return changed.filter((key) => wanted.has(key));
|
|
3519
|
+
}
|
|
3520
|
+
function integrityEntriesFor(source, target, adapter, changedKeys) {
|
|
3521
|
+
const entries = [];
|
|
3522
|
+
for (const key of changedKeys) {
|
|
3523
|
+
const sourceEntry = source.entries.get(key);
|
|
3524
|
+
const targetEntry = target.entries.get(key);
|
|
3525
|
+
if (sourceEntry === void 0 || targetEntry === void 0) {
|
|
3526
|
+
continue;
|
|
3527
|
+
}
|
|
3528
|
+
entries.push(checkEntryIntegrity(adapter, sourceEntry, targetEntry));
|
|
3529
|
+
}
|
|
3530
|
+
return entries;
|
|
3531
|
+
}
|
|
3532
|
+
async function keyIntegrity(input, deps = {}) {
|
|
3533
|
+
const config = input.config;
|
|
3534
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3535
|
+
const fs = deps.fs ?? defaultFs;
|
|
3536
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3537
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
3538
|
+
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
3539
|
+
return Promise.all(
|
|
3540
|
+
selectLocales(config, input.locales).map(async (locale) => {
|
|
3541
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
3542
|
+
const diffResult = diffResources(source.resource, target, {
|
|
3543
|
+
baseline: baselineFor(lock, locale)
|
|
3544
|
+
});
|
|
3545
|
+
const changedKeys = selectChangedKeys(diffResult.changed, input.keys);
|
|
3546
|
+
const entries = integrityEntriesFor(source.resource, target, adapter, changedKeys);
|
|
3547
|
+
return { locale, entries };
|
|
3548
|
+
})
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
// src/flow/key-value.ts
|
|
3553
|
+
async function keyValue(input, deps = {}) {
|
|
3554
|
+
const config = input.config;
|
|
3555
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3556
|
+
const fs = deps.fs ?? defaultFs;
|
|
3557
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3558
|
+
const [locale] = selectLocales(config, [input.locale]);
|
|
3559
|
+
if (locale === void 0) {
|
|
3560
|
+
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
3561
|
+
}
|
|
3562
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
3563
|
+
const sourceEntry = source.resource.entries.get(input.key);
|
|
3564
|
+
if (sourceEntry === void 0) {
|
|
3565
|
+
throw new SdkError(
|
|
3566
|
+
"UNKNOWN_KEY",
|
|
3567
|
+
`The key "${input.key}" was not found in the source resource.`
|
|
3568
|
+
);
|
|
3569
|
+
}
|
|
3570
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
3571
|
+
const targetEntry = target.entries.get(input.key);
|
|
3572
|
+
return {
|
|
3573
|
+
source: sourceEntry.value,
|
|
3574
|
+
...targetEntry !== void 0 ? { target: targetEntry.value } : {}
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
// src/flow/locale-snapshot.ts
|
|
3579
|
+
async function readLocaleFileSnapshot(input, deps = {}) {
|
|
3580
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3581
|
+
const fs = deps.fs ?? defaultFs;
|
|
3582
|
+
const adapter = selectAdapter(input.config.format, deps.adapterRegistry);
|
|
3583
|
+
const resource = await readTarget(cwd, input.config, adapter, fs, input.locale);
|
|
3584
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
3585
|
+
for (const [key, entry] of resource.entries) {
|
|
3586
|
+
hashes.set(key, contentHash(entry));
|
|
3587
|
+
}
|
|
3588
|
+
return { locale: input.locale, hashes };
|
|
3589
|
+
}
|
|
3590
|
+
function diffLocaleSnapshots(previous, current) {
|
|
3591
|
+
let added = 0;
|
|
3592
|
+
let changed = 0;
|
|
3593
|
+
for (const [key, hash] of current.hashes) {
|
|
3594
|
+
const previousHash = previous.hashes.get(key);
|
|
3595
|
+
if (previousHash === void 0) {
|
|
3596
|
+
added += 1;
|
|
3597
|
+
} else if (previousHash !== hash) {
|
|
3598
|
+
changed += 1;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
let removed = 0;
|
|
3602
|
+
for (const key of previous.hashes.keys()) {
|
|
3603
|
+
if (!current.hashes.has(key)) {
|
|
3604
|
+
removed += 1;
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
return { added, changed, removed };
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3210
3610
|
// src/flow/lock-state.ts
|
|
3211
3611
|
function toLockLocaleState(locale, keyCount, diff2) {
|
|
3212
3612
|
return {
|
|
@@ -3254,6 +3654,174 @@ function selectProvider(config, createProvider = buildProvider) {
|
|
|
3254
3654
|
}
|
|
3255
3655
|
}
|
|
3256
3656
|
|
|
3657
|
+
// src/flow/retranslate-entry.ts
|
|
3658
|
+
function buildSingleEntryRequest(config, locale, sourceEntry, adapter) {
|
|
3659
|
+
return {
|
|
3660
|
+
sourceLocale: config.sourceLocale,
|
|
3661
|
+
targetLocale: locale,
|
|
3662
|
+
entries: [sourceEntry],
|
|
3663
|
+
extractPlaceholders: adapter.extractPlaceholders,
|
|
3664
|
+
...config.glossary !== void 0 ? { glossary: config.glossary } : {},
|
|
3665
|
+
...config.tone !== void 0 ? { tone: config.tone } : {},
|
|
3666
|
+
...adapter.comparePlaceholders !== void 0 ? { comparePlaceholders: adapter.comparePlaceholders } : {}
|
|
3667
|
+
};
|
|
3668
|
+
}
|
|
3669
|
+
async function retranslateEntry(input, deps = {}) {
|
|
3670
|
+
const config = input.config;
|
|
3671
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3672
|
+
const fs = deps.fs ?? defaultFs;
|
|
3673
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3674
|
+
const [locale] = selectLocales(config, [input.locale]);
|
|
3675
|
+
if (locale === void 0) {
|
|
3676
|
+
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
3677
|
+
}
|
|
3678
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
3679
|
+
const sourceEntry = source.resource.entries.get(input.key);
|
|
3680
|
+
if (sourceEntry === void 0) {
|
|
3681
|
+
throw new SdkError(
|
|
3682
|
+
"UNKNOWN_KEY",
|
|
3683
|
+
`The key "${input.key}" was not found in the source resource.`
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
const provider = selectProvider(config.provider, deps.createProvider);
|
|
3687
|
+
return withLocaleWriteLock(cwd, locale, fs, async () => {
|
|
3688
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
3689
|
+
const result = await provider.translateBatch(
|
|
3690
|
+
buildSingleEntryRequest(config, locale, sourceEntry, adapter)
|
|
3691
|
+
);
|
|
3692
|
+
const value = result.values.get(input.key);
|
|
3693
|
+
if (value === void 0) {
|
|
3694
|
+
throw new ProviderError(
|
|
3695
|
+
"INVALID_RESPONSE",
|
|
3696
|
+
`The provider returned no translated value for key "${input.key}".`
|
|
3697
|
+
);
|
|
3698
|
+
}
|
|
3699
|
+
const gate = gateCandidateValue(sourceEntry, value, adapter);
|
|
3700
|
+
if (!gate.accepted) {
|
|
3701
|
+
return { accepted: false, reason: gate.reason, value };
|
|
3702
|
+
}
|
|
3703
|
+
const merged = new Map(target.entries);
|
|
3704
|
+
merged.set(input.key, { ...sourceEntry, value, namespace: target.namespace });
|
|
3705
|
+
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
3706
|
+
await adapter.write(
|
|
3707
|
+
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
3708
|
+
path
|
|
3709
|
+
);
|
|
3710
|
+
await updateLockFileLocale(cwd, fs, locale, {
|
|
3711
|
+
mode: "merge",
|
|
3712
|
+
entries: { [input.key]: contentHash(sourceEntry) }
|
|
3713
|
+
});
|
|
3714
|
+
const reviewReasons = result.reviewFlags?.get(input.key)?.reasons ?? [];
|
|
3715
|
+
return { accepted: true, value, reviewReasons };
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3718
|
+
var RUN_STATUS_DIR_NAME = ".verbatra-local";
|
|
3719
|
+
var RUN_STATUS_FILE_NAME = "run-status.json";
|
|
3720
|
+
var CURRENT_VERSION2 = 1;
|
|
3721
|
+
var MAX_RUN_STATUS_FILE_BYTES = 16 * 1024 * 1024;
|
|
3722
|
+
var reviewReasonCodeSchema = z.enum([
|
|
3723
|
+
"LENGTH_RATIO_OUTLIER",
|
|
3724
|
+
"EQUALS_SOURCE",
|
|
3725
|
+
"GLOSSARY_TERM_MISSED",
|
|
3726
|
+
"INTEGRITY_REORDERED",
|
|
3727
|
+
"PROVIDER_DEGRADED"
|
|
3728
|
+
]);
|
|
3729
|
+
var needsReviewEntrySchema = z.object({
|
|
3730
|
+
key: z.string(),
|
|
3731
|
+
reasons: z.array(reviewReasonCodeSchema)
|
|
3732
|
+
});
|
|
3733
|
+
var usageSummarySchema = z.object({
|
|
3734
|
+
inputTokens: z.number().int().nonnegative(),
|
|
3735
|
+
outputTokens: z.number().int().nonnegative()
|
|
3736
|
+
});
|
|
3737
|
+
var runBudgetSchema = z.object({
|
|
3738
|
+
maxTokens: z.number().int().nonnegative(),
|
|
3739
|
+
behavior: z.enum(["warn", "stop"]),
|
|
3740
|
+
supported: z.boolean(),
|
|
3741
|
+
tokensUsed: z.number().int().nonnegative(),
|
|
3742
|
+
exceeded: z.boolean()
|
|
3743
|
+
});
|
|
3744
|
+
var runStatusLocaleSchema = z.object({
|
|
3745
|
+
locale: z.string(),
|
|
3746
|
+
status: z.enum(["succeeded", "failed"]),
|
|
3747
|
+
needsReview: z.array(needsReviewEntrySchema),
|
|
3748
|
+
usage: usageSummarySchema.optional()
|
|
3749
|
+
});
|
|
3750
|
+
var runStatusFileSchema = z.object({
|
|
3751
|
+
version: z.number().int().positive(),
|
|
3752
|
+
generatedAt: z.string(),
|
|
3753
|
+
usage: usageSummarySchema.optional(),
|
|
3754
|
+
budget: runBudgetSchema.optional(),
|
|
3755
|
+
locales: z.array(runStatusLocaleSchema)
|
|
3756
|
+
});
|
|
3757
|
+
function runStatusFilePath(cwd) {
|
|
3758
|
+
return resolve(cwd, RUN_STATUS_DIR_NAME, RUN_STATUS_FILE_NAME);
|
|
3759
|
+
}
|
|
3760
|
+
function toRunStatusLocale(locale) {
|
|
3761
|
+
return {
|
|
3762
|
+
locale: locale.locale,
|
|
3763
|
+
status: locale.status,
|
|
3764
|
+
needsReview: locale.needsReview,
|
|
3765
|
+
...locale.usage !== void 0 ? { usage: locale.usage } : {}
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
function buildRunStatusFile(summary, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3769
|
+
return {
|
|
3770
|
+
version: CURRENT_VERSION2,
|
|
3771
|
+
generatedAt,
|
|
3772
|
+
...summary.usage !== void 0 ? { usage: summary.usage } : {},
|
|
3773
|
+
...summary.budget !== void 0 ? { budget: summary.budget } : {},
|
|
3774
|
+
locales: summary.locales.map(toRunStatusLocale)
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
function fromParsed(data) {
|
|
3778
|
+
return {
|
|
3779
|
+
version: data.version,
|
|
3780
|
+
generatedAt: data.generatedAt,
|
|
3781
|
+
...data.usage !== void 0 ? { usage: data.usage } : {},
|
|
3782
|
+
...data.budget !== void 0 ? { budget: data.budget } : {},
|
|
3783
|
+
locales: data.locales.map((locale) => ({
|
|
3784
|
+
locale: locale.locale,
|
|
3785
|
+
status: locale.status,
|
|
3786
|
+
needsReview: locale.needsReview,
|
|
3787
|
+
...locale.usage !== void 0 ? { usage: locale.usage } : {}
|
|
3788
|
+
}))
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
async function readRunStatusFile(path, fs) {
|
|
3792
|
+
const read = await fs.readFileBounded(path, MAX_RUN_STATUS_FILE_BYTES);
|
|
3793
|
+
if (read.kind !== "ok") {
|
|
3794
|
+
return void 0;
|
|
3795
|
+
}
|
|
3796
|
+
let parsed;
|
|
3797
|
+
try {
|
|
3798
|
+
parsed = JSON.parse(read.content);
|
|
3799
|
+
} catch {
|
|
3800
|
+
return void 0;
|
|
3801
|
+
}
|
|
3802
|
+
const result = runStatusFileSchema.safeParse(parsed);
|
|
3803
|
+
if (!result.success || result.data.version !== CURRENT_VERSION2) {
|
|
3804
|
+
return void 0;
|
|
3805
|
+
}
|
|
3806
|
+
return fromParsed(result.data);
|
|
3807
|
+
}
|
|
3808
|
+
async function writeRunStatusFile(path, data, fs) {
|
|
3809
|
+
await mkdir(dirname(path), { recursive: true });
|
|
3810
|
+
await fs.writeFile(path, `${JSON.stringify(data, null, 2)}
|
|
3811
|
+
`);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
// src/flow/run-status.ts
|
|
3815
|
+
async function runStatus(input = {}, deps = {}) {
|
|
3816
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3817
|
+
const fs = deps.fs ?? defaultFs;
|
|
3818
|
+
const file = await readRunStatusFile(runStatusFilePath(cwd), fs);
|
|
3819
|
+
if (file === void 0) {
|
|
3820
|
+
return { available: false };
|
|
3821
|
+
}
|
|
3822
|
+
return { available: true, ...file };
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3257
3825
|
// src/flow/budget.ts
|
|
3258
3826
|
function createBudgetTracker(maxTokens, behavior) {
|
|
3259
3827
|
return { maxTokens, behavior, tokensUsed: 0, usageSeen: false, exceeded: false, stopped: false };
|
|
@@ -3536,7 +4104,6 @@ function syntheticEntry(item) {
|
|
|
3536
4104
|
...item.sourceEntry,
|
|
3537
4105
|
key: item.targetKey,
|
|
3538
4106
|
isPlural: true,
|
|
3539
|
-
// The CLDR category travels as data context (the meaning field), never the instruction channel.
|
|
3540
4107
|
meaning: `CLDR plural category "${item.category}"`
|
|
3541
4108
|
};
|
|
3542
4109
|
}
|
|
@@ -3613,17 +4180,17 @@ async function runGenerationSubBatch(context, batch, accepted, withheld, provide
|
|
|
3613
4180
|
return { notices: [subBatchFailedNotice(batch.length, error)], usage: void 0 };
|
|
3614
4181
|
}
|
|
3615
4182
|
for (const item of batch) {
|
|
3616
|
-
foldGenerationItem(item, result, accepted, withheld, providerFailures);
|
|
4183
|
+
foldGenerationItem(item, result, context.adapter, accepted, withheld, providerFailures);
|
|
3617
4184
|
}
|
|
3618
4185
|
return { notices: readNotices(result), usage: result.usage };
|
|
3619
4186
|
}
|
|
3620
|
-
function foldGenerationItem(item, result, accepted, withheld, providerFailures) {
|
|
4187
|
+
function foldGenerationItem(item, result, adapter, accepted, withheld, providerFailures) {
|
|
3621
4188
|
const value = result.values.get(item.targetKey);
|
|
3622
4189
|
if (value === void 0) {
|
|
3623
4190
|
providerFailures.push(item.targetKey);
|
|
3624
4191
|
return;
|
|
3625
4192
|
}
|
|
3626
|
-
if (
|
|
4193
|
+
if (gateCandidateValue(item.sourceEntry, value, adapter).accepted) {
|
|
3627
4194
|
accepted.push({
|
|
3628
4195
|
targetKey: item.targetKey,
|
|
3629
4196
|
entry: { ...syntheticEntry(item), value },
|
|
@@ -3635,16 +4202,6 @@ function foldGenerationItem(item, result, accepted, withheld, providerFailures)
|
|
|
3635
4202
|
}
|
|
3636
4203
|
|
|
3637
4204
|
// src/flow/locale-run.ts
|
|
3638
|
-
function emptyResource(locale, format) {
|
|
3639
|
-
return { locale, namespace: "", format, entries: /* @__PURE__ */ new Map() };
|
|
3640
|
-
}
|
|
3641
|
-
async function readTarget2(params) {
|
|
3642
|
-
const path = localeFilePath(params.cwd, params.filesPattern, params.targetLocale);
|
|
3643
|
-
if (!await params.fs.fileExists(path)) {
|
|
3644
|
-
return emptyResource(params.targetLocale, params.format);
|
|
3645
|
-
}
|
|
3646
|
-
return (await params.adapter.read(path, params.targetLocale)).resource;
|
|
3647
|
-
}
|
|
3648
4205
|
function buildRequest3(params, entries) {
|
|
3649
4206
|
return {
|
|
3650
4207
|
sourceLocale: params.sourceLocale,
|
|
@@ -3657,7 +4214,14 @@ function buildRequest3(params, entries) {
|
|
|
3657
4214
|
};
|
|
3658
4215
|
}
|
|
3659
4216
|
async function runLocale(params) {
|
|
3660
|
-
const target = await
|
|
4217
|
+
const target = await readTargetResource({
|
|
4218
|
+
cwd: params.cwd,
|
|
4219
|
+
filesPattern: params.filesPattern,
|
|
4220
|
+
format: params.format,
|
|
4221
|
+
locale: params.targetLocale,
|
|
4222
|
+
adapter: params.adapter,
|
|
4223
|
+
fs: params.fs
|
|
4224
|
+
});
|
|
3661
4225
|
const diff2 = diffResources(params.source, target, { baseline: params.baseline });
|
|
3662
4226
|
const orphaned = params.generatePlurals ? diff2.orphaned.filter((key) => !isGeneratedPluralKey(key, sourcePluralBaseKeys(params.source))) : diff2.orphaned;
|
|
3663
4227
|
const pruned = params.prune ? orphaned : [];
|
|
@@ -3711,8 +4275,11 @@ async function runLocale(params) {
|
|
|
3711
4275
|
for (const key of pruned) {
|
|
3712
4276
|
merged.delete(key);
|
|
3713
4277
|
}
|
|
3714
|
-
for (const
|
|
3715
|
-
|
|
4278
|
+
for (const key of params.source.entries.keys()) {
|
|
4279
|
+
const hit = accepted.get(key);
|
|
4280
|
+
if (hit !== void 0) {
|
|
4281
|
+
merged.set(key, { ...hit.source, value: hit.value, namespace: target.namespace });
|
|
4282
|
+
}
|
|
3716
4283
|
}
|
|
3717
4284
|
const generation = await runGeneration(params, provider);
|
|
3718
4285
|
for (const form of generation.accepted) {
|
|
@@ -3741,7 +4308,6 @@ async function runLocale(params) {
|
|
|
3741
4308
|
...invalidIcuSource,
|
|
3742
4309
|
...generation.withheld,
|
|
3743
4310
|
...generation.providerFailures,
|
|
3744
|
-
// Source-present keys withheld by the budget guardrail must keep their prior lock hash too.
|
|
3745
4311
|
...budgetWithheld
|
|
3746
4312
|
]);
|
|
3747
4313
|
const localeUsage = combineUsage(translation.usage, generation.usage);
|
|
@@ -3753,9 +4319,7 @@ async function runLocale(params) {
|
|
|
3753
4319
|
invalidIcuSource,
|
|
3754
4320
|
translated: [...accepted.keys()],
|
|
3755
4321
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
3756
|
-
// Withheld generated forms surface alongside withheld translations: both failed integrity.
|
|
3757
4322
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
3758
|
-
// A generation sub-batch whose provider call itself threw is a provider failure, never integrity.
|
|
3759
4323
|
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
3760
4324
|
budgetWithheld: [...budgetWithheld, ...generation.budgetWithheld].sort(),
|
|
3761
4325
|
pruned,
|
|
@@ -3873,7 +4437,7 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
3873
4437
|
return { notices: [subBatchFailedNotice(batch.length, error)], usage: void 0 };
|
|
3874
4438
|
}
|
|
3875
4439
|
for (const entry of batch) {
|
|
3876
|
-
foldEntryResult(entry, result, accepted, integrityMismatches, providerFailures);
|
|
4440
|
+
foldEntryResult(entry, result, params.adapter, accepted, integrityMismatches, providerFailures);
|
|
3877
4441
|
}
|
|
3878
4442
|
if (result.reviewFlags !== void 0) {
|
|
3879
4443
|
for (const [key, flag] of result.reviewFlags) {
|
|
@@ -3882,13 +4446,13 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
3882
4446
|
}
|
|
3883
4447
|
return { notices: readNotices(result), usage: result.usage };
|
|
3884
4448
|
}
|
|
3885
|
-
function foldEntryResult(entry, result, accepted, integrityMismatches, providerFailures) {
|
|
4449
|
+
function foldEntryResult(entry, result, adapter, accepted, integrityMismatches, providerFailures) {
|
|
3886
4450
|
const value = result.values.get(entry.key);
|
|
3887
4451
|
if (value === void 0) {
|
|
3888
4452
|
providerFailures.push(entry.key);
|
|
3889
4453
|
return;
|
|
3890
4454
|
}
|
|
3891
|
-
if (
|
|
4455
|
+
if (gateCandidateValue(entry, value, adapter).accepted) {
|
|
3892
4456
|
accepted.set(entry.key, { value, source: entry });
|
|
3893
4457
|
} else {
|
|
3894
4458
|
integrityMismatches.push(entry.key);
|
|
@@ -3930,6 +4494,79 @@ function carryGeneratedLock(lockEntries, baseline, key, sourceBaseKeys) {
|
|
|
3930
4494
|
}
|
|
3931
4495
|
|
|
3932
4496
|
// src/flow/translate-project.ts
|
|
4497
|
+
async function recordRunStatus(cwd, dryRun, summary, fs) {
|
|
4498
|
+
if (dryRun) {
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
try {
|
|
4502
|
+
await writeRunStatusFile(runStatusFilePath(cwd), buildRunStatusFile(summary), fs);
|
|
4503
|
+
} catch {
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
4507
|
+
return {
|
|
4508
|
+
source: context.source.resource,
|
|
4509
|
+
sourceInvalidIcuKeys: context.source.invalidIcuKeys,
|
|
4510
|
+
baseline,
|
|
4511
|
+
adapter: context.adapter,
|
|
4512
|
+
provider: context.provider,
|
|
4513
|
+
cwd: context.cwd,
|
|
4514
|
+
filesPattern: context.config.files.pattern,
|
|
4515
|
+
sourceLocale: context.config.sourceLocale,
|
|
4516
|
+
targetLocale,
|
|
4517
|
+
format: context.config.format,
|
|
4518
|
+
glossary: context.config.glossary,
|
|
4519
|
+
tone: context.config.tone,
|
|
4520
|
+
prune: context.prune,
|
|
4521
|
+
generatePlurals: context.generatePlurals,
|
|
4522
|
+
maxBatchSize: context.maxBatchSize,
|
|
4523
|
+
fs: context.fs,
|
|
4524
|
+
budget: context.budget
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4527
|
+
async function runDryLocale(context, targetLocale, lock) {
|
|
4528
|
+
const params = buildLocaleRunParams(context, targetLocale, baselineFor(lock, targetLocale));
|
|
4529
|
+
return (await runLocale(params)).summary;
|
|
4530
|
+
}
|
|
4531
|
+
async function runLiveLocale(context, targetLocale) {
|
|
4532
|
+
return withLocaleWriteLock(context.cwd, targetLocale, context.fs, async () => {
|
|
4533
|
+
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4534
|
+
const params = buildLocaleRunParams(context, targetLocale, baselineFor(lock, targetLocale));
|
|
4535
|
+
const result = await runLocale(params);
|
|
4536
|
+
await updateLockFileLocale(context.cwd, context.fs, targetLocale, {
|
|
4537
|
+
mode: "replace",
|
|
4538
|
+
entries: result.lockEntries
|
|
4539
|
+
});
|
|
4540
|
+
return result.summary;
|
|
4541
|
+
});
|
|
4542
|
+
}
|
|
4543
|
+
async function runOneLocale(targetLocale, run) {
|
|
4544
|
+
try {
|
|
4545
|
+
return await run();
|
|
4546
|
+
} catch (error) {
|
|
4547
|
+
if (error instanceof SdkError && error.code === "LOCK_FILE_INVALID") {
|
|
4548
|
+
throw error;
|
|
4549
|
+
}
|
|
4550
|
+
return failureSummary(targetLocale, error);
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
async function runAllLocalesDry(context, targetLocales) {
|
|
4554
|
+
const lock = await readLockFile(lockFilePath(context.cwd), context.fs);
|
|
4555
|
+
const summaries = [];
|
|
4556
|
+
for (const targetLocale of targetLocales) {
|
|
4557
|
+
summaries.push(
|
|
4558
|
+
await runOneLocale(targetLocale, () => runDryLocale(context, targetLocale, lock))
|
|
4559
|
+
);
|
|
4560
|
+
}
|
|
4561
|
+
return summaries;
|
|
4562
|
+
}
|
|
4563
|
+
async function runAllLocalesLive(context, targetLocales) {
|
|
4564
|
+
const summaries = [];
|
|
4565
|
+
for (const targetLocale of targetLocales) {
|
|
4566
|
+
summaries.push(await runOneLocale(targetLocale, () => runLiveLocale(context, targetLocale)));
|
|
4567
|
+
}
|
|
4568
|
+
return summaries;
|
|
4569
|
+
}
|
|
3933
4570
|
async function translate2(input, deps = {}) {
|
|
3934
4571
|
const config = input.config;
|
|
3935
4572
|
const cwd = input.cwd ?? process.cwd();
|
|
@@ -3945,47 +4582,26 @@ async function translate2(input, deps = {}) {
|
|
|
3945
4582
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3946
4583
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
3947
4584
|
const source = await readSource(config, cwd, fs, adapter);
|
|
3948
|
-
const
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
sourceLocale: config.sourceLocale,
|
|
3962
|
-
targetLocale,
|
|
3963
|
-
format: config.format,
|
|
3964
|
-
glossary: config.glossary,
|
|
3965
|
-
tone: config.tone,
|
|
3966
|
-
prune,
|
|
3967
|
-
generatePlurals,
|
|
3968
|
-
maxBatchSize,
|
|
3969
|
-
fs,
|
|
3970
|
-
budget
|
|
3971
|
-
};
|
|
3972
|
-
const { summary, lockEntries } = await runLocale(params);
|
|
3973
|
-
if (!dryRun) {
|
|
3974
|
-
lock = updateLockLocale(lock, targetLocale, lockEntries);
|
|
3975
|
-
await writeLockFile(lockPath, lock, fs);
|
|
3976
|
-
}
|
|
3977
|
-
summaries.push(summary);
|
|
3978
|
-
} catch (error) {
|
|
3979
|
-
summaries.push(failureSummary(targetLocale, error));
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
4585
|
+
const context = {
|
|
4586
|
+
source,
|
|
4587
|
+
adapter,
|
|
4588
|
+
provider,
|
|
4589
|
+
cwd,
|
|
4590
|
+
config,
|
|
4591
|
+
prune,
|
|
4592
|
+
generatePlurals,
|
|
4593
|
+
maxBatchSize,
|
|
4594
|
+
fs,
|
|
4595
|
+
budget
|
|
4596
|
+
};
|
|
4597
|
+
const summaries = dryRun ? await runAllLocalesDry(context, config.targetLocales) : await runAllLocalesLive(context, config.targetLocales);
|
|
3982
4598
|
const { succeeded, failed } = partition(summaries);
|
|
3983
4599
|
const usage = summaries.reduce(
|
|
3984
|
-
(total,
|
|
4600
|
+
(total, summary2) => combineUsage(total, summary2.usage),
|
|
3985
4601
|
void 0
|
|
3986
4602
|
);
|
|
3987
4603
|
const budgetSummary = toBudgetSummary(budget);
|
|
3988
|
-
|
|
4604
|
+
const summary = {
|
|
3989
4605
|
dryRun,
|
|
3990
4606
|
locales: summaries,
|
|
3991
4607
|
succeeded,
|
|
@@ -3993,6 +4609,8 @@ async function translate2(input, deps = {}) {
|
|
|
3993
4609
|
...usage !== void 0 ? { usage } : {},
|
|
3994
4610
|
...budgetSummary !== void 0 ? { budget: budgetSummary } : {}
|
|
3995
4611
|
};
|
|
4612
|
+
await recordRunStatus(cwd, dryRun, summary, fs);
|
|
4613
|
+
return summary;
|
|
3996
4614
|
}
|
|
3997
4615
|
var ExchangeError = class extends Error {
|
|
3998
4616
|
code;
|
|
@@ -4278,8 +4896,6 @@ var rowSchema = z.object({
|
|
|
4278
4896
|
sourceHash: z.string(),
|
|
4279
4897
|
translation: z.string(),
|
|
4280
4898
|
context: z.string(),
|
|
4281
|
-
// A legacy workbook exported before these columns existed has no such cell; cellString then yields
|
|
4282
|
-
// "", and .catch maps it to "ok" / "" rather than rejecting the row.
|
|
4283
4899
|
reviewStatus: z.enum(["ok", "review"]).catch("ok"),
|
|
4284
4900
|
reviewReasons: z.string().catch("")
|
|
4285
4901
|
});
|
|
@@ -4315,10 +4931,7 @@ function parseRow(sheet, row) {
|
|
|
4315
4931
|
status: cellString(row.getCell(COLUMN.status)),
|
|
4316
4932
|
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
4317
4933
|
translation: cellString(row.getCell(COLUMN.translation)),
|
|
4318
|
-
// A workbook built before the Context column existed has no cell here; getCell auto-vivifies an
|
|
4319
|
-
// empty one, so cellString yields "" and the row still validates, keeping import backward-compatible.
|
|
4320
4934
|
context: cellString(row.getCell(COLUMN.context)),
|
|
4321
|
-
// Same backward-compatible pattern for a workbook built before the Review columns existed.
|
|
4322
4935
|
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
4323
4936
|
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
4324
4937
|
};
|
|
@@ -4387,13 +5000,6 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
4387
5000
|
|
|
4388
5001
|
// src/flow/workbook/export-workbook.ts
|
|
4389
5002
|
var DEFAULT_WORKBOOK_PATH = "verbatra-translations.xlsx";
|
|
4390
|
-
async function readTarget3(cwd, config, adapter, fs, locale) {
|
|
4391
|
-
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
4392
|
-
if (!await fs.fileExists(path)) {
|
|
4393
|
-
return { locale, namespace: "", format: config.format, entries: /* @__PURE__ */ new Map() };
|
|
4394
|
-
}
|
|
4395
|
-
return (await adapter.read(path, locale)).resource;
|
|
4396
|
-
}
|
|
4397
5003
|
function reasonLabel(reason) {
|
|
4398
5004
|
return reason.toLowerCase().replace(/_/g, "-");
|
|
4399
5005
|
}
|
|
@@ -4467,7 +5073,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
4467
5073
|
const locales = selectLocales(config, input.locales);
|
|
4468
5074
|
const sheets = await Promise.all(
|
|
4469
5075
|
locales.map(async (locale) => {
|
|
4470
|
-
const target = await
|
|
5076
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
4471
5077
|
const rows = buildRows(
|
|
4472
5078
|
source.resource,
|
|
4473
5079
|
target,
|
|
@@ -4505,14 +5111,8 @@ function judge(row, sourceEntry, adapter) {
|
|
|
4505
5111
|
if (contentHash(sourceEntry) !== row.sourceHash) {
|
|
4506
5112
|
return "drift";
|
|
4507
5113
|
}
|
|
4508
|
-
const
|
|
4509
|
-
|
|
4510
|
-
return "placeholder";
|
|
4511
|
-
}
|
|
4512
|
-
if (!adapter.validateMessage(row.translation)) {
|
|
4513
|
-
return "icu";
|
|
4514
|
-
}
|
|
4515
|
-
return void 0;
|
|
5114
|
+
const gate = gateCandidateValue(sourceEntry, row.translation, adapter);
|
|
5115
|
+
return gate.accepted ? void 0 : gate.reason;
|
|
4516
5116
|
}
|
|
4517
5117
|
function trackBlankDrift(row, params, buckets) {
|
|
4518
5118
|
const sourceEntry = params.source.entries.get(row.key);
|
|
@@ -4569,19 +5169,13 @@ function importLocale(params) {
|
|
|
4569
5169
|
translated: [...buckets.accepted.keys()].sort(),
|
|
4570
5170
|
unchanged: diff2.unchanged,
|
|
4571
5171
|
orphaned: diff2.orphaned,
|
|
4572
|
-
// Import never prunes: orphans are reported but never removed here (pruning is a translate-flow concern).
|
|
4573
5172
|
pruned: [],
|
|
4574
5173
|
invalidIcuSource,
|
|
4575
5174
|
integrityMismatches: [...buckets.mismatches].sort(),
|
|
4576
|
-
// A workbook import never calls a provider, so a provider-call failure cannot occur here.
|
|
4577
5175
|
providerFailures: [],
|
|
4578
|
-
// A workbook import never calls a provider, so the budget guardrail never withholds anything here.
|
|
4579
5176
|
budgetWithheld: [],
|
|
4580
|
-
// Plural generation is a translate-flow concern; the manual workbook import never generates forms.
|
|
4581
5177
|
generated: [],
|
|
4582
5178
|
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : [],
|
|
4583
|
-
// A workbook import never calls a provider and never recomputes review flags on its own path; the
|
|
4584
|
-
// Excel workbook's Review status/reasons columns are informational only (see export-workbook.ts).
|
|
4585
5179
|
needsReview: []
|
|
4586
5180
|
};
|
|
4587
5181
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
@@ -4602,13 +5196,6 @@ async function readWorkbookBytes(path, fs) {
|
|
|
4602
5196
|
}
|
|
4603
5197
|
return read.bytes;
|
|
4604
5198
|
}
|
|
4605
|
-
async function readTarget4(cwd, config, adapter, fs, locale) {
|
|
4606
|
-
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
4607
|
-
if (!await fs.fileExists(path)) {
|
|
4608
|
-
return { locale, namespace: "", format: config.format, entries: /* @__PURE__ */ new Map() };
|
|
4609
|
-
}
|
|
4610
|
-
return (await adapter.read(path, locale)).resource;
|
|
4611
|
-
}
|
|
4612
5199
|
function mergeAccepted(target, accepted) {
|
|
4613
5200
|
const merged = new Map(target.entries);
|
|
4614
5201
|
for (const [key, { value, source }] of accepted) {
|
|
@@ -4639,7 +5226,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
4639
5226
|
`The workbook has a sheet for locale "${sheet.locale}", which is not a configured target locale.`
|
|
4640
5227
|
);
|
|
4641
5228
|
}
|
|
4642
|
-
const target = await
|
|
5229
|
+
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
4643
5230
|
const baseline = baselineFor(lock, sheet.locale);
|
|
4644
5231
|
const { summary, accepted } = importLocale({
|
|
4645
5232
|
sheet,
|
|
@@ -4682,8 +5269,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
4682
5269
|
} catch (error) {
|
|
4683
5270
|
throw new SdkError("SOURCE_INVALID", error.message);
|
|
4684
5271
|
}
|
|
4685
|
-
const
|
|
4686
|
-
let lock = await readLockFile(lockPath, fs);
|
|
5272
|
+
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
4687
5273
|
const ctx = {
|
|
4688
5274
|
config,
|
|
4689
5275
|
cwd,
|
|
@@ -4696,10 +5282,18 @@ async function importWorkbook(input, deps = {}) {
|
|
|
4696
5282
|
const summaries = [];
|
|
4697
5283
|
for (const sheet of data.sheets) {
|
|
4698
5284
|
try {
|
|
4699
|
-
|
|
4700
|
-
if (
|
|
4701
|
-
|
|
4702
|
-
|
|
5285
|
+
let summary;
|
|
5286
|
+
if (dryRun) {
|
|
5287
|
+
summary = (await runSheet(ctx, sheet, lock)).summary;
|
|
5288
|
+
} else {
|
|
5289
|
+
summary = await withLocaleWriteLock(cwd, sheet.locale, fs, async () => {
|
|
5290
|
+
const result = await runSheet(ctx, sheet, lock);
|
|
5291
|
+
await updateLockFileLocale(cwd, fs, sheet.locale, {
|
|
5292
|
+
mode: "replace",
|
|
5293
|
+
entries: result.lockEntries
|
|
5294
|
+
});
|
|
5295
|
+
return result.summary;
|
|
5296
|
+
});
|
|
4703
5297
|
}
|
|
4704
5298
|
summaries.push(summary);
|
|
4705
5299
|
} catch (error) {
|
|
@@ -4832,6 +5426,6 @@ async function watch(input, deps = {}) {
|
|
|
4832
5426
|
return { stop };
|
|
4833
5427
|
}
|
|
4834
5428
|
|
|
4835
|
-
export { DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, loadConfigWithMeta, loadLockFile, lockState, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
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 };
|
|
4836
5430
|
//# sourceMappingURL=index.js.map
|
|
4837
5431
|
//# sourceMappingURL=index.js.map
|