@verbatra/sdk 0.3.0 → 0.4.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 +28 -2
- package/dist/index.cjs +714 -238
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +206 -146
- package/dist/index.d.ts +206 -146
- package/dist/index.js +712 -240
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -5,12 +5,15 @@ import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import Anthropic from '@anthropic-ai/sdk';
|
|
7
7
|
import * as deepl from 'deepl-node';
|
|
8
|
+
import { createRequire } from 'module';
|
|
8
9
|
import log from 'loglevel';
|
|
9
10
|
import { GoogleGenAI } from '@google/genai';
|
|
10
11
|
import OpenAI from 'openai';
|
|
11
12
|
import { randomUUID } from 'crypto';
|
|
12
13
|
import { access, writeFile, rename, rm, open } from 'fs/promises';
|
|
13
|
-
import { parse, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
14
|
+
import { parse as parse$1, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
15
|
+
import { XMLSerializer, DOMParser } from '@xmldom/xmldom';
|
|
16
|
+
import { stringify, parse } from 'yaml';
|
|
14
17
|
import ExcelJS from 'exceljs';
|
|
15
18
|
import JSZip from 'jszip';
|
|
16
19
|
import { watch as watch$1 } from 'chokidar';
|
|
@@ -100,7 +103,10 @@ var SUPPORTED_FORMATS = [
|
|
|
100
103
|
"i18next-json",
|
|
101
104
|
"vue-i18n-json",
|
|
102
105
|
"next-intl-json",
|
|
103
|
-
"ngx-translate-json"
|
|
106
|
+
"ngx-translate-json",
|
|
107
|
+
"xliff",
|
|
108
|
+
"yaml",
|
|
109
|
+
"arb"
|
|
104
110
|
];
|
|
105
111
|
var supportedFormatSchema = z.enum(SUPPORTED_FORMATS);
|
|
106
112
|
var translationEntrySchema = z.object({
|
|
@@ -155,6 +161,26 @@ var LOCALE_TOKEN = "{locale}";
|
|
|
155
161
|
function localeFilePath(cwd, pattern, locale) {
|
|
156
162
|
return resolve(cwd, pattern.replaceAll(LOCALE_TOKEN, locale));
|
|
157
163
|
}
|
|
164
|
+
var REDACTED = "[REDACTED]";
|
|
165
|
+
var KEY_PATTERNS = [
|
|
166
|
+
// The `\b` anchors `sk-` to a word start so hyphenated words like "risk-" or "task-" pass through.
|
|
167
|
+
/\bsk-[A-Za-z0-9_-]{8,}/g,
|
|
168
|
+
/AIza[0-9A-Za-z_-]{35}/g,
|
|
169
|
+
/[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
|
|
170
|
+
];
|
|
171
|
+
function escapeForRegExp(value) {
|
|
172
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
173
|
+
}
|
|
174
|
+
function redact(text, secret = process.env.ANTHROPIC_API_KEY) {
|
|
175
|
+
let out = text;
|
|
176
|
+
for (const pattern of KEY_PATTERNS) {
|
|
177
|
+
out = out.replace(pattern, REDACTED);
|
|
178
|
+
}
|
|
179
|
+
if (secret !== void 0 && secret.length > 0) {
|
|
180
|
+
out = out.replace(new RegExp(escapeForRegExp(secret), "g"), REDACTED);
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
158
184
|
var ProviderError = class extends Error {
|
|
159
185
|
/** The stable {@link ProviderErrorCode} for this failure; branch on this, not the message. */
|
|
160
186
|
code;
|
|
@@ -163,7 +189,7 @@ var ProviderError = class extends Error {
|
|
|
163
189
|
* @param message - A fixed, safe message; callers must never pass key, SDK, or request-derived text.
|
|
164
190
|
*/
|
|
165
191
|
constructor(code, message) {
|
|
166
|
-
super(message);
|
|
192
|
+
super(redact(message, ""));
|
|
167
193
|
this.name = "ProviderError";
|
|
168
194
|
this.code = code;
|
|
169
195
|
}
|
|
@@ -290,6 +316,12 @@ function assertNotTruncated(truncated) {
|
|
|
290
316
|
throw new ProviderError("OUTPUT_TRUNCATED", OUTPUT_TRUNCATED_MESSAGE);
|
|
291
317
|
}
|
|
292
318
|
}
|
|
319
|
+
var PROVIDER_ENV = {
|
|
320
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
321
|
+
openai: "OPENAI_API_KEY",
|
|
322
|
+
gemini: "GEMINI_API_KEY",
|
|
323
|
+
deepl: "DEEPL_API_KEY"
|
|
324
|
+
};
|
|
293
325
|
function readRequiredEnv(name) {
|
|
294
326
|
const value = process.env[name];
|
|
295
327
|
if (value === void 0 || value.length === 0) {
|
|
@@ -298,16 +330,16 @@ function readRequiredEnv(name) {
|
|
|
298
330
|
return value;
|
|
299
331
|
}
|
|
300
332
|
function requireAnthropicKey() {
|
|
301
|
-
return readRequiredEnv(
|
|
333
|
+
return readRequiredEnv(PROVIDER_ENV.anthropic);
|
|
302
334
|
}
|
|
303
335
|
function requireOpenAiKey() {
|
|
304
|
-
return readRequiredEnv(
|
|
336
|
+
return readRequiredEnv(PROVIDER_ENV.openai);
|
|
305
337
|
}
|
|
306
338
|
function requireGeminiKey() {
|
|
307
|
-
return readRequiredEnv(
|
|
339
|
+
return readRequiredEnv(PROVIDER_ENV.gemini);
|
|
308
340
|
}
|
|
309
341
|
function requireDeepLKey() {
|
|
310
|
-
return readRequiredEnv(
|
|
342
|
+
return readRequiredEnv(PROVIDER_ENV.deepl);
|
|
311
343
|
}
|
|
312
344
|
function createDefaultClient() {
|
|
313
345
|
const sdk = new Anthropic({ apiKey: requireAnthropicKey(), logLevel: "off" });
|
|
@@ -408,8 +440,22 @@ function toUsage(usage) {
|
|
|
408
440
|
var deepLConfigSchema = z.object({
|
|
409
441
|
glossaryId: z.string().min(1).optional()
|
|
410
442
|
});
|
|
443
|
+
var DEEPL_LOGGER = "deepl";
|
|
444
|
+
function resolveDeeplLoglevel(requireFn = createRequire(import.meta.url)) {
|
|
445
|
+
try {
|
|
446
|
+
const entry = requireFn.resolve("deepl-node");
|
|
447
|
+
return createRequire(entry)("loglevel");
|
|
448
|
+
} catch {
|
|
449
|
+
return void 0;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function silenceDeeplLogger(instances) {
|
|
453
|
+
for (const instance of instances) {
|
|
454
|
+
instance?.getLogger(DEEPL_LOGGER).setLevel("silent");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
411
457
|
function silenceSdkLogging() {
|
|
412
|
-
log
|
|
458
|
+
silenceDeeplLogger([log, resolveDeeplLoglevel()]);
|
|
413
459
|
}
|
|
414
460
|
function createDefaultClient2() {
|
|
415
461
|
silenceSdkLogging();
|
|
@@ -770,6 +816,11 @@ function createMechanism3(client, config) {
|
|
|
770
816
|
function callClient4(client, body) {
|
|
771
817
|
return guardProviderCall(() => client.chat.completions.create(body));
|
|
772
818
|
}
|
|
819
|
+
var SCAFFOLD_MODELS = {
|
|
820
|
+
anthropic: "claude-sonnet-4-6",
|
|
821
|
+
openai: "gpt-5.4-mini",
|
|
822
|
+
gemini: "gemini-2.5-flash"
|
|
823
|
+
};
|
|
773
824
|
var providerConfigSchema = z.discriminatedUnion("id", [
|
|
774
825
|
z.object({ id: z.literal("anthropic"), options: anthropicConfigSchema.strict() }),
|
|
775
826
|
z.object({ id: z.literal("openai"), options: openAiConfigSchema.strict() }),
|
|
@@ -800,27 +851,25 @@ var verbatraConfigSchema = z.strictObject({
|
|
|
800
851
|
glossary: z.record(z.string(), z.string()).optional(),
|
|
801
852
|
tone: z.enum(["formal", "informal", "neutral"]).optional(),
|
|
802
853
|
/**
|
|
803
|
-
* Opt-in orphan pruning, off by default
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
* is non-secret, consistent with the config's no-secret invariant.
|
|
854
|
+
* Opt-in orphan pruning, off by default. When true, keys present in a target file but absent from
|
|
855
|
+
* the source are removed from the written file and the lock. The per-run `prune` option on
|
|
856
|
+
* `translate` (the CLI `--prune` flag) overrides this.
|
|
807
857
|
*/
|
|
808
858
|
prune: z.boolean().optional(),
|
|
809
859
|
/**
|
|
810
|
-
* Opt-in plural-category generation, off by default
|
|
811
|
-
*
|
|
812
|
-
*
|
|
860
|
+
* Opt-in plural-category generation, off by default. When true, and only for an i18next-JSON project
|
|
861
|
+
* translated by an LLM provider, verbatra synthesizes the CLDR plural forms a target language
|
|
862
|
+
* requires but the source does not supply (for example Polish few/many). The per-run
|
|
813
863
|
* `generatePlurals` option on `translate` overrides this. Unsupported cases (DeepL, non-i18next, an
|
|
814
864
|
* unknown language) fall back to the per-locale plural warning.
|
|
815
865
|
*/
|
|
816
866
|
generatePlurals: z.boolean().optional(),
|
|
817
867
|
/**
|
|
818
|
-
* Optional maximum number of entries sent in a single provider request. A locale's missing
|
|
819
|
-
* entries are split into sequential sub-batches no larger than this so one oversized request
|
|
820
|
-
* the whole locale; a failed sub-batch is withheld
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
* well inside provider context windows for typical short i18n strings while keeping request counts low.
|
|
868
|
+
* Optional maximum number of entries sent in a single provider request. A locale's missing and
|
|
869
|
+
* changed entries are split into sequential sub-batches no larger than this, so one oversized request
|
|
870
|
+
* cannot sink the whole locale; a failed sub-batch is withheld while the others make progress. Must
|
|
871
|
+
* be a positive integer (zero, negative, or non-integer is rejected, never coerced). When absent,
|
|
872
|
+
* {@link DEFAULT_MAX_BATCH_SIZE} applies.
|
|
824
873
|
*/
|
|
825
874
|
maxBatchSize: z.number().int().positive().optional()
|
|
826
875
|
}).refine((config) => !config.targetLocales.includes(config.sourceLocale), {
|
|
@@ -1052,6 +1101,85 @@ async function writeLockFile(path, lock, fs) {
|
|
|
1052
1101
|
await fs.writeFile(path, `${JSON.stringify(ordered, null, 2)}
|
|
1053
1102
|
`);
|
|
1054
1103
|
}
|
|
1104
|
+
var VALID_EMPTY = { placeholders: [], isPlural: false, valid: true };
|
|
1105
|
+
var INVALID = { placeholders: [], isPlural: false, valid: false };
|
|
1106
|
+
function tokenOf(element) {
|
|
1107
|
+
switch (element.type) {
|
|
1108
|
+
case TYPE.argument:
|
|
1109
|
+
case TYPE.number:
|
|
1110
|
+
case TYPE.date:
|
|
1111
|
+
case TYPE.time:
|
|
1112
|
+
case TYPE.select:
|
|
1113
|
+
case TYPE.plural:
|
|
1114
|
+
return `{${element.value}}`;
|
|
1115
|
+
case TYPE.tag:
|
|
1116
|
+
return `<${element.value}>`;
|
|
1117
|
+
default:
|
|
1118
|
+
return void 0;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
function childMessages(element) {
|
|
1122
|
+
if (element.type === TYPE.plural || element.type === TYPE.select) {
|
|
1123
|
+
return Object.values(element.options).map((option) => option.value);
|
|
1124
|
+
}
|
|
1125
|
+
if (element.type === TYPE.tag) {
|
|
1126
|
+
return [element.children];
|
|
1127
|
+
}
|
|
1128
|
+
return [];
|
|
1129
|
+
}
|
|
1130
|
+
function collect(elements, add, state) {
|
|
1131
|
+
for (const element of elements) {
|
|
1132
|
+
const token = tokenOf(element);
|
|
1133
|
+
if (token !== void 0) {
|
|
1134
|
+
add(token);
|
|
1135
|
+
}
|
|
1136
|
+
if (element.type === TYPE.plural) {
|
|
1137
|
+
state.isPlural = true;
|
|
1138
|
+
}
|
|
1139
|
+
for (const child of childMessages(element)) {
|
|
1140
|
+
collect(child, add, state);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function analyzeIcuValue(value) {
|
|
1145
|
+
if (!value.includes("{") && !value.includes("<")) {
|
|
1146
|
+
return VALID_EMPTY;
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
const ast = parse$1(value);
|
|
1150
|
+
const placeholders = [];
|
|
1151
|
+
const state = { isPlural: false };
|
|
1152
|
+
collect(
|
|
1153
|
+
ast,
|
|
1154
|
+
(token) => {
|
|
1155
|
+
placeholders.push(token);
|
|
1156
|
+
},
|
|
1157
|
+
state
|
|
1158
|
+
);
|
|
1159
|
+
return { placeholders, isPlural: state.isPlural, valid: true };
|
|
1160
|
+
} catch {
|
|
1161
|
+
return INVALID;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function icuPlaceholders(value) {
|
|
1165
|
+
return analyzeIcuValue(value).placeholders;
|
|
1166
|
+
}
|
|
1167
|
+
function icuIsValid(value) {
|
|
1168
|
+
return analyzeIcuValue(value).valid;
|
|
1169
|
+
}
|
|
1170
|
+
function icuDeriveEntry(_key, value) {
|
|
1171
|
+
const analysis = analyzeIcuValue(value);
|
|
1172
|
+
return { placeholders: analysis.placeholders, isPlural: analysis.isPlural };
|
|
1173
|
+
}
|
|
1174
|
+
function icuInvalidKeys(entries) {
|
|
1175
|
+
const invalid = [];
|
|
1176
|
+
for (const [key, entry] of entries) {
|
|
1177
|
+
if (!icuIsValid(entry.value)) {
|
|
1178
|
+
invalid.push(key);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return invalid;
|
|
1182
|
+
}
|
|
1055
1183
|
var AdapterError = class extends Error {
|
|
1056
1184
|
code;
|
|
1057
1185
|
constructor(code, message) {
|
|
@@ -1060,6 +1188,82 @@ var AdapterError = class extends Error {
|
|
|
1060
1188
|
this.code = code;
|
|
1061
1189
|
}
|
|
1062
1190
|
};
|
|
1191
|
+
var MAX_DEPTH = 100;
|
|
1192
|
+
var MAX_INPUT_BYTES = 16 * 1024 * 1024;
|
|
1193
|
+
var jsonTreeSchema = z.lazy(
|
|
1194
|
+
() => z.union([z.string(), z.record(z.string(), jsonTreeSchema)])
|
|
1195
|
+
);
|
|
1196
|
+
var rootSchema = z.record(z.string(), jsonTreeSchema);
|
|
1197
|
+
function assertWithinDepth(value, max) {
|
|
1198
|
+
const stack = [{ node: value, depth: 1 }];
|
|
1199
|
+
while (stack.length > 0) {
|
|
1200
|
+
const top = stack.pop();
|
|
1201
|
+
if (top === void 0) {
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
const { node, depth } = top;
|
|
1205
|
+
if (typeof node !== "object" || node === null) {
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (depth > max) {
|
|
1209
|
+
throw new AdapterError("MAX_DEPTH_EXCEEDED", "The file nests objects too deeply.");
|
|
1210
|
+
}
|
|
1211
|
+
for (const child of Object.values(node)) {
|
|
1212
|
+
stack.push({ node: child, depth: depth + 1 });
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function assertJsonRecord(value) {
|
|
1217
|
+
assertWithinDepth(value, MAX_DEPTH);
|
|
1218
|
+
const result = rootSchema.safeParse(value);
|
|
1219
|
+
if (!result.success) {
|
|
1220
|
+
throw new AdapterError(
|
|
1221
|
+
"INVALID_STRUCTURE",
|
|
1222
|
+
"The file is not a valid object (expected nested objects of string values)."
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return result.data;
|
|
1226
|
+
}
|
|
1227
|
+
function parseJsonObject(content) {
|
|
1228
|
+
let parsed;
|
|
1229
|
+
try {
|
|
1230
|
+
parsed = JSON.parse(content);
|
|
1231
|
+
} catch {
|
|
1232
|
+
throw new AdapterError("INVALID_JSON", "The file is not valid JSON.");
|
|
1233
|
+
}
|
|
1234
|
+
return assertJsonRecord(parsed);
|
|
1235
|
+
}
|
|
1236
|
+
function serializeJsonTree(tree) {
|
|
1237
|
+
return `${JSON.stringify(tree, null, 2)}
|
|
1238
|
+
`;
|
|
1239
|
+
}
|
|
1240
|
+
function namespaceOf(filePath) {
|
|
1241
|
+
return basename(filePath, extname(filePath));
|
|
1242
|
+
}
|
|
1243
|
+
function rethrowStructured(error, message) {
|
|
1244
|
+
if (error instanceof AdapterError) {
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
throw new AdapterError("INVALID_STRUCTURE", message);
|
|
1248
|
+
}
|
|
1249
|
+
function computeIcu(entries, compute) {
|
|
1250
|
+
if (!compute) {
|
|
1251
|
+
return [];
|
|
1252
|
+
}
|
|
1253
|
+
try {
|
|
1254
|
+
return compute(entries);
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
rethrowStructured(error, "The file could not be analyzed for message validity.");
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function buildCanHandle(extensions, sniff) {
|
|
1260
|
+
return (filePath, sample) => {
|
|
1261
|
+
if (!extensions.includes(extname(filePath).toLowerCase())) {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
return sample === void 0 || sniff === void 0 || sniff(sample);
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1063
1267
|
var nodeOps = {
|
|
1064
1268
|
writeFile: (path, data) => writeFile(path, data, "utf8"),
|
|
1065
1269
|
rename: (from, to) => rename(from, to),
|
|
@@ -1084,8 +1288,6 @@ async function atomicWriteFile(path, data, ops = nodeOps) {
|
|
|
1084
1288
|
throw error;
|
|
1085
1289
|
}
|
|
1086
1290
|
}
|
|
1087
|
-
var MAX_DEPTH = 100;
|
|
1088
|
-
var MAX_INPUT_BYTES = 16 * 1024 * 1024;
|
|
1089
1291
|
async function readBoundedUtf82(handle, size) {
|
|
1090
1292
|
const buffer = Buffer.allocUnsafe(size);
|
|
1091
1293
|
let offset = 0;
|
|
@@ -1113,6 +1315,16 @@ async function readBounded2(filePath) {
|
|
|
1113
1315
|
await handle.close();
|
|
1114
1316
|
}
|
|
1115
1317
|
}
|
|
1318
|
+
async function readFileContent(filePath) {
|
|
1319
|
+
const outcome = await readBounded2(filePath);
|
|
1320
|
+
if (outcome.kind === "not-a-file") {
|
|
1321
|
+
throw new AdapterError("INVALID_STRUCTURE", "The path is not a regular file.");
|
|
1322
|
+
}
|
|
1323
|
+
if (outcome.kind === "too-large") {
|
|
1324
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
1325
|
+
}
|
|
1326
|
+
return outcome.content;
|
|
1327
|
+
}
|
|
1116
1328
|
var BACKSLASH = "\\";
|
|
1117
1329
|
var DOT = ".";
|
|
1118
1330
|
var ESCAPED_BACKSLASH = "\\\\";
|
|
@@ -1226,46 +1438,6 @@ function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
|
|
|
1226
1438
|
addEntries({ namespace, derive, out, claimed: /* @__PURE__ */ new Map() }, [], tree);
|
|
1227
1439
|
return out;
|
|
1228
1440
|
}
|
|
1229
|
-
var jsonTreeSchema = z.lazy(
|
|
1230
|
-
() => z.union([z.string(), z.record(z.string(), jsonTreeSchema)])
|
|
1231
|
-
);
|
|
1232
|
-
var rootSchema = z.record(z.string(), jsonTreeSchema);
|
|
1233
|
-
function assertWithinDepth(value, max) {
|
|
1234
|
-
const stack = [{ node: value, depth: 1 }];
|
|
1235
|
-
while (stack.length > 0) {
|
|
1236
|
-
const top = stack.pop();
|
|
1237
|
-
if (top === void 0) {
|
|
1238
|
-
break;
|
|
1239
|
-
}
|
|
1240
|
-
const { node, depth } = top;
|
|
1241
|
-
if (typeof node !== "object" || node === null) {
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1244
|
-
if (depth > max) {
|
|
1245
|
-
throw new AdapterError("MAX_DEPTH_EXCEEDED", "The file nests objects too deeply.");
|
|
1246
|
-
}
|
|
1247
|
-
for (const child of Object.values(node)) {
|
|
1248
|
-
stack.push({ node: child, depth: depth + 1 });
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
function parseJsonObject(content) {
|
|
1253
|
-
let parsed;
|
|
1254
|
-
try {
|
|
1255
|
-
parsed = JSON.parse(content);
|
|
1256
|
-
} catch {
|
|
1257
|
-
throw new AdapterError("INVALID_JSON", "The file is not valid JSON.");
|
|
1258
|
-
}
|
|
1259
|
-
assertWithinDepth(parsed, MAX_DEPTH);
|
|
1260
|
-
const result = rootSchema.safeParse(parsed);
|
|
1261
|
-
if (!result.success) {
|
|
1262
|
-
throw new AdapterError(
|
|
1263
|
-
"INVALID_STRUCTURE",
|
|
1264
|
-
"The file is not a valid JSON object (expected nested objects of string values)."
|
|
1265
|
-
);
|
|
1266
|
-
}
|
|
1267
|
-
return result.data;
|
|
1268
|
-
}
|
|
1269
1441
|
function emptyNode() {
|
|
1270
1442
|
return /* @__PURE__ */ Object.create(null);
|
|
1271
1443
|
}
|
|
@@ -1302,77 +1474,146 @@ function unflattenEntries(entries) {
|
|
|
1302
1474
|
}
|
|
1303
1475
|
return root;
|
|
1304
1476
|
}
|
|
1305
|
-
function
|
|
1306
|
-
return basename(filePath, extname(filePath));
|
|
1307
|
-
}
|
|
1308
|
-
function canHandle(filePath, sample) {
|
|
1309
|
-
if (extname(filePath).toLowerCase() !== ".json") {
|
|
1310
|
-
return false;
|
|
1311
|
-
}
|
|
1312
|
-
return sample === void 0 || sample.trimStart().startsWith("{");
|
|
1313
|
-
}
|
|
1314
|
-
function rethrowStructured(error, message) {
|
|
1315
|
-
if (error instanceof AdapterError) {
|
|
1316
|
-
throw error;
|
|
1317
|
-
}
|
|
1318
|
-
throw new AdapterError("INVALID_STRUCTURE", message);
|
|
1319
|
-
}
|
|
1320
|
-
function toEntries(content, namespace, deriveEntry, keyMode, validateTree) {
|
|
1477
|
+
function toEntries(content, namespace, parse2, deriveEntry, keyMode, validateTree) {
|
|
1321
1478
|
try {
|
|
1322
|
-
const tree =
|
|
1479
|
+
const tree = parse2(content);
|
|
1323
1480
|
validateTree?.(tree);
|
|
1324
1481
|
return flattenTree(tree, namespace, deriveEntry, keyMode);
|
|
1325
1482
|
} catch (error) {
|
|
1326
|
-
rethrowStructured(error, "The file could not be
|
|
1483
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
1327
1484
|
}
|
|
1328
1485
|
}
|
|
1329
|
-
function
|
|
1330
|
-
if (!compute) {
|
|
1331
|
-
return [];
|
|
1332
|
-
}
|
|
1333
|
-
try {
|
|
1334
|
-
return compute(entries);
|
|
1335
|
-
} catch (error) {
|
|
1336
|
-
rethrowStructured(error, "The file could not be analyzed for message validity.");
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
function createJsonFileAdapter(options) {
|
|
1486
|
+
function createTreeFileAdapter(options) {
|
|
1340
1487
|
const {
|
|
1341
1488
|
format,
|
|
1489
|
+
extensions,
|
|
1490
|
+
sniff,
|
|
1491
|
+
parse: parse2,
|
|
1492
|
+
serialize,
|
|
1342
1493
|
deriveEntry,
|
|
1343
|
-
extractPlaceholders
|
|
1344
|
-
computeInvalidIcuKeys
|
|
1345
|
-
validateMessage
|
|
1494
|
+
extractPlaceholders,
|
|
1495
|
+
computeInvalidIcuKeys,
|
|
1496
|
+
validateMessage,
|
|
1346
1497
|
validateTree,
|
|
1347
1498
|
buildWriteTree,
|
|
1348
1499
|
keyMode = "literal-leaf"
|
|
1349
1500
|
} = options;
|
|
1350
1501
|
return {
|
|
1351
1502
|
format,
|
|
1352
|
-
canHandle,
|
|
1353
|
-
extractPlaceholders
|
|
1354
|
-
|
|
1355
|
-
validateMessage: validateMessage2 ?? (() => true),
|
|
1503
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
1504
|
+
extractPlaceholders,
|
|
1505
|
+
validateMessage: validateMessage ?? (() => true),
|
|
1356
1506
|
async read(filePath, locale) {
|
|
1357
|
-
const
|
|
1358
|
-
if (outcome.kind === "not-a-file") {
|
|
1359
|
-
throw new AdapterError("INVALID_STRUCTURE", "The path is not a regular file.");
|
|
1360
|
-
}
|
|
1361
|
-
if (outcome.kind === "too-large") {
|
|
1362
|
-
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
1363
|
-
}
|
|
1507
|
+
const content = await readFileContent(filePath);
|
|
1364
1508
|
const namespace = namespaceOf(filePath);
|
|
1365
|
-
const entries = toEntries(
|
|
1509
|
+
const entries = toEntries(content, namespace, parse2, deriveEntry, keyMode, validateTree);
|
|
1366
1510
|
const resource = { locale, namespace, format, entries };
|
|
1367
|
-
const invalidIcuKeys = computeIcu(entries,
|
|
1511
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
1368
1512
|
return { resource, invalidIcuKeys };
|
|
1369
1513
|
},
|
|
1370
1514
|
async write(resource, filePath) {
|
|
1371
1515
|
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
1372
|
-
await atomicWriteFile(filePath,
|
|
1373
|
-
|
|
1516
|
+
await atomicWriteFile(filePath, serialize(tree));
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function isMetadataKey(key) {
|
|
1521
|
+
return key.startsWith("@");
|
|
1522
|
+
}
|
|
1523
|
+
function parseArbObject(content) {
|
|
1524
|
+
let parsed;
|
|
1525
|
+
try {
|
|
1526
|
+
parsed = JSON.parse(content);
|
|
1527
|
+
} catch {
|
|
1528
|
+
throw new AdapterError("INVALID_JSON", "The file is not valid JSON.");
|
|
1529
|
+
}
|
|
1530
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1531
|
+
throw new AdapterError(
|
|
1532
|
+
"INVALID_STRUCTURE",
|
|
1533
|
+
"The file is not a valid object (expected nested objects of string values)."
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
return parsed;
|
|
1537
|
+
}
|
|
1538
|
+
function stripArbMetadata(tree) {
|
|
1539
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
1540
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
1541
|
+
if (!isMetadataKey(key)) {
|
|
1542
|
+
out[key] = value;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return out;
|
|
1546
|
+
}
|
|
1547
|
+
function originalKey(encoded) {
|
|
1548
|
+
return decodeKeyToSegments(encoded).join(".");
|
|
1549
|
+
}
|
|
1550
|
+
function messagesFromEntries(entries) {
|
|
1551
|
+
const out = /* @__PURE__ */ new Map();
|
|
1552
|
+
for (const [key, entry] of entries) {
|
|
1553
|
+
out.set(originalKey(key), entry.value);
|
|
1554
|
+
}
|
|
1555
|
+
return out;
|
|
1556
|
+
}
|
|
1557
|
+
async function readDestinationPairs(filePath) {
|
|
1558
|
+
let parsed;
|
|
1559
|
+
try {
|
|
1560
|
+
const outcome = await readBounded2(filePath);
|
|
1561
|
+
if (outcome.kind !== "ok") {
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
parsed = JSON.parse(outcome.content);
|
|
1565
|
+
} catch {
|
|
1566
|
+
return null;
|
|
1567
|
+
}
|
|
1568
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
return Object.entries(parsed);
|
|
1572
|
+
}
|
|
1573
|
+
async function buildArbWriteTree(entries, filePath) {
|
|
1574
|
+
const messages = messagesFromEntries(entries);
|
|
1575
|
+
const pairs = await readDestinationPairs(filePath);
|
|
1576
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
1577
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
1578
|
+
for (const [key, value] of pairs ?? []) {
|
|
1579
|
+
const translated = isMetadataKey(key) ? void 0 : messages.get(key);
|
|
1580
|
+
if (translated !== void 0) {
|
|
1581
|
+
consumed.add(key);
|
|
1374
1582
|
}
|
|
1375
|
-
|
|
1583
|
+
out[key] = translated ?? value;
|
|
1584
|
+
}
|
|
1585
|
+
for (const [key, value] of messages) {
|
|
1586
|
+
if (!consumed.has(key)) {
|
|
1587
|
+
out[key] = value;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return out;
|
|
1591
|
+
}
|
|
1592
|
+
function parseArb(content) {
|
|
1593
|
+
return assertJsonRecord(stripArbMetadata(parseArbObject(content)));
|
|
1594
|
+
}
|
|
1595
|
+
function createArbAdapter() {
|
|
1596
|
+
return createTreeFileAdapter({
|
|
1597
|
+
format: "arb",
|
|
1598
|
+
extensions: [".arb"],
|
|
1599
|
+
sniff: (sample) => sample.trimStart().startsWith("{"),
|
|
1600
|
+
parse: parseArb,
|
|
1601
|
+
serialize: serializeJsonTree,
|
|
1602
|
+
extractPlaceholders: icuPlaceholders,
|
|
1603
|
+
deriveEntry: icuDeriveEntry,
|
|
1604
|
+
computeInvalidIcuKeys: icuInvalidKeys,
|
|
1605
|
+
validateMessage: icuIsValid,
|
|
1606
|
+
buildWriteTree: buildArbWriteTree
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
function createJsonFileAdapter(options) {
|
|
1610
|
+
return createTreeFileAdapter({
|
|
1611
|
+
...options,
|
|
1612
|
+
extensions: [".json"],
|
|
1613
|
+
sniff: (sample) => sample.trimStart().startsWith("{"),
|
|
1614
|
+
parse: parseJsonObject,
|
|
1615
|
+
serialize: serializeJsonTree
|
|
1616
|
+
});
|
|
1376
1617
|
}
|
|
1377
1618
|
var DOUBLE_BRACE_PATTERN = /\{\{[^{}]*\}\}/g;
|
|
1378
1619
|
var I18NEXT_PATTERN = /\{\{[^{}]*\}\}|\$t\([^()]*\)/g;
|
|
@@ -1419,91 +1660,13 @@ function createI18nextJsonAdapter() {
|
|
|
1419
1660
|
})
|
|
1420
1661
|
});
|
|
1421
1662
|
}
|
|
1422
|
-
var VALID_EMPTY = { placeholders: [], isPlural: false, valid: true };
|
|
1423
|
-
var INVALID = { placeholders: [], isPlural: false, valid: false };
|
|
1424
|
-
function tokenOf(element) {
|
|
1425
|
-
switch (element.type) {
|
|
1426
|
-
case TYPE.argument:
|
|
1427
|
-
case TYPE.number:
|
|
1428
|
-
case TYPE.date:
|
|
1429
|
-
case TYPE.time:
|
|
1430
|
-
case TYPE.select:
|
|
1431
|
-
case TYPE.plural:
|
|
1432
|
-
return `{${element.value}}`;
|
|
1433
|
-
case TYPE.tag:
|
|
1434
|
-
return `<${element.value}>`;
|
|
1435
|
-
default:
|
|
1436
|
-
return void 0;
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
function childMessages(element) {
|
|
1440
|
-
if (element.type === TYPE.plural || element.type === TYPE.select) {
|
|
1441
|
-
return Object.values(element.options).map((option) => option.value);
|
|
1442
|
-
}
|
|
1443
|
-
if (element.type === TYPE.tag) {
|
|
1444
|
-
return [element.children];
|
|
1445
|
-
}
|
|
1446
|
-
return [];
|
|
1447
|
-
}
|
|
1448
|
-
function collect(elements, add, state) {
|
|
1449
|
-
for (const element of elements) {
|
|
1450
|
-
const token = tokenOf(element);
|
|
1451
|
-
if (token !== void 0) {
|
|
1452
|
-
add(token);
|
|
1453
|
-
}
|
|
1454
|
-
if (element.type === TYPE.plural) {
|
|
1455
|
-
state.isPlural = true;
|
|
1456
|
-
}
|
|
1457
|
-
for (const child of childMessages(element)) {
|
|
1458
|
-
collect(child, add, state);
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
function analyzeIcuValue(value) {
|
|
1463
|
-
if (!value.includes("{") && !value.includes("<")) {
|
|
1464
|
-
return VALID_EMPTY;
|
|
1465
|
-
}
|
|
1466
|
-
try {
|
|
1467
|
-
const ast = parse(value);
|
|
1468
|
-
const placeholders = [];
|
|
1469
|
-
const state = { isPlural: false };
|
|
1470
|
-
collect(
|
|
1471
|
-
ast,
|
|
1472
|
-
(token) => {
|
|
1473
|
-
placeholders.push(token);
|
|
1474
|
-
},
|
|
1475
|
-
state
|
|
1476
|
-
);
|
|
1477
|
-
return { placeholders, isPlural: state.isPlural, valid: true };
|
|
1478
|
-
} catch {
|
|
1479
|
-
return INVALID;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
function extractPlaceholders(value) {
|
|
1483
|
-
return analyzeIcuValue(value).placeholders;
|
|
1484
|
-
}
|
|
1485
|
-
function validateMessage(value) {
|
|
1486
|
-
return analyzeIcuValue(value).valid;
|
|
1487
|
-
}
|
|
1488
|
-
function computeInvalidIcuKeys(entries) {
|
|
1489
|
-
const invalid = [];
|
|
1490
|
-
for (const [key, entry] of entries) {
|
|
1491
|
-
if (!validateMessage(entry.value)) {
|
|
1492
|
-
invalid.push(key);
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
return invalid;
|
|
1496
|
-
}
|
|
1497
1663
|
function createNextIntlJsonAdapter() {
|
|
1498
1664
|
return createJsonFileAdapter({
|
|
1499
1665
|
format: "next-intl-json",
|
|
1500
|
-
extractPlaceholders,
|
|
1501
|
-
deriveEntry:
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
},
|
|
1505
|
-
computeInvalidIcuKeys,
|
|
1506
|
-
validateMessage
|
|
1666
|
+
extractPlaceholders: icuPlaceholders,
|
|
1667
|
+
deriveEntry: icuDeriveEntry,
|
|
1668
|
+
computeInvalidIcuKeys: icuInvalidKeys,
|
|
1669
|
+
validateMessage: icuIsValid
|
|
1507
1670
|
});
|
|
1508
1671
|
}
|
|
1509
1672
|
function assertNotMixed(tree) {
|
|
@@ -1565,8 +1728,7 @@ function createNgxTranslateJsonAdapter() {
|
|
|
1565
1728
|
}),
|
|
1566
1729
|
validateTree: assertNotMixed,
|
|
1567
1730
|
buildWriteTree: buildNgxWriteTree,
|
|
1568
|
-
// ngx-translate flat style uses dotted keys as path notation, not literal leaves
|
|
1569
|
-
// keep the legacy non-encoding flatten so its flat/nested round-trip is unchanged.
|
|
1731
|
+
// ngx-translate flat style uses dotted keys as path notation, not literal leaves.
|
|
1570
1732
|
keyMode: "path-notation"
|
|
1571
1733
|
});
|
|
1572
1734
|
}
|
|
@@ -1642,8 +1804,245 @@ function createVueI18nJsonAdapter() {
|
|
|
1642
1804
|
})
|
|
1643
1805
|
});
|
|
1644
1806
|
}
|
|
1807
|
+
function toEntries2(content, namespace, parseEntries) {
|
|
1808
|
+
try {
|
|
1809
|
+
return parseEntries(content, namespace);
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
rethrowStructured(error, "The file could not be parsed.");
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function createFlatFileAdapter(options) {
|
|
1815
|
+
const {
|
|
1816
|
+
format,
|
|
1817
|
+
extensions,
|
|
1818
|
+
sniff,
|
|
1819
|
+
parseEntries,
|
|
1820
|
+
serializeEntries,
|
|
1821
|
+
extractPlaceholders,
|
|
1822
|
+
validateMessage,
|
|
1823
|
+
computeInvalidIcuKeys
|
|
1824
|
+
} = options;
|
|
1825
|
+
return {
|
|
1826
|
+
format,
|
|
1827
|
+
canHandle: buildCanHandle(extensions, sniff),
|
|
1828
|
+
extractPlaceholders,
|
|
1829
|
+
validateMessage: validateMessage ?? (() => true),
|
|
1830
|
+
async read(filePath, locale) {
|
|
1831
|
+
const content = await readFileContent(filePath);
|
|
1832
|
+
const namespace = namespaceOf(filePath);
|
|
1833
|
+
const entries = toEntries2(content, namespace, parseEntries);
|
|
1834
|
+
const resource = { locale, namespace, format, entries };
|
|
1835
|
+
const invalidIcuKeys = computeIcu(entries, computeInvalidIcuKeys);
|
|
1836
|
+
return { resource, invalidIcuKeys };
|
|
1837
|
+
},
|
|
1838
|
+
async write(resource, filePath) {
|
|
1839
|
+
const data = await serializeEntries(resource.entries, filePath);
|
|
1840
|
+
await atomicWriteFile(filePath, data);
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
var XLIFF_PATTERN = /<(?:x|g|bx|ex|ph|it|mrk)\b[^>]*>|\{[^{}]+\}/g;
|
|
1845
|
+
function extractXliffPlaceholders(value) {
|
|
1846
|
+
const result = [];
|
|
1847
|
+
for (const match of value.matchAll(XLIFF_PATTERN)) {
|
|
1848
|
+
const token = match[0];
|
|
1849
|
+
if (token !== void 0) {
|
|
1850
|
+
result.push(token);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return result;
|
|
1854
|
+
}
|
|
1855
|
+
var ELEMENT_NODE = 1;
|
|
1856
|
+
function isElement(node) {
|
|
1857
|
+
return node.nodeType === ELEMENT_NODE;
|
|
1858
|
+
}
|
|
1859
|
+
function elementChildren(parent) {
|
|
1860
|
+
return Array.from(parent.childNodes).filter(isElement);
|
|
1861
|
+
}
|
|
1862
|
+
function childByName(parent, name) {
|
|
1863
|
+
return elementChildren(parent).find((el) => el.localName === name) ?? null;
|
|
1864
|
+
}
|
|
1865
|
+
function collectByTag(root, name) {
|
|
1866
|
+
return Array.from(root.getElementsByTagName(name));
|
|
1867
|
+
}
|
|
1868
|
+
function unitKey(element, index) {
|
|
1869
|
+
return element.getAttribute("id") ?? element.getAttribute("resname") ?? `unit-${index}`;
|
|
1870
|
+
}
|
|
1871
|
+
function onFatal(level) {
|
|
1872
|
+
if (level === "fatalError") {
|
|
1873
|
+
throw new Error("malformed XML");
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
function assertNoDoctype(content) {
|
|
1877
|
+
if (/<!DOCTYPE/i.test(content) || /<!ENTITY/i.test(content)) {
|
|
1878
|
+
throw new AdapterError("INVALID_XML", "XLIFF with a DTD or entity declaration is rejected.");
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
function parseXml(content) {
|
|
1882
|
+
assertNoDoctype(content);
|
|
1883
|
+
let doc;
|
|
1884
|
+
try {
|
|
1885
|
+
doc = new DOMParser({ onError: onFatal }).parseFromString(content, "text/xml");
|
|
1886
|
+
} catch {
|
|
1887
|
+
throw new AdapterError("INVALID_XML", "The file is not valid XML.");
|
|
1888
|
+
}
|
|
1889
|
+
const root = doc.documentElement;
|
|
1890
|
+
if (root === null || root.localName !== "xliff") {
|
|
1891
|
+
throw new AdapterError("INVALID_STRUCTURE", "The file is not an XLIFF document.");
|
|
1892
|
+
}
|
|
1893
|
+
return { doc, root };
|
|
1894
|
+
}
|
|
1895
|
+
function walkXliff12(root) {
|
|
1896
|
+
const units = [];
|
|
1897
|
+
collectByTag(root, "trans-unit").forEach((tu, index) => {
|
|
1898
|
+
const source = childByName(tu, "source");
|
|
1899
|
+
if (source !== null) {
|
|
1900
|
+
units.push({
|
|
1901
|
+
key: unitKey(tu, index),
|
|
1902
|
+
source,
|
|
1903
|
+
target: childByName(tu, "target"),
|
|
1904
|
+
container: tu
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
return units;
|
|
1909
|
+
}
|
|
1910
|
+
function walkXliff20(root) {
|
|
1911
|
+
const units = [];
|
|
1912
|
+
collectByTag(root, "unit").forEach((unit, index) => {
|
|
1913
|
+
const baseKey = unitKey(unit, index);
|
|
1914
|
+
const segments = elementChildren(unit).filter((el) => el.localName === "segment");
|
|
1915
|
+
segments.forEach((segment, segIndex) => {
|
|
1916
|
+
const source = childByName(segment, "source");
|
|
1917
|
+
if (source !== null) {
|
|
1918
|
+
const key = segments.length > 1 ? `${baseKey}#${segIndex}` : baseKey;
|
|
1919
|
+
units.push({ key, source, target: childByName(segment, "target"), container: segment });
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
});
|
|
1923
|
+
return units;
|
|
1924
|
+
}
|
|
1925
|
+
function walkUnits(root) {
|
|
1926
|
+
const version = root.getAttribute("version") ?? "1.2";
|
|
1927
|
+
return version.startsWith("2") ? walkXliff20(root) : walkXliff12(root);
|
|
1928
|
+
}
|
|
1929
|
+
function innerXml(serializer, element) {
|
|
1930
|
+
return Array.from(element.childNodes).map((node) => serializer.serializeToString(node)).join("");
|
|
1931
|
+
}
|
|
1932
|
+
function unitValue(serializer, unit) {
|
|
1933
|
+
if (unit.target !== null) {
|
|
1934
|
+
const targetXml = innerXml(serializer, unit.target);
|
|
1935
|
+
if (targetXml.trim() !== "") {
|
|
1936
|
+
return targetXml;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return innerXml(serializer, unit.source);
|
|
1940
|
+
}
|
|
1941
|
+
function parseXliffEntries(content, namespace) {
|
|
1942
|
+
const { root } = parseXml(content);
|
|
1943
|
+
const serializer = new XMLSerializer();
|
|
1944
|
+
const out = /* @__PURE__ */ new Map();
|
|
1945
|
+
for (const unit of walkUnits(root)) {
|
|
1946
|
+
const value = unitValue(serializer, unit);
|
|
1947
|
+
out.set(unit.key, {
|
|
1948
|
+
key: unit.key,
|
|
1949
|
+
namespace,
|
|
1950
|
+
value,
|
|
1951
|
+
placeholders: extractXliffPlaceholders(value),
|
|
1952
|
+
isPlural: false
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
return out;
|
|
1956
|
+
}
|
|
1957
|
+
async function readDestination(filePath) {
|
|
1958
|
+
let outcome;
|
|
1959
|
+
try {
|
|
1960
|
+
outcome = await readBounded2(filePath);
|
|
1961
|
+
} catch {
|
|
1962
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination XLIFF file does not exist.");
|
|
1963
|
+
}
|
|
1964
|
+
if (outcome.kind === "not-a-file") {
|
|
1965
|
+
throw new AdapterError("INVALID_STRUCTURE", "The destination path is not a regular file.");
|
|
1966
|
+
}
|
|
1967
|
+
if (outcome.kind === "too-large") {
|
|
1968
|
+
throw new AdapterError("INPUT_TOO_LARGE", "The file exceeds the maximum allowed size.");
|
|
1969
|
+
}
|
|
1970
|
+
return outcome.content;
|
|
1971
|
+
}
|
|
1972
|
+
function fragmentNodes(parser, value) {
|
|
1973
|
+
try {
|
|
1974
|
+
const root = parser.parseFromString(`<wrapper>${value}</wrapper>`, "text/xml").documentElement;
|
|
1975
|
+
return root === null ? null : Array.from(root.childNodes);
|
|
1976
|
+
} catch {
|
|
1977
|
+
return null;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
function setTargetValue(doc, parser, element, value) {
|
|
1981
|
+
while (element.firstChild !== null) {
|
|
1982
|
+
element.removeChild(element.firstChild);
|
|
1983
|
+
}
|
|
1984
|
+
const nodes = fragmentNodes(parser, value);
|
|
1985
|
+
if (nodes === null) {
|
|
1986
|
+
element.textContent = value;
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
for (const node of nodes) {
|
|
1990
|
+
element.appendChild(doc.importNode(node, true));
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
async function serializeXliffEntries(entries, filePath) {
|
|
1994
|
+
const { doc, root } = parseXml(await readDestination(filePath));
|
|
1995
|
+
const parser = new DOMParser({ onError: onFatal });
|
|
1996
|
+
for (const unit of walkUnits(root)) {
|
|
1997
|
+
const entry = entries.get(unit.key);
|
|
1998
|
+
if (entry !== void 0) {
|
|
1999
|
+
const target = unit.target ?? doc.createElement("target");
|
|
2000
|
+
if (unit.target === null) {
|
|
2001
|
+
unit.container.appendChild(target);
|
|
2002
|
+
}
|
|
2003
|
+
setTargetValue(doc, parser, target, entry.value);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return new XMLSerializer().serializeToString(doc);
|
|
2007
|
+
}
|
|
2008
|
+
function sniffXliff(sample) {
|
|
2009
|
+
const head = sample.trimStart();
|
|
2010
|
+
return head.startsWith("<xliff") || head.startsWith("<?xml");
|
|
2011
|
+
}
|
|
2012
|
+
function createXliffAdapter() {
|
|
2013
|
+
return createFlatFileAdapter({
|
|
2014
|
+
format: "xliff",
|
|
2015
|
+
extensions: [".xlf", ".xliff"],
|
|
2016
|
+
sniff: sniffXliff,
|
|
2017
|
+
parseEntries: parseXliffEntries,
|
|
2018
|
+
serializeEntries: serializeXliffEntries,
|
|
2019
|
+
extractPlaceholders: extractXliffPlaceholders
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
function parseYamlObject(content) {
|
|
2023
|
+
let parsed;
|
|
2024
|
+
try {
|
|
2025
|
+
parsed = parse(content, { maxAliasCount: 100 });
|
|
2026
|
+
} catch {
|
|
2027
|
+
throw new AdapterError("INVALID_YAML", "The file is not valid YAML.");
|
|
2028
|
+
}
|
|
2029
|
+
return assertJsonRecord(parsed);
|
|
2030
|
+
}
|
|
2031
|
+
function createYamlAdapter() {
|
|
2032
|
+
return createTreeFileAdapter({
|
|
2033
|
+
format: "yaml",
|
|
2034
|
+
extensions: [".yml", ".yaml"],
|
|
2035
|
+
parse: parseYamlObject,
|
|
2036
|
+
serialize: (tree) => stringify(tree),
|
|
2037
|
+
extractPlaceholders: extractDoubleBracePlaceholders,
|
|
2038
|
+
deriveEntry: (_key, value) => ({
|
|
2039
|
+
placeholders: extractDoubleBracePlaceholders(value),
|
|
2040
|
+
isPlural: false
|
|
2041
|
+
})
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
1645
2044
|
function createDefaultRegistry() {
|
|
1646
|
-
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter());
|
|
2045
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter()).register(createVueI18nJsonAdapter()).register(createNextIntlJsonAdapter()).register(createNgxTranslateJsonAdapter()).register(createXliffAdapter()).register(createYamlAdapter()).register(createArbAdapter());
|
|
1647
2046
|
}
|
|
1648
2047
|
|
|
1649
2048
|
// src/selection/select-adapter.ts
|
|
@@ -1658,6 +2057,89 @@ function selectAdapter(format, registry = createDefaultRegistry()) {
|
|
|
1658
2057
|
);
|
|
1659
2058
|
}
|
|
1660
2059
|
|
|
2060
|
+
// src/flow/source.ts
|
|
2061
|
+
async function readSource(config, cwd, fs, adapter) {
|
|
2062
|
+
const sourcePath = localeFilePath(cwd, config.files.pattern, config.sourceLocale);
|
|
2063
|
+
if (!await fs.fileExists(sourcePath)) {
|
|
2064
|
+
throw new SdkError(
|
|
2065
|
+
"SOURCE_UNREADABLE",
|
|
2066
|
+
`The source locale file was not found at ${sourcePath}.`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
try {
|
|
2070
|
+
return await adapter.read(sourcePath, config.sourceLocale);
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2073
|
+
throw new SdkError(
|
|
2074
|
+
"SOURCE_INVALID",
|
|
2075
|
+
`The source locale file at ${sourcePath} could not be read: ${detail}`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// src/flow/diff-locales.ts
|
|
2081
|
+
async function readTarget(cwd, config, adapter, fs, locale) {
|
|
2082
|
+
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
2083
|
+
if (!await fs.fileExists(path)) {
|
|
2084
|
+
return { locale, namespace: "", format: config.format, entries: /* @__PURE__ */ new Map() };
|
|
2085
|
+
}
|
|
2086
|
+
return (await adapter.read(path, locale)).resource;
|
|
2087
|
+
}
|
|
2088
|
+
function selectedLocales(config, requested) {
|
|
2089
|
+
if (requested === void 0) {
|
|
2090
|
+
return config.targetLocales;
|
|
2091
|
+
}
|
|
2092
|
+
const wanted = new Set(requested);
|
|
2093
|
+
return config.targetLocales.filter((locale) => wanted.has(locale));
|
|
2094
|
+
}
|
|
2095
|
+
async function diffLocales(input, deps = {}) {
|
|
2096
|
+
const config = input.config;
|
|
2097
|
+
const cwd = input.cwd ?? process.cwd();
|
|
2098
|
+
const fs = deps.fs ?? defaultFs;
|
|
2099
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
2100
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
2101
|
+
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
2102
|
+
return Promise.all(
|
|
2103
|
+
selectedLocales(config, input.locales).map(async (locale) => {
|
|
2104
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
2105
|
+
const diff2 = diffResources(source.resource, target, { baseline: baselineFor(lock, locale) });
|
|
2106
|
+
return { locale, diff: diff2 };
|
|
2107
|
+
})
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/flow/check.ts
|
|
2112
|
+
function toCheckSummary(locale, diff2) {
|
|
2113
|
+
return {
|
|
2114
|
+
locale,
|
|
2115
|
+
missing: diff2.missing.length,
|
|
2116
|
+
stale: diff2.changed.length,
|
|
2117
|
+
upToDate: diff2.unchanged.length,
|
|
2118
|
+
inSync: diff2.missing.length === 0 && diff2.changed.length === 0
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
async function check(input, deps = {}) {
|
|
2122
|
+
const results = await diffLocales(input, deps);
|
|
2123
|
+
const locales = results.map(({ locale, diff: diff2 }) => toCheckSummary(locale, diff2));
|
|
2124
|
+
return { inSync: locales.every((entry) => entry.inSync), locales };
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// src/flow/diff.ts
|
|
2128
|
+
function toLocaleDiff(locale, diff2) {
|
|
2129
|
+
return {
|
|
2130
|
+
locale,
|
|
2131
|
+
missing: diff2.missing,
|
|
2132
|
+
changed: diff2.changed,
|
|
2133
|
+
orphaned: diff2.orphaned,
|
|
2134
|
+
hasPendingChanges: diff2.missing.length > 0 || diff2.changed.length > 0
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
async function diff(input, deps = {}) {
|
|
2138
|
+
const results = await diffLocales(input, deps);
|
|
2139
|
+
const locales = results.map(({ locale, diff: result }) => toLocaleDiff(locale, result));
|
|
2140
|
+
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
2141
|
+
}
|
|
2142
|
+
|
|
1661
2143
|
// src/selection/select-provider.ts
|
|
1662
2144
|
function selectProvider(config, createProvider = buildProvider) {
|
|
1663
2145
|
try {
|
|
@@ -1908,7 +2390,7 @@ async function generatePluralForms(context) {
|
|
|
1908
2390
|
function emptyResource(locale, format) {
|
|
1909
2391
|
return { locale, namespace: "", format, entries: /* @__PURE__ */ new Map() };
|
|
1910
2392
|
}
|
|
1911
|
-
async function
|
|
2393
|
+
async function readTarget2(params) {
|
|
1912
2394
|
const path = localeFilePath(params.cwd, params.filesPattern, params.targetLocale);
|
|
1913
2395
|
if (!await params.fs.fileExists(path)) {
|
|
1914
2396
|
return emptyResource(params.targetLocale, params.format);
|
|
@@ -1926,12 +2408,12 @@ function buildRequest3(params, entries) {
|
|
|
1926
2408
|
};
|
|
1927
2409
|
}
|
|
1928
2410
|
async function runLocale(params) {
|
|
1929
|
-
const target = await
|
|
1930
|
-
const
|
|
1931
|
-
const orphaned = params.generatePlurals ?
|
|
2411
|
+
const target = await readTarget2(params);
|
|
2412
|
+
const diff2 = diffResources(params.source, target, { baseline: params.baseline });
|
|
2413
|
+
const orphaned = params.generatePlurals ? diff2.orphaned.filter((key) => !isGeneratedPluralKey(key, sourcePluralBaseKeys(params.source))) : diff2.orphaned;
|
|
1932
2414
|
const pruned = params.prune ? orphaned : [];
|
|
1933
2415
|
const invalidIcu = new Set(params.sourceInvalidIcuKeys);
|
|
1934
|
-
const candidates = [...
|
|
2416
|
+
const candidates = [...diff2.missing, ...diff2.changed];
|
|
1935
2417
|
const toTranslate = candidates.filter((key) => !invalidIcu.has(key));
|
|
1936
2418
|
const invalidIcuSource = candidates.filter((key) => invalidIcu.has(key));
|
|
1937
2419
|
const pluralNotice = detectMissingPluralCategories(
|
|
@@ -1945,7 +2427,7 @@ async function runLocale(params) {
|
|
|
1945
2427
|
return {
|
|
1946
2428
|
summary: baseSummary({
|
|
1947
2429
|
locale: params.targetLocale,
|
|
1948
|
-
unchanged:
|
|
2430
|
+
unchanged: diff2.unchanged,
|
|
1949
2431
|
orphaned,
|
|
1950
2432
|
invalidIcuSource,
|
|
1951
2433
|
translated: toTranslate,
|
|
@@ -1994,12 +2476,12 @@ async function runLocale(params) {
|
|
|
1994
2476
|
return {
|
|
1995
2477
|
summary: baseSummary({
|
|
1996
2478
|
locale: params.targetLocale,
|
|
1997
|
-
unchanged:
|
|
2479
|
+
unchanged: diff2.unchanged,
|
|
1998
2480
|
orphaned,
|
|
1999
2481
|
invalidIcuSource,
|
|
2000
2482
|
translated: [...accepted.keys()],
|
|
2001
2483
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
2002
|
-
// Withheld generated forms surface alongside withheld translations
|
|
2484
|
+
// Withheld generated forms surface alongside withheld translations: both failed integrity.
|
|
2003
2485
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
2004
2486
|
pruned,
|
|
2005
2487
|
notices
|
|
@@ -2123,26 +2605,6 @@ function carryGeneratedLock(lockEntries, baseline, key, sourceBaseKeys) {
|
|
|
2123
2605
|
}
|
|
2124
2606
|
}
|
|
2125
2607
|
|
|
2126
|
-
// src/flow/source.ts
|
|
2127
|
-
async function readSource(config, cwd, fs, adapter) {
|
|
2128
|
-
const sourcePath = localeFilePath(cwd, config.files.pattern, config.sourceLocale);
|
|
2129
|
-
if (!await fs.fileExists(sourcePath)) {
|
|
2130
|
-
throw new SdkError(
|
|
2131
|
-
"SOURCE_UNREADABLE",
|
|
2132
|
-
`The source locale file was not found at ${sourcePath}.`
|
|
2133
|
-
);
|
|
2134
|
-
}
|
|
2135
|
-
try {
|
|
2136
|
-
return await adapter.read(sourcePath, config.sourceLocale);
|
|
2137
|
-
} catch (error) {
|
|
2138
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
2139
|
-
throw new SdkError(
|
|
2140
|
-
"SOURCE_INVALID",
|
|
2141
|
-
`The source locale file at ${sourcePath} could not be read: ${detail}`
|
|
2142
|
-
);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
2608
|
// src/flow/translate-project.ts
|
|
2147
2609
|
async function translate2(input, deps = {}) {
|
|
2148
2610
|
const config = input.config;
|
|
@@ -2351,7 +2813,7 @@ var DEFAULT_WORKBOOK_LIMITS = {
|
|
|
2351
2813
|
maxRowsPerSheet: 1e5,
|
|
2352
2814
|
maxCellsPerRow: 64
|
|
2353
2815
|
};
|
|
2354
|
-
function
|
|
2816
|
+
function assertNoDoctype2(name, xml) {
|
|
2355
2817
|
if (/<!DOCTYPE/i.test(xml) || /<!ENTITY/i.test(xml)) {
|
|
2356
2818
|
throw new ExchangeError(
|
|
2357
2819
|
"WORKBOOK_INVALID",
|
|
@@ -2406,7 +2868,7 @@ async function guardWorkbookBytes(bytes, limits) {
|
|
|
2406
2868
|
`The workbook decompresses to more than the maximum of ${limits.maxDecompressedBytes} bytes.`
|
|
2407
2869
|
);
|
|
2408
2870
|
}
|
|
2409
|
-
|
|
2871
|
+
assertNoDoctype2(file.name, content);
|
|
2410
2872
|
}
|
|
2411
2873
|
}
|
|
2412
2874
|
var rowSchema = z.object({
|
|
@@ -2515,7 +2977,7 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
2515
2977
|
|
|
2516
2978
|
// src/flow/workbook/export-workbook.ts
|
|
2517
2979
|
var DEFAULT_WORKBOOK_PATH = "verbatra-translations.xlsx";
|
|
2518
|
-
async function
|
|
2980
|
+
async function readTarget3(cwd, config, adapter, fs, locale) {
|
|
2519
2981
|
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
2520
2982
|
if (!await fs.fileExists(path)) {
|
|
2521
2983
|
return { locale, namespace: "", format: config.format, entries: /* @__PURE__ */ new Map() };
|
|
@@ -2523,7 +2985,7 @@ async function readTarget2(cwd, config, adapter, fs, locale) {
|
|
|
2523
2985
|
return (await adapter.read(path, locale)).resource;
|
|
2524
2986
|
}
|
|
2525
2987
|
function buildRows(source, target, baseline, includeUnchanged) {
|
|
2526
|
-
const
|
|
2988
|
+
const diff2 = diffResources(source, target, { baseline });
|
|
2527
2989
|
const rows = [];
|
|
2528
2990
|
const add = (keys, status) => {
|
|
2529
2991
|
for (const key of keys) {
|
|
@@ -2541,14 +3003,14 @@ function buildRows(source, target, baseline, includeUnchanged) {
|
|
|
2541
3003
|
});
|
|
2542
3004
|
}
|
|
2543
3005
|
};
|
|
2544
|
-
add(
|
|
2545
|
-
add(
|
|
3006
|
+
add(diff2.missing, "new");
|
|
3007
|
+
add(diff2.changed, "changed");
|
|
2546
3008
|
if (includeUnchanged) {
|
|
2547
|
-
add(
|
|
3009
|
+
add(diff2.unchanged, "changed");
|
|
2548
3010
|
}
|
|
2549
3011
|
return [...rows].sort((a, b) => a.key < b.key ? -1 : 1);
|
|
2550
3012
|
}
|
|
2551
|
-
function
|
|
3013
|
+
function selectedLocales2(config, requested) {
|
|
2552
3014
|
if (requested === void 0) {
|
|
2553
3015
|
return config.targetLocales;
|
|
2554
3016
|
}
|
|
@@ -2562,10 +3024,10 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
2562
3024
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
2563
3025
|
const source = await readSource(config, cwd, fs, adapter);
|
|
2564
3026
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
2565
|
-
const locales =
|
|
3027
|
+
const locales = selectedLocales2(config, input.locales);
|
|
2566
3028
|
const sheets = await Promise.all(
|
|
2567
3029
|
locales.map(async (locale) => {
|
|
2568
|
-
const target = await
|
|
3030
|
+
const target = await readTarget3(cwd, config, adapter, fs, locale);
|
|
2569
3031
|
const rows = buildRows(
|
|
2570
3032
|
source.resource,
|
|
2571
3033
|
target,
|
|
@@ -2635,7 +3097,7 @@ function classifyRows(params, buckets) {
|
|
|
2635
3097
|
}
|
|
2636
3098
|
}
|
|
2637
3099
|
function importLocale(params) {
|
|
2638
|
-
const
|
|
3100
|
+
const diff2 = diffResources(params.source, params.target, { baseline: params.baseline });
|
|
2639
3101
|
const buckets = { accepted: /* @__PURE__ */ new Map(), mismatches: [], withheld: /* @__PURE__ */ new Set() };
|
|
2640
3102
|
classifyRows(params, buckets);
|
|
2641
3103
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
@@ -2644,8 +3106,8 @@ function importLocale(params) {
|
|
|
2644
3106
|
locale: params.sheet.locale,
|
|
2645
3107
|
status: "succeeded",
|
|
2646
3108
|
translated: [...buckets.accepted.keys()].sort(),
|
|
2647
|
-
unchanged:
|
|
2648
|
-
orphaned:
|
|
3109
|
+
unchanged: diff2.unchanged,
|
|
3110
|
+
orphaned: diff2.orphaned,
|
|
2649
3111
|
// Import never prunes: orphans are reported but never removed here (pruning is a translate-flow concern).
|
|
2650
3112
|
pruned: [],
|
|
2651
3113
|
invalidIcuSource,
|
|
@@ -2672,7 +3134,7 @@ async function readWorkbookBytes(path, fs) {
|
|
|
2672
3134
|
}
|
|
2673
3135
|
return read.bytes;
|
|
2674
3136
|
}
|
|
2675
|
-
async function
|
|
3137
|
+
async function readTarget4(cwd, config, adapter, fs, locale) {
|
|
2676
3138
|
const path = localeFilePath(cwd, config.files.pattern, locale);
|
|
2677
3139
|
if (!await fs.fileExists(path)) {
|
|
2678
3140
|
return { locale, namespace: "", format: config.format, entries: /* @__PURE__ */ new Map() };
|
|
@@ -2711,7 +3173,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
2711
3173
|
`The workbook has a sheet for locale "${sheet.locale}", which is not a configured target locale.`
|
|
2712
3174
|
);
|
|
2713
3175
|
}
|
|
2714
|
-
const target = await
|
|
3176
|
+
const target = await readTarget4(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
2715
3177
|
const baseline = baselineFor(lock, sheet.locale);
|
|
2716
3178
|
const { summary, accepted, withheld } = importLocale({
|
|
2717
3179
|
sheet,
|
|
@@ -2781,6 +3243,16 @@ async function importWorkbook(input, deps = {}) {
|
|
|
2781
3243
|
const { succeeded, failed } = partition(summaries);
|
|
2782
3244
|
return { dryRun, locales: summaries, succeeded, failed };
|
|
2783
3245
|
}
|
|
3246
|
+
|
|
3247
|
+
// src/scaffolding.ts
|
|
3248
|
+
var scaffoldingMetadata = {
|
|
3249
|
+
/** Provider id -> the environment variable its API key is read from. Owned by ai-providers. */
|
|
3250
|
+
providerEnv: PROVIDER_ENV,
|
|
3251
|
+
/** LLM provider id -> a cosmetic default scaffold model. Owned by ai-providers. DeepL has none. */
|
|
3252
|
+
scaffoldModels: SCAFFOLD_MODELS,
|
|
3253
|
+
/** The closed set of source format ids. Owned by core. */
|
|
3254
|
+
supportedFormats: SUPPORTED_FORMATS
|
|
3255
|
+
};
|
|
2784
3256
|
var defaultCreateWatcher = (paths) => {
|
|
2785
3257
|
const fsWatcher = watch$1([...paths], { persistent: true, ignoreInitial: true });
|
|
2786
3258
|
return {
|
|
@@ -2887,6 +3359,6 @@ async function watch(input, deps = {}) {
|
|
|
2887
3359
|
return { stop };
|
|
2888
3360
|
}
|
|
2889
3361
|
|
|
2890
|
-
export { DEFAULT_WORKBOOK_PATH, SdkError, defineConfig, exportWorkbook, importWorkbook, loadConfig, translate2 as translate, verbatraConfigSchema, watch };
|
|
3362
|
+
export { DEFAULT_WORKBOOK_PATH, SdkError, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
2891
3363
|
//# sourceMappingURL=index.js.map
|
|
2892
3364
|
//# sourceMappingURL=index.js.map
|