@verbatra/sdk 0.6.4 → 0.7.1
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 +7 -7
- package/dist/index.cjs +718 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -16
- package/dist/index.d.ts +177 -16
- package/dist/index.js +718 -106
- package/dist/index.js.map +1 -1
- package/package.json +16 -9
package/dist/index.cjs
CHANGED
|
@@ -288,6 +288,9 @@ var defaultFs = {
|
|
|
288
288
|
createExclusive: (path, data) => createExclusive(path, data),
|
|
289
289
|
deleteFile: async (path) => {
|
|
290
290
|
await promises.rm(path, { force: true });
|
|
291
|
+
},
|
|
292
|
+
mkdir: async (path) => {
|
|
293
|
+
await promises.mkdir(path, { recursive: true });
|
|
291
294
|
}
|
|
292
295
|
};
|
|
293
296
|
var MAX_GLOSSARY_FILE_BYTES = 1024 * 1024;
|
|
@@ -526,9 +529,109 @@ function assessValueDegeneracy(sourceValue, candidateValue) {
|
|
|
526
529
|
}
|
|
527
530
|
return { degenerate: hasRunawayRepetition(candidate) };
|
|
528
531
|
}
|
|
532
|
+
|
|
533
|
+
// src/locale-path/pattern.ts
|
|
529
534
|
var LOCALE_TOKEN = "{locale}";
|
|
530
|
-
|
|
531
|
-
|
|
535
|
+
var SEPARATOR = /[/\\]/;
|
|
536
|
+
function tokenOccupiesWholeSegments(pattern) {
|
|
537
|
+
return pattern.split(SEPARATOR).every((segment) => segment === LOCALE_TOKEN || !segment.includes(LOCALE_TOKEN));
|
|
538
|
+
}
|
|
539
|
+
function expandPattern(pattern, spelling) {
|
|
540
|
+
return pattern.replaceAll(LOCALE_TOKEN, () => spelling);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/locale-path/tag.ts
|
|
544
|
+
var LANGUAGE = /^[a-z]{2,3}$/;
|
|
545
|
+
var SCRIPT = /^[a-z]{4}$/;
|
|
546
|
+
var REGION = /^([a-z]{2}|[0-9]{3})$/;
|
|
547
|
+
var NUMERIC_REGION = /^[0-9]{3}$/;
|
|
548
|
+
var VARIANT = /^([a-z0-9]{5,8}|[0-9][a-z0-9]{3})$/;
|
|
549
|
+
function subtagIf(subtags, index, pattern) {
|
|
550
|
+
const subtag = subtags[index];
|
|
551
|
+
return subtag !== void 0 && pattern.test(subtag) ? subtag : void 0;
|
|
552
|
+
}
|
|
553
|
+
function parseLocaleTag(locale) {
|
|
554
|
+
const subtags = locale.toLowerCase().split("-");
|
|
555
|
+
const language = subtags[0];
|
|
556
|
+
if (language === void 0 || !LANGUAGE.test(language)) {
|
|
557
|
+
return void 0;
|
|
558
|
+
}
|
|
559
|
+
let index = 1;
|
|
560
|
+
const script = subtagIf(subtags, index, SCRIPT);
|
|
561
|
+
index += script === void 0 ? 0 : 1;
|
|
562
|
+
const region = subtagIf(subtags, index, REGION);
|
|
563
|
+
index += region === void 0 ? 0 : 1;
|
|
564
|
+
const variants = subtags.slice(index);
|
|
565
|
+
if (!variants.every((variant) => VARIANT.test(variant))) {
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
return { language, script, region, variants };
|
|
569
|
+
}
|
|
570
|
+
function conventionalSubtags(tag) {
|
|
571
|
+
const subtags = [tag.language];
|
|
572
|
+
if (tag.script !== void 0) {
|
|
573
|
+
subtags.push(tag.script.charAt(0).toUpperCase() + tag.script.slice(1));
|
|
574
|
+
}
|
|
575
|
+
if (tag.region !== void 0) {
|
|
576
|
+
subtags.push(tag.region.toUpperCase());
|
|
577
|
+
}
|
|
578
|
+
subtags.push(...tag.variants);
|
|
579
|
+
return subtags;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/locale-path/android.ts
|
|
583
|
+
var SOURCE_SEGMENT = "values";
|
|
584
|
+
var MAX_BCP47_SUBTAGS = 4;
|
|
585
|
+
var UI_MODE_LANGUAGE_COLLISION = "car";
|
|
586
|
+
function needsBcp47(tag, subtagCount) {
|
|
587
|
+
return tag.script !== void 0 || tag.variants.length > 0 || tag.region !== void 0 && NUMERIC_REGION.test(tag.region) || tag.language === UI_MODE_LANGUAGE_COLLISION || subtagCount >= 3;
|
|
588
|
+
}
|
|
589
|
+
function legacySegment(tag) {
|
|
590
|
+
return tag.region === void 0 ? `${SOURCE_SEGMENT}-${tag.language}` : `${SOURCE_SEGMENT}-${tag.language}-r${tag.region.toUpperCase()}`;
|
|
591
|
+
}
|
|
592
|
+
function androidSegment(locale, isSourceLocale) {
|
|
593
|
+
if (isSourceLocale) {
|
|
594
|
+
return SOURCE_SEGMENT;
|
|
595
|
+
}
|
|
596
|
+
const tag = parseLocaleTag(locale);
|
|
597
|
+
if (tag === void 0) {
|
|
598
|
+
return void 0;
|
|
599
|
+
}
|
|
600
|
+
const subtags = conventionalSubtags(tag);
|
|
601
|
+
if (!needsBcp47(tag, subtags.length)) {
|
|
602
|
+
return legacySegment(tag);
|
|
603
|
+
}
|
|
604
|
+
if (subtags.length > MAX_BCP47_SUBTAGS) {
|
|
605
|
+
return void 0;
|
|
606
|
+
}
|
|
607
|
+
return `${SOURCE_SEGMENT}-b+${subtags.join("+")}`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/locale-path/style.ts
|
|
611
|
+
var LOCALE_STYLES = ["literal", "posix", "android"];
|
|
612
|
+
var SEGMENT_STYLES = /* @__PURE__ */ new Set(["android"]);
|
|
613
|
+
var UNSAFE_IN_SEGMENT = /[/\\\0]/;
|
|
614
|
+
function isSegmentStyle(style) {
|
|
615
|
+
return SEGMENT_STYLES.has(style);
|
|
616
|
+
}
|
|
617
|
+
function posixSpelling(locale) {
|
|
618
|
+
const tag = parseLocaleTag(locale);
|
|
619
|
+
if (tag === void 0 || tag.script !== void 0 || tag.variants.length > 0) {
|
|
620
|
+
return void 0;
|
|
621
|
+
}
|
|
622
|
+
if (tag.region !== void 0 && NUMERIC_REGION.test(tag.region)) {
|
|
623
|
+
return void 0;
|
|
624
|
+
}
|
|
625
|
+
return locale.replaceAll("-", "_");
|
|
626
|
+
}
|
|
627
|
+
function spellLocale(locale, style, isSourceLocale) {
|
|
628
|
+
if (style === "android") {
|
|
629
|
+
return androidSegment(locale, isSourceLocale);
|
|
630
|
+
}
|
|
631
|
+
return style === "posix" ? posixSpelling(locale) : locale;
|
|
632
|
+
}
|
|
633
|
+
function isSafeSpelling(spelling) {
|
|
634
|
+
return spelling.length > 0 && !UNSAFE_IN_SEGMENT.test(spelling) && spelling !== "." && spelling !== "..";
|
|
532
635
|
}
|
|
533
636
|
function checkBatchIntegrity(inputs, extract, compare) {
|
|
534
637
|
const integrity = /* @__PURE__ */ new Map();
|
|
@@ -701,7 +804,7 @@ function deriveJsonSchema(schema) {
|
|
|
701
804
|
result[key] = value;
|
|
702
805
|
}
|
|
703
806
|
}
|
|
704
|
-
return result;
|
|
807
|
+
return { ...result, type: "object" };
|
|
705
808
|
}
|
|
706
809
|
function partitionTranslations(translations, requested) {
|
|
707
810
|
const firstSeen = /* @__PURE__ */ new Map();
|
|
@@ -821,6 +924,8 @@ function assertNotTruncated(truncated) {
|
|
|
821
924
|
var RATE_LIMITED_STATUS = 429;
|
|
822
925
|
var TIMEOUT_STATUS = 408;
|
|
823
926
|
var AUTH_FAILED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
|
|
927
|
+
var SERVER_OUTAGE_MIN_STATUS = 500;
|
|
928
|
+
var SERVER_OUTAGE_MAX_STATUS = 599;
|
|
824
929
|
var RATE_LIMITED_CLASS_NAMES = /* @__PURE__ */ new Set([
|
|
825
930
|
"RateLimitError",
|
|
826
931
|
"TooManyRequestsError"
|
|
@@ -842,6 +947,9 @@ function getErrorStatus(error) {
|
|
|
842
947
|
const status = error.status;
|
|
843
948
|
return typeof status === "number" ? status : void 0;
|
|
844
949
|
}
|
|
950
|
+
function isServerOutageStatus(status) {
|
|
951
|
+
return status !== void 0 && status >= SERVER_OUTAGE_MIN_STATUS && status <= SERVER_OUTAGE_MAX_STATUS;
|
|
952
|
+
}
|
|
845
953
|
function readClassName(error) {
|
|
846
954
|
if (typeof error !== "object" || error === null) {
|
|
847
955
|
return void 0;
|
|
@@ -859,6 +967,9 @@ function classifyProviderError(error) {
|
|
|
859
967
|
if (status === TIMEOUT_STATUS) {
|
|
860
968
|
return "TIMEOUT";
|
|
861
969
|
}
|
|
970
|
+
if (isServerOutageStatus(status)) {
|
|
971
|
+
return "PROVIDER_UNAVAILABLE";
|
|
972
|
+
}
|
|
862
973
|
const className = readClassName(error);
|
|
863
974
|
if (className !== void 0) {
|
|
864
975
|
if (RATE_LIMITED_CLASS_NAMES.has(className)) {
|
|
@@ -890,10 +1001,12 @@ var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
|
890
1001
|
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
891
1002
|
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
892
1003
|
var AUTH_FAILED_MESSAGE = "The translation provider rejected the request credentials.";
|
|
1004
|
+
var PROVIDER_UNAVAILABLE_MESSAGE = "The translation provider is currently unavailable.";
|
|
893
1005
|
var MESSAGE_BY_CODE = {
|
|
894
1006
|
RATE_LIMITED: RATE_LIMITED_MESSAGE,
|
|
895
1007
|
TIMEOUT: TIMEOUT_MESSAGE,
|
|
896
1008
|
AUTH_FAILED: AUTH_FAILED_MESSAGE,
|
|
1009
|
+
PROVIDER_UNAVAILABLE: PROVIDER_UNAVAILABLE_MESSAGE,
|
|
897
1010
|
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
898
1011
|
};
|
|
899
1012
|
async function guardProviderCall(call, signal) {
|
|
@@ -974,14 +1087,14 @@ function resolveOpenAiCompatibleKey(customEnvVar) {
|
|
|
974
1087
|
}
|
|
975
1088
|
return OPENAI_COMPATIBLE_KEY_PLACEHOLDER;
|
|
976
1089
|
}
|
|
1090
|
+
function toMutableRequest(request) {
|
|
1091
|
+
return request;
|
|
1092
|
+
}
|
|
977
1093
|
function createDefaultClient() {
|
|
978
1094
|
const sdk = new Anthropic__default.default({ apiKey: requireAnthropicKey(), logLevel: "off" });
|
|
979
1095
|
return {
|
|
980
1096
|
messages: {
|
|
981
|
-
create: async (body, options) => await sdk.messages.create(
|
|
982
|
-
body,
|
|
983
|
-
options
|
|
984
|
-
)
|
|
1097
|
+
create: async (body, options) => await sdk.messages.create(toMutableRequest(body), options)
|
|
985
1098
|
}
|
|
986
1099
|
};
|
|
987
1100
|
}
|
|
@@ -1399,7 +1512,7 @@ function createDefaultClient3() {
|
|
|
1399
1512
|
models: {
|
|
1400
1513
|
generateContent: (request) => withGeminiRetry(
|
|
1401
1514
|
async () => await ai.models.generateContent(
|
|
1402
|
-
request
|
|
1515
|
+
toMutableRequest(request)
|
|
1403
1516
|
),
|
|
1404
1517
|
request.config.abortSignal
|
|
1405
1518
|
)
|
|
@@ -1563,7 +1676,7 @@ function createDefaultClient4() {
|
|
|
1563
1676
|
chat: {
|
|
1564
1677
|
completions: {
|
|
1565
1678
|
create: async (body, options) => await sdk.chat.completions.create(
|
|
1566
|
-
body,
|
|
1679
|
+
toMutableRequest(body),
|
|
1567
1680
|
options
|
|
1568
1681
|
)
|
|
1569
1682
|
}
|
|
@@ -1764,7 +1877,7 @@ function createDefaultClient5(config) {
|
|
|
1764
1877
|
chat: {
|
|
1765
1878
|
completions: {
|
|
1766
1879
|
create: async (body, options) => await sdk.chat.completions.create(
|
|
1767
|
-
body,
|
|
1880
|
+
toMutableRequest(body),
|
|
1768
1881
|
options
|
|
1769
1882
|
)
|
|
1770
1883
|
}
|
|
@@ -1846,7 +1959,13 @@ var verbatraConfigSchema = zod.z.strictObject({
|
|
|
1846
1959
|
targetLocales: zod.z.array(zod.z.string().min(1)).min(1),
|
|
1847
1960
|
format: supportedFormatSchema,
|
|
1848
1961
|
files: zod.z.strictObject({
|
|
1849
|
-
pattern: zod.z.string().min(1)
|
|
1962
|
+
pattern: zod.z.string().min(1),
|
|
1963
|
+
/**
|
|
1964
|
+
* How the `{locale}` token in `pattern` is spelled for each locale. Absent means `"literal"`,
|
|
1965
|
+
* the configured tag verbatim, which is what every layout except Android and the POSIX
|
|
1966
|
+
* underscore layouts needs. See `LocaleStyle` for what each style produces.
|
|
1967
|
+
*/
|
|
1968
|
+
localeStyle: zod.z.enum(LOCALE_STYLES).optional()
|
|
1850
1969
|
}),
|
|
1851
1970
|
provider: providerConfigSchema,
|
|
1852
1971
|
/**
|
|
@@ -1867,8 +1986,9 @@ var verbatraConfigSchema = zod.z.strictObject({
|
|
|
1867
1986
|
* Opt-in plural-category generation, off by default. When true, and only for an i18next-JSON project
|
|
1868
1987
|
* translated by an LLM provider, verbatra synthesizes the CLDR plural forms a target language
|
|
1869
1988
|
* requires but the source does not supply (for example Polish few/many). The per-run
|
|
1870
|
-
* `generatePlurals` option on `translate` overrides this.
|
|
1871
|
-
*
|
|
1989
|
+
* `generatePlurals` option on `translate` overrides this. A plural form the target file already holds
|
|
1990
|
+
* is adopted, never generated over. Unsupported cases (DeepL, non-i18next, an unknown language) fall
|
|
1991
|
+
* back to the per-locale plural warning.
|
|
1872
1992
|
*/
|
|
1873
1993
|
generatePlurals: zod.z.boolean().optional(),
|
|
1874
1994
|
/**
|
|
@@ -2003,6 +2123,63 @@ async function loadConfig(options = {}) {
|
|
|
2003
2123
|
const { config } = await loadConfigWithMeta(options);
|
|
2004
2124
|
return config;
|
|
2005
2125
|
}
|
|
2126
|
+
var DEFAULT_LOCALE_STYLE = "literal";
|
|
2127
|
+
function validatePattern(pattern, style) {
|
|
2128
|
+
if (!pattern.includes(LOCALE_TOKEN)) {
|
|
2129
|
+
throw new SdkError(
|
|
2130
|
+
"LOCALE_LAYOUT_INVALID",
|
|
2131
|
+
`The files pattern "${pattern}" must contain the ${LOCALE_TOKEN} token.`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
if (isSegmentStyle(style) && !tokenOccupiesWholeSegments(pattern)) {
|
|
2135
|
+
throw new SdkError(
|
|
2136
|
+
"LOCALE_LAYOUT_INVALID",
|
|
2137
|
+
`The "${style}" locale style expands ${LOCALE_TOKEN} to a whole path segment, so the token must stand alone between separators, but the pattern "${pattern}" embeds it in a segment.`
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
function safeSpelling(locale, style, sourceLocale) {
|
|
2142
|
+
const spelling = spellLocale(locale, style, locale === sourceLocale);
|
|
2143
|
+
if (spelling === void 0) {
|
|
2144
|
+
throw new SdkError(
|
|
2145
|
+
"LOCALE_LAYOUT_INVALID",
|
|
2146
|
+
`The "${style}" locale style has no valid path spelling for the locale "${locale}".`
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
if (!isSafeSpelling(spelling)) {
|
|
2150
|
+
throw new SdkError(
|
|
2151
|
+
"LOCALE_LAYOUT_INVALID",
|
|
2152
|
+
`The locale "${locale}" expands to "${spelling}" under the "${style}" locale style, which is not a single path segment.`
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
return spelling;
|
|
2156
|
+
}
|
|
2157
|
+
function buildForwardMap(config, pathFor) {
|
|
2158
|
+
const forward = /* @__PURE__ */ new Map();
|
|
2159
|
+
for (const locale of [config.sourceLocale, ...config.targetLocales]) {
|
|
2160
|
+
const path = pathFor(locale);
|
|
2161
|
+
const claimed = forward.get(path);
|
|
2162
|
+
if (claimed !== void 0 && claimed !== locale) {
|
|
2163
|
+
throw new SdkError(
|
|
2164
|
+
"LOCALE_PATH_COLLISION",
|
|
2165
|
+
`The locales "${claimed}" and "${locale}" both resolve to ${path}.`
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
forward.set(path, locale);
|
|
2169
|
+
}
|
|
2170
|
+
return forward;
|
|
2171
|
+
}
|
|
2172
|
+
function createLocalePathResolver(cwd, config) {
|
|
2173
|
+
const style = config.files.localeStyle ?? DEFAULT_LOCALE_STYLE;
|
|
2174
|
+
const pattern = config.files.pattern;
|
|
2175
|
+
validatePattern(pattern, style);
|
|
2176
|
+
const pathFor = (locale) => path.resolve(cwd, expandPattern(pattern, safeSpelling(locale, style, config.sourceLocale)));
|
|
2177
|
+
const forward = buildForwardMap(config, pathFor);
|
|
2178
|
+
return {
|
|
2179
|
+
pathFor,
|
|
2180
|
+
localeFor: (absolutePath) => forward.get(path.resolve(cwd, absolutePath))
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2006
2183
|
var LOCAL_DIR_NAME = ".verbatra-local";
|
|
2007
2184
|
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
2008
2185
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
@@ -4024,7 +4201,7 @@ function selectAdapter(format, registry = createDefaultRegistry()) {
|
|
|
4024
4201
|
|
|
4025
4202
|
// src/flow/read-target.ts
|
|
4026
4203
|
async function readTargetResource(input) {
|
|
4027
|
-
const path =
|
|
4204
|
+
const path = input.resolver.pathFor(input.locale);
|
|
4028
4205
|
if (!await input.fs.fileExists(path)) {
|
|
4029
4206
|
return { locale: input.locale, namespace: "", format: input.format, entries: /* @__PURE__ */ new Map() };
|
|
4030
4207
|
}
|
|
@@ -4051,7 +4228,7 @@ function selectLocales(config, requested) {
|
|
|
4051
4228
|
|
|
4052
4229
|
// src/flow/source.ts
|
|
4053
4230
|
async function readSource(config, cwd, fs, adapter) {
|
|
4054
|
-
const sourcePath =
|
|
4231
|
+
const sourcePath = createLocalePathResolver(cwd, config).pathFor(config.sourceLocale);
|
|
4055
4232
|
if (!await fs.fileExists(sourcePath)) {
|
|
4056
4233
|
throw new SdkError(
|
|
4057
4234
|
"SOURCE_UNREADABLE",
|
|
@@ -4072,8 +4249,7 @@ async function readSource(config, cwd, fs, adapter) {
|
|
|
4072
4249
|
// src/flow/diff-locales.ts
|
|
4073
4250
|
async function readTarget(cwd, config, adapter, fs, locale) {
|
|
4074
4251
|
return readTargetResource({
|
|
4075
|
-
cwd,
|
|
4076
|
-
filesPattern: config.files.pattern,
|
|
4252
|
+
resolver: createLocalePathResolver(cwd, config),
|
|
4077
4253
|
format: config.format,
|
|
4078
4254
|
locale,
|
|
4079
4255
|
adapter,
|
|
@@ -4194,7 +4370,7 @@ async function editEntry(input, deps = {}) {
|
|
|
4194
4370
|
}
|
|
4195
4371
|
const merged = new Map(target.entries);
|
|
4196
4372
|
merged.set(input.key, { ...sourceEntry, value: input.value, namespace: target.namespace });
|
|
4197
|
-
const path =
|
|
4373
|
+
const path = createLocalePathResolver(cwd, config).pathFor(locale);
|
|
4198
4374
|
await adapter.write(
|
|
4199
4375
|
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
4200
4376
|
path
|
|
@@ -4417,7 +4593,7 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
4417
4593
|
}
|
|
4418
4594
|
const merged = new Map(target.entries);
|
|
4419
4595
|
merged.set(input.key, { ...sourceEntry, value, namespace: target.namespace });
|
|
4420
|
-
const path =
|
|
4596
|
+
const path = createLocalePathResolver(cwd, config).pathFor(locale);
|
|
4421
4597
|
await adapter.write(
|
|
4422
4598
|
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
4423
4599
|
path
|
|
@@ -4836,6 +5012,9 @@ function syntheticEntry(item) {
|
|
|
4836
5012
|
meaning: `CLDR plural category "${item.category}"`
|
|
4837
5013
|
};
|
|
4838
5014
|
}
|
|
5015
|
+
function isAdopted(item, targetKeys, baseline) {
|
|
5016
|
+
return targetKeys.has(item.targetKey) && !baseline.has(item.targetKey);
|
|
5017
|
+
}
|
|
4839
5018
|
function staleItems(items, baseline) {
|
|
4840
5019
|
return items.filter((item) => {
|
|
4841
5020
|
const hash = generatedLockHash(item.governingEntries, item.category);
|
|
@@ -4855,7 +5034,10 @@ function buildRequest2(context, entries) {
|
|
|
4855
5034
|
}
|
|
4856
5035
|
async function generatePluralForms(context) {
|
|
4857
5036
|
const plan = planPluralGeneration(context.source, context.targetLocale, context.format);
|
|
4858
|
-
const
|
|
5037
|
+
const candidates = plan.items.filter(
|
|
5038
|
+
(item) => !isAdopted(item, context.targetKeys, context.baseline)
|
|
5039
|
+
);
|
|
5040
|
+
const stale = staleItems(candidates, context.baseline);
|
|
4859
5041
|
if (stale.length === 0) {
|
|
4860
5042
|
return EMPTY_RESULT;
|
|
4861
5043
|
}
|
|
@@ -5045,8 +5227,7 @@ async function shouldWriteTarget(params, path, changed) {
|
|
|
5045
5227
|
}
|
|
5046
5228
|
async function runLocale(params) {
|
|
5047
5229
|
const target = await readTargetResource({
|
|
5048
|
-
|
|
5049
|
-
filesPattern: params.filesPattern,
|
|
5230
|
+
resolver: params.resolver,
|
|
5050
5231
|
format: params.format,
|
|
5051
5232
|
locale: params.targetLocale,
|
|
5052
5233
|
adapter: params.adapter,
|
|
@@ -5123,11 +5304,11 @@ async function runLocale(params) {
|
|
|
5123
5304
|
merged.set(key, { ...hit.source, value: hit.value, namespace: target.namespace });
|
|
5124
5305
|
}
|
|
5125
5306
|
}
|
|
5126
|
-
const generation = await runGeneration(params, provider);
|
|
5307
|
+
const generation = await runGeneration(params, provider, new Set(target.entries.keys()));
|
|
5127
5308
|
for (const form of generation.accepted) {
|
|
5128
5309
|
merged.set(form.targetKey, { ...form.entry, namespace: target.namespace });
|
|
5129
5310
|
}
|
|
5130
|
-
const path =
|
|
5311
|
+
const path = params.resolver.pathFor(params.targetLocale);
|
|
5131
5312
|
const writeNeeded = await shouldWriteTarget(params, path, {
|
|
5132
5313
|
accepted: accepted.size,
|
|
5133
5314
|
pruned: pruned.length,
|
|
@@ -5190,7 +5371,7 @@ var NO_GENERATION_RESULT = {
|
|
|
5190
5371
|
usage: void 0,
|
|
5191
5372
|
tripped: false
|
|
5192
5373
|
};
|
|
5193
|
-
async function runGeneration(params, provider) {
|
|
5374
|
+
async function runGeneration(params, provider, targetKeys) {
|
|
5194
5375
|
if (!params.generatePlurals || provider.kind !== "llm") {
|
|
5195
5376
|
return NO_GENERATION_RESULT;
|
|
5196
5377
|
}
|
|
@@ -5204,6 +5385,7 @@ async function runGeneration(params, provider) {
|
|
|
5204
5385
|
glossary: params.glossary,
|
|
5205
5386
|
tone: params.tone,
|
|
5206
5387
|
baseline: params.baseline,
|
|
5388
|
+
targetKeys,
|
|
5207
5389
|
maxBatchSize: params.maxBatchSize,
|
|
5208
5390
|
budget: params.budget
|
|
5209
5391
|
});
|
|
@@ -5446,7 +5628,7 @@ function buildLocaleRunParams(context, targetLocale, baseline) {
|
|
|
5446
5628
|
adapter: context.adapter,
|
|
5447
5629
|
provider: context.provider,
|
|
5448
5630
|
cwd: context.cwd,
|
|
5449
|
-
|
|
5631
|
+
resolver: context.resolver,
|
|
5450
5632
|
sourceLocale: context.config.sourceLocale,
|
|
5451
5633
|
targetLocale,
|
|
5452
5634
|
format: context.config.format,
|
|
@@ -5600,6 +5782,7 @@ async function translate2(input, deps = {}) {
|
|
|
5600
5782
|
config.maxTokens,
|
|
5601
5783
|
config.budgetBehavior ?? DEFAULT_BUDGET_BEHAVIOR
|
|
5602
5784
|
);
|
|
5785
|
+
const resolver = createLocalePathResolver(cwd, config);
|
|
5603
5786
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
5604
5787
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
5605
5788
|
const source = await readSource(config, cwd, fs, adapter);
|
|
@@ -5610,6 +5793,7 @@ async function translate2(input, deps = {}) {
|
|
|
5610
5793
|
provider,
|
|
5611
5794
|
cwd,
|
|
5612
5795
|
config,
|
|
5796
|
+
resolver,
|
|
5613
5797
|
prune,
|
|
5614
5798
|
generatePlurals,
|
|
5615
5799
|
maxBatchSize,
|
|
@@ -5650,6 +5834,123 @@ var ExchangeError = class extends Error {
|
|
|
5650
5834
|
this.code = code;
|
|
5651
5835
|
}
|
|
5652
5836
|
};
|
|
5837
|
+
var DELIMITER = {
|
|
5838
|
+
csv: ",",
|
|
5839
|
+
tsv: " "
|
|
5840
|
+
};
|
|
5841
|
+
var FORBIDDEN_FILE_NAME_CHARS = /[\\/:*?"<>|\p{Cc}]/u;
|
|
5842
|
+
function assertPlainFileLocale(locale) {
|
|
5843
|
+
if (locale.length === 0 || locale === "." || locale === "..") {
|
|
5844
|
+
throw new ExchangeError(
|
|
5845
|
+
"WORKBOOK_INVALID",
|
|
5846
|
+
`The locale "${locale}" cannot be an interchange file name: it must name a file, not a directory.`
|
|
5847
|
+
);
|
|
5848
|
+
}
|
|
5849
|
+
if (FORBIDDEN_FILE_NAME_CHARS.test(locale)) {
|
|
5850
|
+
throw new ExchangeError(
|
|
5851
|
+
"WORKBOOK_INVALID",
|
|
5852
|
+
`The locale "${locale}" cannot be an interchange file name: it must not contain a path separator, a control character, or any of : * ? " < > | .`
|
|
5853
|
+
);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
function delimitedFileName(locale, format) {
|
|
5857
|
+
assertPlainFileLocale(locale);
|
|
5858
|
+
return `${locale}.${format}`;
|
|
5859
|
+
}
|
|
5860
|
+
var COLUMN = {
|
|
5861
|
+
key: 1,
|
|
5862
|
+
source: 2,
|
|
5863
|
+
current: 3,
|
|
5864
|
+
status: 4,
|
|
5865
|
+
translation: 5,
|
|
5866
|
+
sourceHash: 6,
|
|
5867
|
+
context: 7,
|
|
5868
|
+
reviewStatus: 8,
|
|
5869
|
+
reviewReasons: 9
|
|
5870
|
+
};
|
|
5871
|
+
var HEADERS = [
|
|
5872
|
+
"Key",
|
|
5873
|
+
"Source",
|
|
5874
|
+
"Current translation",
|
|
5875
|
+
"Status",
|
|
5876
|
+
"Translation",
|
|
5877
|
+
"Source hash",
|
|
5878
|
+
"Context",
|
|
5879
|
+
"Review status",
|
|
5880
|
+
"Review reasons"
|
|
5881
|
+
];
|
|
5882
|
+
var HEADER_ROW = 1;
|
|
5883
|
+
var INSTRUCTIONS_SHEET_NAME = "Instructions";
|
|
5884
|
+
var rowSchema = zod.z.object({
|
|
5885
|
+
key: zod.z.string().min(1),
|
|
5886
|
+
source: zod.z.string(),
|
|
5887
|
+
currentTarget: zod.z.string(),
|
|
5888
|
+
status: zod.z.enum(["new", "changed", "unchanged"]),
|
|
5889
|
+
sourceHash: zod.z.string(),
|
|
5890
|
+
translation: zod.z.string(),
|
|
5891
|
+
context: zod.z.string(),
|
|
5892
|
+
reviewStatus: zod.z.enum(["ok", "review"]).catch("ok"),
|
|
5893
|
+
reviewReasons: zod.z.string().catch("")
|
|
5894
|
+
});
|
|
5895
|
+
var MALFORMED_ROW_COLUMN = "Status";
|
|
5896
|
+
function cellAt(cells, column) {
|
|
5897
|
+
return cells[column - 1] ?? "";
|
|
5898
|
+
}
|
|
5899
|
+
function parseRowCells(cells) {
|
|
5900
|
+
const result = rowSchema.safeParse({
|
|
5901
|
+
key: cellAt(cells, COLUMN.key),
|
|
5902
|
+
source: cellAt(cells, COLUMN.source),
|
|
5903
|
+
currentTarget: cellAt(cells, COLUMN.current),
|
|
5904
|
+
status: cellAt(cells, COLUMN.status),
|
|
5905
|
+
sourceHash: cellAt(cells, COLUMN.sourceHash),
|
|
5906
|
+
translation: cellAt(cells, COLUMN.translation).trim(),
|
|
5907
|
+
context: cellAt(cells, COLUMN.context),
|
|
5908
|
+
reviewStatus: cellAt(cells, COLUMN.reviewStatus),
|
|
5909
|
+
reviewReasons: cellAt(cells, COLUMN.reviewReasons)
|
|
5910
|
+
});
|
|
5911
|
+
if (!result.success) {
|
|
5912
|
+
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
5913
|
+
}
|
|
5914
|
+
return { ok: true, row: result.data };
|
|
5915
|
+
}
|
|
5916
|
+
function rowCells(row) {
|
|
5917
|
+
const cells = new Array(HEADERS.length).fill("");
|
|
5918
|
+
cells[COLUMN.key - 1] = row.key;
|
|
5919
|
+
cells[COLUMN.source - 1] = row.source;
|
|
5920
|
+
cells[COLUMN.current - 1] = row.currentTarget;
|
|
5921
|
+
cells[COLUMN.status - 1] = row.status;
|
|
5922
|
+
cells[COLUMN.translation - 1] = row.translation;
|
|
5923
|
+
cells[COLUMN.sourceHash - 1] = row.sourceHash;
|
|
5924
|
+
cells[COLUMN.context - 1] = row.context;
|
|
5925
|
+
cells[COLUMN.reviewStatus - 1] = row.reviewStatus;
|
|
5926
|
+
cells[COLUMN.reviewReasons - 1] = row.reviewReasons;
|
|
5927
|
+
return cells;
|
|
5928
|
+
}
|
|
5929
|
+
var QUOTE = '"';
|
|
5930
|
+
var ESCAPED_QUOTE = '""';
|
|
5931
|
+
var LINE_BREAK = "\n";
|
|
5932
|
+
var UTF8_BOM = "\uFEFF";
|
|
5933
|
+
function needsQuoting(value, delimiter) {
|
|
5934
|
+
return value.includes(delimiter) || value.includes(QUOTE) || value.includes("\n") || value.includes("\r") || value !== value.trim();
|
|
5935
|
+
}
|
|
5936
|
+
function encodeField(value, delimiter) {
|
|
5937
|
+
if (!needsQuoting(value, delimiter)) {
|
|
5938
|
+
return value;
|
|
5939
|
+
}
|
|
5940
|
+
return `${QUOTE}${value.replaceAll(QUOTE, ESCAPED_QUOTE)}${QUOTE}`;
|
|
5941
|
+
}
|
|
5942
|
+
function encodeRecord(fields, delimiter) {
|
|
5943
|
+
return fields.map((field) => encodeField(field, delimiter)).join(delimiter);
|
|
5944
|
+
}
|
|
5945
|
+
function buildDelimited(sheet, format) {
|
|
5946
|
+
const delimiter = DELIMITER[format];
|
|
5947
|
+
const records = [encodeRecord(HEADERS, delimiter)];
|
|
5948
|
+
for (const row of sheet.rows) {
|
|
5949
|
+
records.push(encodeRecord(rowCells(row), delimiter));
|
|
5950
|
+
}
|
|
5951
|
+
const text = `${records.join(LINE_BREAK)}${LINE_BREAK}`;
|
|
5952
|
+
return format === "csv" ? `${UTF8_BOM}${text}` : text;
|
|
5953
|
+
}
|
|
5653
5954
|
var INSTRUCTIONS_LINES = [
|
|
5654
5955
|
"How to use this workbook",
|
|
5655
5956
|
"",
|
|
@@ -5678,30 +5979,6 @@ var INSTRUCTIONS_LINES = [
|
|
|
5678
5979
|
"a value with a broken placeholder, invalid ICU, or a source that changed since export is",
|
|
5679
5980
|
"withheld (not written) and reported so you can fix and resubmit it."
|
|
5680
5981
|
];
|
|
5681
|
-
var COLUMN = {
|
|
5682
|
-
key: 1,
|
|
5683
|
-
source: 2,
|
|
5684
|
-
current: 3,
|
|
5685
|
-
status: 4,
|
|
5686
|
-
translation: 5,
|
|
5687
|
-
sourceHash: 6,
|
|
5688
|
-
context: 7,
|
|
5689
|
-
reviewStatus: 8,
|
|
5690
|
-
reviewReasons: 9
|
|
5691
|
-
};
|
|
5692
|
-
var HEADERS = [
|
|
5693
|
-
"Key",
|
|
5694
|
-
"Source",
|
|
5695
|
-
"Current translation",
|
|
5696
|
-
"Status",
|
|
5697
|
-
"Translation",
|
|
5698
|
-
"Source hash",
|
|
5699
|
-
"Context",
|
|
5700
|
-
"Review status",
|
|
5701
|
-
"Review reasons"
|
|
5702
|
-
];
|
|
5703
|
-
var HEADER_ROW = 1;
|
|
5704
|
-
var INSTRUCTIONS_SHEET_NAME = "Instructions";
|
|
5705
5982
|
var READ_ONLY_FILL = {
|
|
5706
5983
|
type: "pattern",
|
|
5707
5984
|
pattern: "solid",
|
|
@@ -5861,6 +6138,12 @@ async function buildWorkbook(model) {
|
|
|
5861
6138
|
const protectedBytes = await protectWorkbookStructure(serialized);
|
|
5862
6139
|
return Uint8Array.prototype.slice.call(protectedBytes);
|
|
5863
6140
|
}
|
|
6141
|
+
var DEFAULT_DELIMITED_LIMITS = {
|
|
6142
|
+
maxInputBytes: 32 * 1024 * 1024,
|
|
6143
|
+
maxRowsPerFile: 1e5,
|
|
6144
|
+
maxFieldsPerRow: 64,
|
|
6145
|
+
maxFieldLength: 1024 * 1024
|
|
6146
|
+
};
|
|
5864
6147
|
var DEFAULT_WORKBOOK_LIMITS = {
|
|
5865
6148
|
maxDecompressedBytes: 64 * 1024 * 1024,
|
|
5866
6149
|
maxEntryCount: 1024,
|
|
@@ -5868,6 +6151,205 @@ var DEFAULT_WORKBOOK_LIMITS = {
|
|
|
5868
6151
|
maxRowsPerSheet: 1e5,
|
|
5869
6152
|
maxCellsPerRow: 64
|
|
5870
6153
|
};
|
|
6154
|
+
var QUOTE2 = '"';
|
|
6155
|
+
var HEADER_RECORD = 1;
|
|
6156
|
+
function scanQuotedField(text, start) {
|
|
6157
|
+
const parts = [];
|
|
6158
|
+
let cursor = start;
|
|
6159
|
+
for (; ; ) {
|
|
6160
|
+
const quote = text.indexOf(QUOTE2, cursor);
|
|
6161
|
+
if (quote === -1) {
|
|
6162
|
+
parts.push(text.slice(cursor));
|
|
6163
|
+
return { value: parts.join(""), next: text.length };
|
|
6164
|
+
}
|
|
6165
|
+
parts.push(text.slice(cursor, quote));
|
|
6166
|
+
if (text[quote + 1] !== QUOTE2) {
|
|
6167
|
+
return { value: parts.join(""), next: quote + 1 };
|
|
6168
|
+
}
|
|
6169
|
+
parts.push(QUOTE2);
|
|
6170
|
+
cursor = quote + 2;
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
function scanPlainField(text, start, delimiter) {
|
|
6174
|
+
let cursor = start;
|
|
6175
|
+
while (cursor < text.length) {
|
|
6176
|
+
const char = text[cursor];
|
|
6177
|
+
if (char === delimiter || char === "\n" || char === "\r") {
|
|
6178
|
+
break;
|
|
6179
|
+
}
|
|
6180
|
+
cursor += 1;
|
|
6181
|
+
}
|
|
6182
|
+
return { value: text.slice(start, cursor), next: cursor };
|
|
6183
|
+
}
|
|
6184
|
+
function scanField(text, start, delimiter) {
|
|
6185
|
+
if (text[start] !== QUOTE2) {
|
|
6186
|
+
return scanPlainField(text, start, delimiter);
|
|
6187
|
+
}
|
|
6188
|
+
const quoted = scanQuotedField(text, start + 1);
|
|
6189
|
+
const trailing = scanPlainField(text, quoted.next, delimiter);
|
|
6190
|
+
return { value: `${quoted.value}${trailing.value}`, next: trailing.next };
|
|
6191
|
+
}
|
|
6192
|
+
function countLineBreaks(value) {
|
|
6193
|
+
let breaks = 0;
|
|
6194
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
6195
|
+
const char = value[index];
|
|
6196
|
+
if (char === "\n") {
|
|
6197
|
+
breaks += 1;
|
|
6198
|
+
} else if (char === "\r") {
|
|
6199
|
+
breaks += 1;
|
|
6200
|
+
if (value[index + 1] === "\n") {
|
|
6201
|
+
index += 1;
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
return breaks;
|
|
6206
|
+
}
|
|
6207
|
+
function scanRecord(text, start, delimiter, limits) {
|
|
6208
|
+
const fields = [];
|
|
6209
|
+
let breaks = 0;
|
|
6210
|
+
let cursor = start;
|
|
6211
|
+
for (; ; ) {
|
|
6212
|
+
const field = scanField(text, cursor, delimiter);
|
|
6213
|
+
assertFieldLength(field.value, limits);
|
|
6214
|
+
fields.push(field.value);
|
|
6215
|
+
breaks += countLineBreaks(field.value);
|
|
6216
|
+
assertFieldCount(fields.length, limits);
|
|
6217
|
+
cursor = field.next;
|
|
6218
|
+
if (text[cursor] !== delimiter) {
|
|
6219
|
+
return { fields, breaks, next: cursor };
|
|
6220
|
+
}
|
|
6221
|
+
cursor += 1;
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
function consumeLineBreak(text, cursor) {
|
|
6225
|
+
if (text[cursor] === "\r") {
|
|
6226
|
+
return text[cursor + 1] === "\n" ? cursor + 2 : cursor + 1;
|
|
6227
|
+
}
|
|
6228
|
+
return text[cursor] === "\n" ? cursor + 1 : cursor;
|
|
6229
|
+
}
|
|
6230
|
+
function scanRecords(text, delimiter, limits) {
|
|
6231
|
+
const records = [];
|
|
6232
|
+
let cursor = 0;
|
|
6233
|
+
let line = 1;
|
|
6234
|
+
while (cursor < text.length) {
|
|
6235
|
+
const record = scanRecord(text, cursor, delimiter, limits);
|
|
6236
|
+
records.push({ fields: record.fields, line });
|
|
6237
|
+
assertRecordCount(records.length, limits);
|
|
6238
|
+
const next = consumeLineBreak(text, record.next);
|
|
6239
|
+
if (next === record.next) {
|
|
6240
|
+
break;
|
|
6241
|
+
}
|
|
6242
|
+
line += record.breaks + 1;
|
|
6243
|
+
cursor = next;
|
|
6244
|
+
}
|
|
6245
|
+
return records;
|
|
6246
|
+
}
|
|
6247
|
+
function assertInputBytes(text, limits) {
|
|
6248
|
+
if (Buffer.byteLength(text, "utf8") > limits.maxInputBytes) {
|
|
6249
|
+
throw new ExchangeError(
|
|
6250
|
+
"WORKBOOK_INVALID",
|
|
6251
|
+
`The interchange file is larger than the maximum of ${limits.maxInputBytes} bytes.`
|
|
6252
|
+
);
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
function assertFieldLength(value, limits) {
|
|
6256
|
+
if (value.length > limits.maxFieldLength) {
|
|
6257
|
+
throw new ExchangeError(
|
|
6258
|
+
"WORKBOOK_INVALID",
|
|
6259
|
+
`The interchange file has a field longer than the maximum of ${limits.maxFieldLength} characters.`
|
|
6260
|
+
);
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
function assertFieldCount(count, limits) {
|
|
6264
|
+
if (count > limits.maxFieldsPerRow) {
|
|
6265
|
+
throw new ExchangeError(
|
|
6266
|
+
"WORKBOOK_INVALID",
|
|
6267
|
+
`The interchange file has a row with more than the maximum of ${limits.maxFieldsPerRow} fields.`
|
|
6268
|
+
);
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
function assertRecordCount(count, limits) {
|
|
6272
|
+
if (count - HEADER_RECORD > limits.maxRowsPerFile) {
|
|
6273
|
+
throw new ExchangeError(
|
|
6274
|
+
"WORKBOOK_INVALID",
|
|
6275
|
+
`The interchange file has more than the maximum of ${limits.maxRowsPerFile} rows.`
|
|
6276
|
+
);
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
function assertHeaderRecord(records) {
|
|
6280
|
+
const header = records[0]?.fields;
|
|
6281
|
+
if (header === void 0 || header.length !== HEADERS.length) {
|
|
6282
|
+
throw new ExchangeError(
|
|
6283
|
+
"WORKBOOK_INVALID",
|
|
6284
|
+
`The interchange file does not start with the expected header line of ${HEADERS.length} columns.`
|
|
6285
|
+
);
|
|
6286
|
+
}
|
|
6287
|
+
for (const [index, label] of HEADERS.entries()) {
|
|
6288
|
+
if (header[index] !== label) {
|
|
6289
|
+
throw new ExchangeError(
|
|
6290
|
+
"WORKBOOK_INVALID",
|
|
6291
|
+
`The interchange file's header line does not match the expected columns (column ${index + 1} should be "${label}").`
|
|
6292
|
+
);
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
function fieldCountColumn(fieldCount) {
|
|
6297
|
+
const label = HEADERS.at(Math.min(fieldCount, HEADERS.length - 1));
|
|
6298
|
+
if (label === void 0) {
|
|
6299
|
+
return MALFORMED_ROW_COLUMN;
|
|
6300
|
+
}
|
|
6301
|
+
return label;
|
|
6302
|
+
}
|
|
6303
|
+
function isBlankRecord(fields) {
|
|
6304
|
+
return fields.length === 1 && fields[0] === "";
|
|
6305
|
+
}
|
|
6306
|
+
function readRecord(fields, locale, at, into) {
|
|
6307
|
+
const { row, line } = at;
|
|
6308
|
+
if (isBlankRecord(fields)) {
|
|
6309
|
+
return;
|
|
6310
|
+
}
|
|
6311
|
+
if (fields.length !== HEADERS.length) {
|
|
6312
|
+
into.malformed.push({ locale, row, line, column: fieldCountColumn(fields.length) });
|
|
6313
|
+
return;
|
|
6314
|
+
}
|
|
6315
|
+
if (fields[COLUMN.key - 1] === "") {
|
|
6316
|
+
return;
|
|
6317
|
+
}
|
|
6318
|
+
const outcome = parseRowCells(fields);
|
|
6319
|
+
if (!outcome.ok) {
|
|
6320
|
+
into.malformed.push({ locale, row, line, column: outcome.column });
|
|
6321
|
+
return;
|
|
6322
|
+
}
|
|
6323
|
+
if (into.seenKeys.has(outcome.row.key)) {
|
|
6324
|
+
into.duplicates.push({ locale, key: outcome.row.key, row, line });
|
|
6325
|
+
return;
|
|
6326
|
+
}
|
|
6327
|
+
into.seenKeys.add(outcome.row.key);
|
|
6328
|
+
into.rows.push(outcome.row);
|
|
6329
|
+
}
|
|
6330
|
+
function readDelimited(input, options = {}) {
|
|
6331
|
+
const limits = options.limits ?? DEFAULT_DELIMITED_LIMITS;
|
|
6332
|
+
assertInputBytes(input.text, limits);
|
|
6333
|
+
const text = input.text.startsWith(UTF8_BOM) ? input.text.slice(UTF8_BOM.length) : input.text;
|
|
6334
|
+
const records = scanRecords(text, DELIMITER[input.format], limits);
|
|
6335
|
+
assertHeaderRecord(records);
|
|
6336
|
+
const into = {
|
|
6337
|
+
rows: [],
|
|
6338
|
+
malformed: [],
|
|
6339
|
+
duplicates: [],
|
|
6340
|
+
seenKeys: /* @__PURE__ */ new Set()
|
|
6341
|
+
};
|
|
6342
|
+
for (const [index, record] of records.entries()) {
|
|
6343
|
+
if (index >= HEADER_RECORD) {
|
|
6344
|
+
readRecord(record.fields, input.locale, { row: index + 1, line: record.line }, into);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
return {
|
|
6348
|
+
sheets: [{ locale: input.locale, rows: into.rows }],
|
|
6349
|
+
malformedRows: into.malformed,
|
|
6350
|
+
duplicateKeys: into.duplicates
|
|
6351
|
+
};
|
|
6352
|
+
}
|
|
5871
6353
|
function assertNoDoctype2(name, xml) {
|
|
5872
6354
|
if (/<!DOCTYPE/i.test(xml) || /<!ENTITY/i.test(xml)) {
|
|
5873
6355
|
throw new ExchangeError(
|
|
@@ -5944,17 +6426,6 @@ async function guardWorkbookBytes(bytes, limits) {
|
|
|
5944
6426
|
assertNoDoctype2(file.name, content);
|
|
5945
6427
|
}
|
|
5946
6428
|
}
|
|
5947
|
-
var rowSchema = zod.z.object({
|
|
5948
|
-
key: zod.z.string().min(1),
|
|
5949
|
-
source: zod.z.string(),
|
|
5950
|
-
currentTarget: zod.z.string(),
|
|
5951
|
-
status: zod.z.enum(["new", "changed", "unchanged"]),
|
|
5952
|
-
sourceHash: zod.z.string(),
|
|
5953
|
-
translation: zod.z.string(),
|
|
5954
|
-
context: zod.z.string(),
|
|
5955
|
-
reviewStatus: zod.z.enum(["ok", "review"]).catch("ok"),
|
|
5956
|
-
reviewReasons: zod.z.string().catch("")
|
|
5957
|
-
});
|
|
5958
6429
|
function cellString(cell) {
|
|
5959
6430
|
const value = cell.value;
|
|
5960
6431
|
if (value === null || value === void 0) {
|
|
@@ -5979,24 +6450,8 @@ function assertHeader(sheet) {
|
|
|
5979
6450
|
);
|
|
5980
6451
|
}
|
|
5981
6452
|
}
|
|
5982
|
-
var MALFORMED_ROW_COLUMN = "Status";
|
|
5983
6453
|
function parseRow(row) {
|
|
5984
|
-
|
|
5985
|
-
key: cellString(row.getCell(COLUMN.key)),
|
|
5986
|
-
source: cellString(row.getCell(COLUMN.source)),
|
|
5987
|
-
currentTarget: cellString(row.getCell(COLUMN.current)),
|
|
5988
|
-
status: cellString(row.getCell(COLUMN.status)),
|
|
5989
|
-
sourceHash: cellString(row.getCell(COLUMN.sourceHash)),
|
|
5990
|
-
translation: cellString(row.getCell(COLUMN.translation)).trim(),
|
|
5991
|
-
context: cellString(row.getCell(COLUMN.context)),
|
|
5992
|
-
reviewStatus: cellString(row.getCell(COLUMN.reviewStatus)),
|
|
5993
|
-
reviewReasons: cellString(row.getCell(COLUMN.reviewReasons))
|
|
5994
|
-
};
|
|
5995
|
-
const result = rowSchema.safeParse(candidate);
|
|
5996
|
-
if (!result.success) {
|
|
5997
|
-
return { ok: false, column: MALFORMED_ROW_COLUMN };
|
|
5998
|
-
}
|
|
5999
|
-
return { ok: true, row: result.data };
|
|
6454
|
+
return parseRowCells(HEADERS.map((_, index) => cellString(row.getCell(index + 1))));
|
|
6000
6455
|
}
|
|
6001
6456
|
function readDataSheet(sheet, limits) {
|
|
6002
6457
|
assertHeader(sheet);
|
|
@@ -6070,8 +6525,52 @@ async function readWorkbook(bytes, options = {}) {
|
|
|
6070
6525
|
return { sheets, malformedRows, duplicateKeys };
|
|
6071
6526
|
}
|
|
6072
6527
|
|
|
6528
|
+
// src/flow/workbook/exchange-format.ts
|
|
6529
|
+
function isDelimitedFormat(format) {
|
|
6530
|
+
return format === "csv" || format === "tsv";
|
|
6531
|
+
}
|
|
6532
|
+
var MANIFEST_VERSION = 1;
|
|
6533
|
+
var MAX_MANIFEST_BYTES = 1024 * 1024;
|
|
6534
|
+
function exportManifestFileName(format) {
|
|
6535
|
+
return `.verbatra-export-${format}.json`;
|
|
6536
|
+
}
|
|
6537
|
+
var manifestSchema = zod.z.object({
|
|
6538
|
+
version: zod.z.number().int().positive(),
|
|
6539
|
+
format: zod.z.enum(["csv", "tsv"]),
|
|
6540
|
+
locales: zod.z.array(zod.z.string())
|
|
6541
|
+
});
|
|
6542
|
+
async function writeExportManifest(fs, directory, format, locales) {
|
|
6543
|
+
const manifest = { version: MANIFEST_VERSION, format, locales: [...locales] };
|
|
6544
|
+
await fs.writeFile(
|
|
6545
|
+
path.join(directory, exportManifestFileName(format)),
|
|
6546
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
6547
|
+
`
|
|
6548
|
+
);
|
|
6549
|
+
}
|
|
6550
|
+
async function readExportedLocales(fs, directory, format) {
|
|
6551
|
+
const read = await fs.readFileBounded(
|
|
6552
|
+
path.join(directory, exportManifestFileName(format)),
|
|
6553
|
+
MAX_MANIFEST_BYTES
|
|
6554
|
+
);
|
|
6555
|
+
if (read.kind !== "ok") {
|
|
6556
|
+
return void 0;
|
|
6557
|
+
}
|
|
6558
|
+
let parsed;
|
|
6559
|
+
try {
|
|
6560
|
+
parsed = JSON.parse(read.content);
|
|
6561
|
+
} catch {
|
|
6562
|
+
return void 0;
|
|
6563
|
+
}
|
|
6564
|
+
const result = manifestSchema.safeParse(parsed);
|
|
6565
|
+
if (!result.success || result.data.version !== MANIFEST_VERSION || result.data.format !== format) {
|
|
6566
|
+
return void 0;
|
|
6567
|
+
}
|
|
6568
|
+
return new Set(result.data.locales);
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6073
6571
|
// src/flow/workbook/export-workbook.ts
|
|
6074
6572
|
var DEFAULT_WORKBOOK_PATH = "verbatra-translations.xlsx";
|
|
6573
|
+
var DEFAULT_DELIMITED_PATH = "verbatra-translations";
|
|
6075
6574
|
function reasonLabel(reason) {
|
|
6076
6575
|
return reason.toLowerCase().replace(/_/g, "-");
|
|
6077
6576
|
}
|
|
@@ -6135,6 +6634,21 @@ function buildRows(source, target, baseline, includeUnchanged, adapter, glossary
|
|
|
6135
6634
|
}
|
|
6136
6635
|
return [...rows].sort((a, b) => a.key < b.key ? -1 : 1);
|
|
6137
6636
|
}
|
|
6637
|
+
async function writeDelimitedFiles(fs, directory, format, sheets) {
|
|
6638
|
+
await fs.mkdir?.(directory);
|
|
6639
|
+
for (const sheet of sheets) {
|
|
6640
|
+
await fs.writeFile(
|
|
6641
|
+
path.join(directory, delimitedFileName(sheet.locale, format)),
|
|
6642
|
+
buildDelimited(sheet, format)
|
|
6643
|
+
);
|
|
6644
|
+
}
|
|
6645
|
+
await writeExportManifest(
|
|
6646
|
+
fs,
|
|
6647
|
+
directory,
|
|
6648
|
+
format,
|
|
6649
|
+
sheets.map((sheet) => sheet.locale)
|
|
6650
|
+
);
|
|
6651
|
+
}
|
|
6138
6652
|
async function exportWorkbook(input, deps = {}) {
|
|
6139
6653
|
const config = input.config;
|
|
6140
6654
|
const cwd = input.cwd ?? process.cwd();
|
|
@@ -6157,10 +6671,17 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
6157
6671
|
return { locale, rows };
|
|
6158
6672
|
})
|
|
6159
6673
|
);
|
|
6160
|
-
const
|
|
6161
|
-
const
|
|
6162
|
-
|
|
6163
|
-
|
|
6674
|
+
const format = input.format ?? "xlsx";
|
|
6675
|
+
const path$1 = path.resolve(
|
|
6676
|
+
cwd,
|
|
6677
|
+
input.out ?? (isDelimitedFormat(format) ? DEFAULT_DELIMITED_PATH : DEFAULT_WORKBOOK_PATH)
|
|
6678
|
+
);
|
|
6679
|
+
if (isDelimitedFormat(format)) {
|
|
6680
|
+
await writeDelimitedFiles(fs, path$1, format, sheets);
|
|
6681
|
+
} else {
|
|
6682
|
+
const model = { sheets };
|
|
6683
|
+
await fs.writeBytes(path$1, await buildWorkbook(model));
|
|
6684
|
+
}
|
|
6164
6685
|
return {
|
|
6165
6686
|
path: path$1,
|
|
6166
6687
|
locales: sheets.map((sheet) => ({ locale: sheet.locale, rows: sheet.rows.length }))
|
|
@@ -6289,6 +6810,7 @@ function importLocale(params) {
|
|
|
6289
6810
|
|
|
6290
6811
|
// src/flow/workbook/import-workbook.ts
|
|
6291
6812
|
var MAX_WORKBOOK_FILE_BYTES = 64 * 1024 * 1024;
|
|
6813
|
+
var MAX_DELIMITED_FILE_BYTES = 32 * 1024 * 1024;
|
|
6292
6814
|
async function readWorkbookBytes(path, fs) {
|
|
6293
6815
|
const read = await fs.readBytesBounded(path, MAX_WORKBOOK_FILE_BYTES);
|
|
6294
6816
|
if (read.kind === "missing") {
|
|
@@ -6302,6 +6824,75 @@ async function readWorkbookBytes(path, fs) {
|
|
|
6302
6824
|
}
|
|
6303
6825
|
return read.bytes;
|
|
6304
6826
|
}
|
|
6827
|
+
async function readDelimitedText(path, fs) {
|
|
6828
|
+
const read = await fs.readFileBounded(path, MAX_DELIMITED_FILE_BYTES);
|
|
6829
|
+
if (read.kind === "missing") {
|
|
6830
|
+
return void 0;
|
|
6831
|
+
}
|
|
6832
|
+
if (read.kind === "too-large") {
|
|
6833
|
+
throw new SdkError(
|
|
6834
|
+
"SOURCE_INVALID",
|
|
6835
|
+
`The interchange file at ${path} exceeds the maximum allowed size of ${MAX_DELIMITED_FILE_BYTES} bytes.`
|
|
6836
|
+
);
|
|
6837
|
+
}
|
|
6838
|
+
return read.content;
|
|
6839
|
+
}
|
|
6840
|
+
async function collectDelimitedSources(path$1, config, fs, format) {
|
|
6841
|
+
const single = await readDelimitedText(path$1, fs);
|
|
6842
|
+
if (single !== void 0) {
|
|
6843
|
+
return { sources: [{ locale: path.basename(path$1, `.${format}`), text: single }], staleLocales: [] };
|
|
6844
|
+
}
|
|
6845
|
+
const exported = await readExportedLocales(fs, path$1, format);
|
|
6846
|
+
const sources = [];
|
|
6847
|
+
const staleLocales = [];
|
|
6848
|
+
for (const locale of config.targetLocales) {
|
|
6849
|
+
const text = await readDelimitedText(path.join(path$1, delimitedFileName(locale, format)), fs);
|
|
6850
|
+
if (text === void 0) {
|
|
6851
|
+
continue;
|
|
6852
|
+
}
|
|
6853
|
+
if (exported !== void 0 && !exported.has(locale)) {
|
|
6854
|
+
staleLocales.push(locale);
|
|
6855
|
+
continue;
|
|
6856
|
+
}
|
|
6857
|
+
sources.push({ locale, text });
|
|
6858
|
+
}
|
|
6859
|
+
if (sources.length === 0 && staleLocales.length === 0) {
|
|
6860
|
+
throw new SdkError(
|
|
6861
|
+
"SOURCE_UNREADABLE",
|
|
6862
|
+
`No ${format} file was found at ${path$1}, and it holds no <locale>.${format} file for any configured target locale.`
|
|
6863
|
+
);
|
|
6864
|
+
}
|
|
6865
|
+
return { sources, staleLocales };
|
|
6866
|
+
}
|
|
6867
|
+
function parseDelimitedSources(sources, format) {
|
|
6868
|
+
const sheets = [];
|
|
6869
|
+
const malformedRows = [];
|
|
6870
|
+
const duplicateKeys = [];
|
|
6871
|
+
for (const source of sources) {
|
|
6872
|
+
const data = readDelimited({ text: source.text, locale: source.locale, format });
|
|
6873
|
+
sheets.push(...data.sheets);
|
|
6874
|
+
malformedRows.push(...data.malformedRows);
|
|
6875
|
+
duplicateKeys.push(...data.duplicateKeys);
|
|
6876
|
+
}
|
|
6877
|
+
return { sheets, malformedRows, duplicateKeys };
|
|
6878
|
+
}
|
|
6879
|
+
async function readImportData(path, config, fs, format) {
|
|
6880
|
+
try {
|
|
6881
|
+
if (isDelimitedFormat(format)) {
|
|
6882
|
+
const handoff = await collectDelimitedSources(path, config, fs, format);
|
|
6883
|
+
return {
|
|
6884
|
+
data: parseDelimitedSources(handoff.sources, format),
|
|
6885
|
+
staleLocales: handoff.staleLocales
|
|
6886
|
+
};
|
|
6887
|
+
}
|
|
6888
|
+
return { data: await readWorkbook(await readWorkbookBytes(path, fs)), staleLocales: [] };
|
|
6889
|
+
} catch (error) {
|
|
6890
|
+
if (error instanceof SdkError) {
|
|
6891
|
+
throw error;
|
|
6892
|
+
}
|
|
6893
|
+
throw new SdkError("SOURCE_INVALID", errorMessage(error));
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6305
6896
|
function mergeAccepted(target, accepted) {
|
|
6306
6897
|
const merged = new Map(target.entries);
|
|
6307
6898
|
for (const [key, { value, source }] of accepted) {
|
|
@@ -6342,18 +6933,43 @@ function computeLockEntries2(source, merged, baseline, accepted) {
|
|
|
6342
6933
|
}
|
|
6343
6934
|
var MissingSheetError = class extends Error {
|
|
6344
6935
|
code = "WORKBOOK_SHEET_MISSING";
|
|
6345
|
-
constructor(locale) {
|
|
6936
|
+
constructor(locale, format) {
|
|
6346
6937
|
super(
|
|
6347
|
-
`The workbook has no sheet (tab) for the configured target locale "${locale}". The tab may have been renamed, deleted, or reordered out of the workbook.`
|
|
6938
|
+
isDelimitedFormat(format) ? `The handoff has no "${delimitedFileName(locale, format)}" file for the configured target locale "${locale}". The file may have been renamed, deleted, or left out of the directory.` : `The workbook has no sheet (tab) for the configured target locale "${locale}". The tab may have been renamed, deleted, or reordered out of the workbook.`
|
|
6348
6939
|
);
|
|
6349
6940
|
this.name = "MissingSheetError";
|
|
6350
6941
|
}
|
|
6351
6942
|
};
|
|
6943
|
+
var StaleHandoffFileError = class extends Error {
|
|
6944
|
+
code = "HANDOFF_FILE_STALE";
|
|
6945
|
+
constructor(locale, format) {
|
|
6946
|
+
super(
|
|
6947
|
+
`The file "${delimitedFileName(locale, format)}" is left over from an earlier export that included the target locale "${locale}"; the most recent export into this directory did not. Its rows were not applied, because they reflect that earlier run. Re-export the locale to refresh the file, or delete it.`
|
|
6948
|
+
);
|
|
6949
|
+
this.name = "StaleHandoffFileError";
|
|
6950
|
+
}
|
|
6951
|
+
};
|
|
6952
|
+
function absentLocaleFailures(config, sheets, format, staleLocales) {
|
|
6953
|
+
const present = new Set(sheets.map((sheet) => sheet.locale));
|
|
6954
|
+
const stale = new Set(staleLocales);
|
|
6955
|
+
const failures = [];
|
|
6956
|
+
for (const locale of config.targetLocales) {
|
|
6957
|
+
if (present.has(locale)) {
|
|
6958
|
+
continue;
|
|
6959
|
+
}
|
|
6960
|
+
const error = stale.has(locale) && isDelimitedFormat(format) ? new StaleHandoffFileError(locale, format) : new MissingSheetError(locale, format);
|
|
6961
|
+
failures.push(failureSummary(locale, error));
|
|
6962
|
+
}
|
|
6963
|
+
return failures;
|
|
6964
|
+
}
|
|
6965
|
+
function lineOf(reported) {
|
|
6966
|
+
return reported.line === void 0 ? {} : { line: reported.line };
|
|
6967
|
+
}
|
|
6352
6968
|
async function runSheet(ctx, sheet, lock) {
|
|
6353
6969
|
if (!ctx.config.targetLocales.includes(sheet.locale)) {
|
|
6354
6970
|
throw new SdkError(
|
|
6355
6971
|
"CONFIG_INVALID",
|
|
6356
|
-
`The workbook has a sheet named "${sheet.locale}", which is not a configured target locale. It may be a renamed, added, or reordered tab; leave every language tab named exactly as exported.`
|
|
6972
|
+
isDelimitedFormat(ctx.format) ? `The handoff has a file named "${sheet.locale}.${ctx.format}", whose locale is not a configured target locale. Name every interchange file exactly as it was exported.` : `The workbook has a sheet named "${sheet.locale}", which is not a configured target locale. It may be a renamed, added, or reordered tab; leave every language tab named exactly as exported.`
|
|
6357
6973
|
);
|
|
6358
6974
|
}
|
|
6359
6975
|
const target = await readTarget(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
@@ -6365,15 +6981,15 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
6365
6981
|
baseline,
|
|
6366
6982
|
adapter: ctx.adapter,
|
|
6367
6983
|
sourceInvalidIcuKeys: ctx.sourceInvalidIcuKeys,
|
|
6368
|
-
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column })),
|
|
6369
|
-
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row }))
|
|
6984
|
+
malformedRows: ctx.malformedRows.filter((problem) => problem.locale === sheet.locale).map((problem) => ({ row: problem.row, column: problem.column, ...lineOf(problem) })),
|
|
6985
|
+
duplicateKeys: ctx.duplicateKeys.filter((duplicate) => duplicate.locale === sheet.locale).map((duplicate) => ({ key: duplicate.key, row: duplicate.row, ...lineOf(duplicate) }))
|
|
6370
6986
|
});
|
|
6371
6987
|
if (ctx.dryRun) {
|
|
6372
6988
|
return { summary, lockEntries: {}, cacheAdditions: {} };
|
|
6373
6989
|
}
|
|
6374
6990
|
const merged = mergeAccepted(target, accepted);
|
|
6375
6991
|
if (accepted.size > 0) {
|
|
6376
|
-
const path =
|
|
6992
|
+
const path = createLocalePathResolver(ctx.cwd, ctx.config).pathFor(sheet.locale);
|
|
6377
6993
|
await ctx.adapter.write(
|
|
6378
6994
|
{
|
|
6379
6995
|
locale: sheet.locale,
|
|
@@ -6397,14 +7013,13 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6397
7013
|
const fs = deps.fs ?? defaultFs;
|
|
6398
7014
|
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
6399
7015
|
const source = await readSource(config, cwd, fs, adapter);
|
|
6400
|
-
const
|
|
6401
|
-
const
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
}
|
|
7016
|
+
const format = input.format ?? "xlsx";
|
|
7017
|
+
const { data, staleLocales } = await readImportData(
|
|
7018
|
+
path.resolve(cwd, input.workbook),
|
|
7019
|
+
config,
|
|
7020
|
+
fs,
|
|
7021
|
+
format
|
|
7022
|
+
);
|
|
6408
7023
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
6409
7024
|
const ctx = {
|
|
6410
7025
|
config,
|
|
@@ -6415,7 +7030,8 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6415
7030
|
sourceInvalidIcuKeys: source.invalidIcuKeys,
|
|
6416
7031
|
dryRun,
|
|
6417
7032
|
malformedRows: data.malformedRows,
|
|
6418
|
-
duplicateKeys: data.duplicateKeys
|
|
7033
|
+
duplicateKeys: data.duplicateKeys,
|
|
7034
|
+
format
|
|
6419
7035
|
};
|
|
6420
7036
|
const summaries = [];
|
|
6421
7037
|
const cacheAdditions = /* @__PURE__ */ new Map();
|
|
@@ -6440,12 +7056,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6440
7056
|
summaries.push(failureSummary(sheet.locale, error));
|
|
6441
7057
|
}
|
|
6442
7058
|
}
|
|
6443
|
-
|
|
6444
|
-
for (const locale of config.targetLocales) {
|
|
6445
|
-
if (!presentLocales.has(locale)) {
|
|
6446
|
-
summaries.push(failureSummary(locale, new MissingSheetError(locale)));
|
|
6447
|
-
}
|
|
6448
|
-
}
|
|
7059
|
+
summaries.push(...absentLocaleFailures(config, data.sheets, format, staleLocales));
|
|
6449
7060
|
if (!dryRun) {
|
|
6450
7061
|
await feedTranslationMemory(cwd, fs, computeFingerprint(config), cacheAdditions);
|
|
6451
7062
|
}
|
|
@@ -6494,7 +7105,8 @@ async function watch(input, deps = {}) {
|
|
|
6494
7105
|
const debounceMs = input.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
6495
7106
|
const fs = deps.fs ?? defaultFs;
|
|
6496
7107
|
resolveRunConcurrency(input.concurrency, false, input.config);
|
|
6497
|
-
const
|
|
7108
|
+
const resolver = createLocalePathResolver(cwd, input.config);
|
|
7109
|
+
const sourcePath = resolver.pathFor(input.config.sourceLocale);
|
|
6498
7110
|
if (!await fs.fileExists(sourcePath)) {
|
|
6499
7111
|
throw new SdkError(
|
|
6500
7112
|
"SOURCE_UNREADABLE",
|
|
@@ -6578,10 +7190,12 @@ async function watch(input, deps = {}) {
|
|
|
6578
7190
|
}
|
|
6579
7191
|
|
|
6580
7192
|
exports.CACHE_FILE_NAME = CACHE_FILE_NAME;
|
|
7193
|
+
exports.DEFAULT_DELIMITED_PATH = DEFAULT_DELIMITED_PATH;
|
|
6581
7194
|
exports.DEFAULT_WORKBOOK_PATH = DEFAULT_WORKBOOK_PATH;
|
|
6582
7195
|
exports.LOCK_FILE_NAME = LOCK_FILE_NAME;
|
|
6583
7196
|
exports.SdkError = SdkError;
|
|
6584
7197
|
exports.check = check;
|
|
7198
|
+
exports.createLocalePathResolver = createLocalePathResolver;
|
|
6585
7199
|
exports.defineConfig = defineConfig;
|
|
6586
7200
|
exports.diff = diff;
|
|
6587
7201
|
exports.diffLocaleSnapshots = diffLocaleSnapshots;
|