@verbatra/sdk 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.cjs +84 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +84 -31
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://www.npmjs.com/package/@verbatra/sdk"><img src="https://img.shields.io/npm/v/@verbatra/sdk?label=%40verbatra%2Fsdk" alt="@verbatra/sdk npm version" /></a>
|
|
13
|
+
<a href="https://github.com/mariokreitz/verbatra/actions/workflows/ci.yml"><img src="https://github.com/mariokreitz/verbatra/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI" /></a>
|
|
14
|
+
<a href="https://github.com/mariokreitz/verbatra/actions/workflows/e2e.yml"><img src="https://github.com/mariokreitz/verbatra/actions/workflows/e2e.yml/badge.svg?branch=main" alt="E2E" /></a>
|
|
13
15
|
<a href="https://codecov.io/gh/mariokreitz/verbatra"><img src="https://codecov.io/gh/mariokreitz/verbatra/graph/badge.svg" alt="Coverage" /></a>
|
|
14
16
|
<a href="https://github.com/mariokreitz/verbatra/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT" /></a>
|
|
15
17
|
</p>
|
package/dist/index.cjs
CHANGED
|
@@ -17,6 +17,7 @@ var icuMessageformatParser = require('@formatjs/icu-messageformat-parser');
|
|
|
17
17
|
var xmldom = require('@xmldom/xmldom');
|
|
18
18
|
var yaml = require('yaml');
|
|
19
19
|
var ExcelJS = require('exceljs');
|
|
20
|
+
var stream = require('stream');
|
|
20
21
|
var JSZip = require('jszip');
|
|
21
22
|
var chokidar = require('chokidar');
|
|
22
23
|
|
|
@@ -181,7 +182,7 @@ function checkPlaceholders(source, translated) {
|
|
|
181
182
|
const extra = multisetExcess(translatedCounts, sourceCounts);
|
|
182
183
|
const reordered = missing.length === 0 && extra.length === 0 && !sameOrder(source, translated);
|
|
183
184
|
return {
|
|
184
|
-
matches: missing.length === 0 && extra.length === 0
|
|
185
|
+
matches: missing.length === 0 && extra.length === 0,
|
|
185
186
|
missing,
|
|
186
187
|
extra,
|
|
187
188
|
reordered
|
|
@@ -502,6 +503,19 @@ function createDefaultClient2() {
|
|
|
502
503
|
};
|
|
503
504
|
return { client, freeAccount };
|
|
504
505
|
}
|
|
506
|
+
var PLACEHOLDER_UNSUPPORTED_MESSAGE = "Some entries contain placeholders or ICU syntax that DeepL cannot preserve; they were left untranslated. Use an LLM provider to translate placeholder-bearing strings.";
|
|
507
|
+
function partitionByPlaceholders(entries) {
|
|
508
|
+
const protectable = [];
|
|
509
|
+
const unprotectable = [];
|
|
510
|
+
for (const entry of entries) {
|
|
511
|
+
if (entry.placeholders.length > 0) {
|
|
512
|
+
unprotectable.push(entry);
|
|
513
|
+
} else {
|
|
514
|
+
protectable.push(entry);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return { protectable, unprotectable };
|
|
518
|
+
}
|
|
505
519
|
var FORMALITY_DOWNGRADED_MESSAGE = "Formality was not applied: the configured DeepL key is a free-tier key, which does not support formality.";
|
|
506
520
|
var GLOSSARY_IGNORED_MESSAGE = "The supplied glossary term map was not applied: DeepL uses configured glossary IDs, not term maps.";
|
|
507
521
|
function buildTranslateOptions(input) {
|
|
@@ -565,6 +579,7 @@ function resolveClient(deps) {
|
|
|
565
579
|
}
|
|
566
580
|
async function translate(bundle, config, request) {
|
|
567
581
|
const data = validateRequest(request);
|
|
582
|
+
const { protectable, unprotectable } = partitionByPlaceholders(data.entries);
|
|
568
583
|
const genericGlossarySupplied = request.glossary !== void 0 && Object.keys(request.glossary).length > 0;
|
|
569
584
|
const { options, notices } = buildTranslateOptions({
|
|
570
585
|
freeAccount: bundle.freeAccount,
|
|
@@ -572,18 +587,28 @@ async function translate(bundle, config, request) {
|
|
|
572
587
|
...data.tone !== void 0 ? { tone: data.tone } : {},
|
|
573
588
|
...config.glossaryId !== void 0 ? { glossaryId: config.glossaryId } : {}
|
|
574
589
|
});
|
|
575
|
-
const
|
|
576
|
-
const results = await callClient2(
|
|
590
|
+
const { values, integrity } = await translateProtectable(
|
|
577
591
|
bundle.client,
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
592
|
+
data,
|
|
593
|
+
protectable,
|
|
594
|
+
options,
|
|
595
|
+
request.extractPlaceholders
|
|
582
596
|
);
|
|
583
|
-
|
|
584
|
-
|
|
597
|
+
if (unprotectable.length > 0) {
|
|
598
|
+
notices.push({ code: "PLACEHOLDER_UNSUPPORTED", message: PLACEHOLDER_UNSUPPORTED_MESSAGE });
|
|
599
|
+
}
|
|
585
600
|
return { values, integrity, notices };
|
|
586
601
|
}
|
|
602
|
+
async function translateProtectable(client, data, protectable, options, extract) {
|
|
603
|
+
if (protectable.length === 0) {
|
|
604
|
+
return { values: /* @__PURE__ */ new Map(), integrity: /* @__PURE__ */ new Map() };
|
|
605
|
+
}
|
|
606
|
+
const texts = protectable.map((entry) => entry.value);
|
|
607
|
+
const results = await callClient2(client, texts, data.sourceLocale, data.targetLocale, options);
|
|
608
|
+
const { values, integrityInputs } = zipResults(protectable, results);
|
|
609
|
+
const integrity = checkBatchIntegrity(integrityInputs, extract);
|
|
610
|
+
return { values, integrity };
|
|
611
|
+
}
|
|
587
612
|
function callClient2(client, texts, sourceLang, targetLang, options) {
|
|
588
613
|
return guardProviderCall(() => client.translateText(texts, sourceLang, targetLang, options));
|
|
589
614
|
}
|
|
@@ -2087,6 +2112,24 @@ function selectAdapter(format, registry = createDefaultRegistry()) {
|
|
|
2087
2112
|
);
|
|
2088
2113
|
}
|
|
2089
2114
|
|
|
2115
|
+
// src/flow/select-locales.ts
|
|
2116
|
+
function selectLocales(config, requested) {
|
|
2117
|
+
if (requested === void 0) {
|
|
2118
|
+
return config.targetLocales;
|
|
2119
|
+
}
|
|
2120
|
+
const configured = new Set(config.targetLocales);
|
|
2121
|
+
const unknown = requested.filter((locale) => !configured.has(locale));
|
|
2122
|
+
if (unknown.length > 0) {
|
|
2123
|
+
const label = unknown.length === 1 ? "locale" : "locales";
|
|
2124
|
+
throw new SdkError(
|
|
2125
|
+
"UNKNOWN_LOCALE",
|
|
2126
|
+
`Requested ${label} not in the configured target locales: ${unknown.join(", ")}. Configured targets: ${config.targetLocales.join(", ")}.`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
const wanted = new Set(requested);
|
|
2130
|
+
return config.targetLocales.filter((locale) => wanted.has(locale));
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2090
2133
|
// src/flow/source.ts
|
|
2091
2134
|
async function readSource(config, cwd, fs, adapter) {
|
|
2092
2135
|
const sourcePath = localeFilePath(cwd, config.files.pattern, config.sourceLocale);
|
|
@@ -2115,13 +2158,6 @@ async function readTarget(cwd, config, adapter, fs, locale) {
|
|
|
2115
2158
|
}
|
|
2116
2159
|
return (await adapter.read(path, locale)).resource;
|
|
2117
2160
|
}
|
|
2118
|
-
function selectedLocales(config, requested) {
|
|
2119
|
-
if (requested === void 0) {
|
|
2120
|
-
return config.targetLocales;
|
|
2121
|
-
}
|
|
2122
|
-
const wanted = new Set(requested);
|
|
2123
|
-
return config.targetLocales.filter((locale) => wanted.has(locale));
|
|
2124
|
-
}
|
|
2125
2161
|
async function diffLocales(input, deps = {}) {
|
|
2126
2162
|
const config = input.config;
|
|
2127
2163
|
const cwd = input.cwd ?? process.cwd();
|
|
@@ -2130,7 +2166,7 @@ async function diffLocales(input, deps = {}) {
|
|
|
2130
2166
|
const source = await readSource(config, cwd, fs, adapter);
|
|
2131
2167
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
2132
2168
|
return Promise.all(
|
|
2133
|
-
|
|
2169
|
+
selectLocales(config, input.locales).map(async (locale) => {
|
|
2134
2170
|
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
2135
2171
|
const diff2 = diffResources(source.resource, target, { baseline: baselineFor(lock, locale) });
|
|
2136
2172
|
return { locale, diff: diff2 };
|
|
@@ -2856,6 +2892,34 @@ function declaredSize(file) {
|
|
|
2856
2892
|
const size = data?.uncompressedSize;
|
|
2857
2893
|
return typeof size === "number" && Number.isFinite(size) ? size : void 0;
|
|
2858
2894
|
}
|
|
2895
|
+
async function streamEntryBounded(file, remaining) {
|
|
2896
|
+
const chunks = [];
|
|
2897
|
+
let raw = 0;
|
|
2898
|
+
let oversize = false;
|
|
2899
|
+
const source = new stream.Readable({ objectMode: true }).wrap(
|
|
2900
|
+
file.nodeStream("nodebuffer")
|
|
2901
|
+
);
|
|
2902
|
+
try {
|
|
2903
|
+
for await (const chunk2 of source) {
|
|
2904
|
+
const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(chunk2);
|
|
2905
|
+
raw += buf.length;
|
|
2906
|
+
if (raw > remaining) {
|
|
2907
|
+
oversize = true;
|
|
2908
|
+
break;
|
|
2909
|
+
}
|
|
2910
|
+
chunks.push(buf);
|
|
2911
|
+
}
|
|
2912
|
+
} catch {
|
|
2913
|
+
throw new ExchangeError("WORKBOOK_INVALID", "A workbook entry could not be decompressed.");
|
|
2914
|
+
}
|
|
2915
|
+
if (oversize) {
|
|
2916
|
+
throw new ExchangeError(
|
|
2917
|
+
"WORKBOOK_INVALID",
|
|
2918
|
+
"The workbook decompresses to more than the permitted maximum bytes."
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2922
|
+
}
|
|
2859
2923
|
async function guardWorkbookBytes(bytes, limits) {
|
|
2860
2924
|
let zip;
|
|
2861
2925
|
try {
|
|
@@ -2885,12 +2949,8 @@ async function guardWorkbookBytes(bytes, limits) {
|
|
|
2885
2949
|
}
|
|
2886
2950
|
let actualTotal = 0;
|
|
2887
2951
|
for (const file of files) {
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
content = await file.async("string");
|
|
2891
|
-
} catch {
|
|
2892
|
-
throw new ExchangeError("WORKBOOK_INVALID", "A workbook entry could not be decompressed.");
|
|
2893
|
-
}
|
|
2952
|
+
const remaining = limits.maxDecompressedBytes - actualTotal;
|
|
2953
|
+
const content = await streamEntryBounded(file, remaining);
|
|
2894
2954
|
actualTotal += Buffer.byteLength(content, "utf8");
|
|
2895
2955
|
if (actualTotal > limits.maxDecompressedBytes) {
|
|
2896
2956
|
throw new ExchangeError(
|
|
@@ -3040,13 +3100,6 @@ function buildRows(source, target, baseline, includeUnchanged) {
|
|
|
3040
3100
|
}
|
|
3041
3101
|
return [...rows].sort((a, b) => a.key < b.key ? -1 : 1);
|
|
3042
3102
|
}
|
|
3043
|
-
function selectedLocales2(config, requested) {
|
|
3044
|
-
if (requested === void 0) {
|
|
3045
|
-
return config.targetLocales;
|
|
3046
|
-
}
|
|
3047
|
-
const wanted = new Set(requested);
|
|
3048
|
-
return config.targetLocales.filter((locale) => wanted.has(locale));
|
|
3049
|
-
}
|
|
3050
3103
|
async function exportWorkbook(input, deps = {}) {
|
|
3051
3104
|
const config = input.config;
|
|
3052
3105
|
const cwd = input.cwd ?? process.cwd();
|
|
@@ -3054,7 +3107,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
3054
3107
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
3055
3108
|
const source = await readSource(config, cwd, fs, adapter);
|
|
3056
3109
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
3057
|
-
const locales =
|
|
3110
|
+
const locales = selectLocales(config, input.locales);
|
|
3058
3111
|
const sheets = await Promise.all(
|
|
3059
3112
|
locales.map(async (locale) => {
|
|
3060
3113
|
const target = await readTarget3(cwd, config, adapter, fs, locale);
|