@verbatra/sdk 0.4.3 → 0.5.0-next.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 +1 -1
- package/dist/index.cjs +428 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +230 -59
- package/dist/index.d.ts +230 -59
- package/dist/index.js +426 -169
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
|
-
import { resolve, join,
|
|
2
|
+
import { dirname, resolve, join, basename, extname } from 'path';
|
|
3
3
|
import { cosmiconfig } from 'cosmiconfig';
|
|
4
4
|
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import { access, writeFile, rename, rm, open } from 'fs/promises';
|
|
5
7
|
import { z } from 'zod';
|
|
6
8
|
import Anthropic from '@anthropic-ai/sdk';
|
|
7
9
|
import * as deepl from 'deepl-node';
|
|
@@ -9,8 +11,6 @@ import { createRequire } from 'module';
|
|
|
9
11
|
import log from 'loglevel';
|
|
10
12
|
import { GoogleGenAI } from '@google/genai';
|
|
11
13
|
import OpenAI from 'openai';
|
|
12
|
-
import { randomUUID } from 'crypto';
|
|
13
|
-
import { access, writeFile, rename, rm, open } from 'fs/promises';
|
|
14
14
|
import { parse as parse$1, TYPE } from '@formatjs/icu-messageformat-parser';
|
|
15
15
|
import { XMLSerializer, DOMParser } from '@xmldom/xmldom';
|
|
16
16
|
import { stringify, parse } from 'yaml';
|
|
@@ -38,6 +38,155 @@ var SdkError = class extends Error {
|
|
|
38
38
|
this.code = code;
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
+
async function readBoundedUtf8(handle, size) {
|
|
42
|
+
const buffer = Buffer.allocUnsafe(size);
|
|
43
|
+
let offset = 0;
|
|
44
|
+
while (offset < size) {
|
|
45
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
46
|
+
if (bytesRead === 0) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
offset += bytesRead;
|
|
50
|
+
}
|
|
51
|
+
return buffer.toString("utf8", 0, offset);
|
|
52
|
+
}
|
|
53
|
+
async function readBounded(path, maxBytes) {
|
|
54
|
+
let handle;
|
|
55
|
+
try {
|
|
56
|
+
handle = await open(path, "r");
|
|
57
|
+
} catch {
|
|
58
|
+
return { kind: "missing" };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const info = await handle.stat();
|
|
62
|
+
if (!info.isFile()) {
|
|
63
|
+
return { kind: "missing" };
|
|
64
|
+
}
|
|
65
|
+
if (info.size > maxBytes) {
|
|
66
|
+
return { kind: "too-large" };
|
|
67
|
+
}
|
|
68
|
+
return { kind: "ok", content: await readBoundedUtf8(handle, info.size) };
|
|
69
|
+
} finally {
|
|
70
|
+
await handle.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function readBoundedBytesInto(handle, size) {
|
|
74
|
+
const buffer = Buffer.allocUnsafeSlow(size);
|
|
75
|
+
let offset = 0;
|
|
76
|
+
while (offset < size) {
|
|
77
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
78
|
+
if (bytesRead === 0) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
offset += bytesRead;
|
|
82
|
+
}
|
|
83
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, offset);
|
|
84
|
+
}
|
|
85
|
+
async function readBoundedBytes(path, maxBytes) {
|
|
86
|
+
let handle;
|
|
87
|
+
try {
|
|
88
|
+
handle = await open(path, "r");
|
|
89
|
+
} catch {
|
|
90
|
+
return { kind: "missing" };
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const info = await handle.stat();
|
|
94
|
+
if (!info.isFile()) {
|
|
95
|
+
return { kind: "missing" };
|
|
96
|
+
}
|
|
97
|
+
if (info.size > maxBytes) {
|
|
98
|
+
return { kind: "too-large" };
|
|
99
|
+
}
|
|
100
|
+
return { kind: "ok", bytes: await readBoundedBytesInto(handle, info.size) };
|
|
101
|
+
} finally {
|
|
102
|
+
await handle.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function tempFileName(path) {
|
|
106
|
+
return join(dirname(path), `.${basename(path)}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
107
|
+
}
|
|
108
|
+
async function atomicWrite(path, data) {
|
|
109
|
+
const tmp = tempFileName(path);
|
|
110
|
+
await (typeof data === "string" ? writeFile(tmp, data, "utf8") : writeFile(tmp, data));
|
|
111
|
+
try {
|
|
112
|
+
await rename(tmp, path);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
await rm(tmp, { force: true });
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
var defaultFs = {
|
|
119
|
+
async fileExists(path) {
|
|
120
|
+
try {
|
|
121
|
+
await access(path);
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
readFileBounded: (path, maxBytes) => readBounded(path, maxBytes),
|
|
128
|
+
readBytesBounded: (path, maxBytes) => readBoundedBytes(path, maxBytes),
|
|
129
|
+
writeFile: (path, data) => atomicWrite(path, data),
|
|
130
|
+
writeBytes: (path, data) => atomicWrite(path, data)
|
|
131
|
+
};
|
|
132
|
+
var MAX_GLOSSARY_FILE_BYTES = 1024 * 1024;
|
|
133
|
+
var glossaryRecordSchema = z.record(z.string(), z.string());
|
|
134
|
+
var BOM = "\uFEFF";
|
|
135
|
+
var REPLACEMENT_CHARACTER = "\uFFFD";
|
|
136
|
+
var NUL = "\0";
|
|
137
|
+
function stripBom(content) {
|
|
138
|
+
return content.startsWith(BOM) ? content.slice(BOM.length) : content;
|
|
139
|
+
}
|
|
140
|
+
function looksLikeInvalidEncoding(content) {
|
|
141
|
+
return content.startsWith(REPLACEMENT_CHARACTER) || content.includes(NUL);
|
|
142
|
+
}
|
|
143
|
+
async function readGlossaryFile(resolvedPath, fs) {
|
|
144
|
+
const read = await fs.readFileBounded(resolvedPath, MAX_GLOSSARY_FILE_BYTES);
|
|
145
|
+
if (read.kind === "missing") {
|
|
146
|
+
throw new SdkError(
|
|
147
|
+
"CONFIG_INVALID",
|
|
148
|
+
`The glossary file at ${resolvedPath} was not found or could not be read.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (read.kind === "too-large") {
|
|
152
|
+
throw new SdkError(
|
|
153
|
+
"CONFIG_INVALID",
|
|
154
|
+
`The glossary file at ${resolvedPath} exceeds the maximum allowed size of ${MAX_GLOSSARY_FILE_BYTES} bytes.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const content = stripBom(read.content);
|
|
158
|
+
if (looksLikeInvalidEncoding(content)) {
|
|
159
|
+
throw new SdkError(
|
|
160
|
+
"CONFIG_INVALID",
|
|
161
|
+
`The glossary file at ${resolvedPath} must be UTF-8 encoded.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
let parsed;
|
|
165
|
+
try {
|
|
166
|
+
parsed = JSON.parse(content);
|
|
167
|
+
} catch {
|
|
168
|
+
throw new SdkError("CONFIG_INVALID", `The glossary file at ${resolvedPath} is not valid JSON.`);
|
|
169
|
+
}
|
|
170
|
+
const result = glossaryRecordSchema.safeParse(parsed);
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
throw new SdkError(
|
|
173
|
+
"CONFIG_INVALID",
|
|
174
|
+
`The glossary file at ${resolvedPath} must contain a flat object of string keys to string values.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return result.data;
|
|
178
|
+
}
|
|
179
|
+
async function resolveGlossary(glossary, baseDir, fs) {
|
|
180
|
+
if (glossary === void 0) {
|
|
181
|
+
return { glossary: void 0, provenance: { source: "none" } };
|
|
182
|
+
}
|
|
183
|
+
if (typeof glossary === "string") {
|
|
184
|
+
const resolvedPath = resolve(baseDir, glossary);
|
|
185
|
+
const record = await readGlossaryFile(resolvedPath, fs);
|
|
186
|
+
return { glossary: record, provenance: { source: "file", path: resolvedPath } };
|
|
187
|
+
}
|
|
188
|
+
return { glossary, provenance: { source: "inline" } };
|
|
189
|
+
}
|
|
41
190
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
42
191
|
var FNV_PRIME = 1099511628211n;
|
|
43
192
|
var U64_MASK = (1n << 64n) - 1n;
|
|
@@ -873,7 +1022,13 @@ var verbatraConfigSchema = z.strictObject({
|
|
|
873
1022
|
pattern: z.string().min(1)
|
|
874
1023
|
}),
|
|
875
1024
|
provider: providerConfigSchema,
|
|
876
|
-
|
|
1025
|
+
/**
|
|
1026
|
+
* A glossary of source terms to preferred target terms, either inline or as a path to a JSON file
|
|
1027
|
+
* carrying the same shape. The two forms are mutually exclusive by type; there is no merge or
|
|
1028
|
+
* precedence between them. A path is resolved and validated by the loader, not by this schema; see
|
|
1029
|
+
* `resolve-glossary.ts`.
|
|
1030
|
+
*/
|
|
1031
|
+
glossary: z.union([z.record(z.string(), z.string()), z.string().min(1)]).optional(),
|
|
877
1032
|
tone: z.enum(["formal", "informal", "neutral"]).optional(),
|
|
878
1033
|
/**
|
|
879
1034
|
* Opt-in orphan pruning, off by default. When true, keys present in a target file but absent from
|
|
@@ -927,7 +1082,7 @@ function formatIssues(error) {
|
|
|
927
1082
|
return issue.code === "unrecognized_keys" ? `${base} (API keys are read from the environment, not the config)` : base;
|
|
928
1083
|
}).join("; ");
|
|
929
1084
|
}
|
|
930
|
-
function
|
|
1085
|
+
function parseConfig(input) {
|
|
931
1086
|
const parsed = verbatraConfigSchema.safeParse(input);
|
|
932
1087
|
if (!parsed.success) {
|
|
933
1088
|
throw new SdkError(
|
|
@@ -937,7 +1092,16 @@ function validate(input) {
|
|
|
937
1092
|
}
|
|
938
1093
|
return parsed.data;
|
|
939
1094
|
}
|
|
940
|
-
async function
|
|
1095
|
+
async function finalizeConfig(parsed, baseDir, fs) {
|
|
1096
|
+
const { glossary: glossaryInput, ...rest } = parsed;
|
|
1097
|
+
const resolved = await resolveGlossary(glossaryInput, baseDir, fs);
|
|
1098
|
+
const config = {
|
|
1099
|
+
...rest,
|
|
1100
|
+
...resolved.glossary !== void 0 ? { glossary: resolved.glossary } : {}
|
|
1101
|
+
};
|
|
1102
|
+
return { config, glossary: resolved.provenance };
|
|
1103
|
+
}
|
|
1104
|
+
async function loadExplicitWithMeta(explorer, configPath, cwd, fs) {
|
|
941
1105
|
const resolved = resolve(cwd ?? process.cwd(), configPath);
|
|
942
1106
|
if (!existsSync(resolved)) {
|
|
943
1107
|
throw new SdkError("CONFIG_NOT_FOUND", `No verbatra configuration file at ${resolved}.`);
|
|
@@ -949,18 +1113,23 @@ async function loadExplicit(explorer, configPath, cwd) {
|
|
|
949
1113
|
const detail = error instanceof Error ? error.message : String(error);
|
|
950
1114
|
throw new SdkError("CONFIG_INVALID", `Failed to load the verbatra configuration: ${detail}`);
|
|
951
1115
|
}
|
|
952
|
-
|
|
1116
|
+
const parsed = parseConfig(result?.config);
|
|
1117
|
+
const { config, glossary } = await finalizeConfig(parsed, dirname(resolved), fs);
|
|
1118
|
+
return { config, source: { kind: "explicit", filepath: resolved }, glossary };
|
|
953
1119
|
}
|
|
954
|
-
async function
|
|
1120
|
+
async function loadConfigWithMeta(options = {}) {
|
|
1121
|
+
const fs = options.fs ?? defaultFs;
|
|
955
1122
|
if (options.configOverride !== void 0) {
|
|
956
|
-
|
|
1123
|
+
const parsed2 = parseConfig(options.configOverride);
|
|
1124
|
+
const { config: config2, glossary: glossary2 } = await finalizeConfig(parsed2, options.cwd ?? process.cwd(), fs);
|
|
1125
|
+
return { config: config2, source: { kind: "override" }, glossary: glossary2 };
|
|
957
1126
|
}
|
|
958
1127
|
const explorer = cosmiconfig(MODULE_NAME, {
|
|
959
1128
|
searchPlaces: SEARCH_PLACES,
|
|
960
1129
|
loaders: { ".ts": TypeScriptLoader() }
|
|
961
1130
|
});
|
|
962
1131
|
if (options.configPath !== void 0) {
|
|
963
|
-
return
|
|
1132
|
+
return loadExplicitWithMeta(explorer, options.configPath, options.cwd, fs);
|
|
964
1133
|
}
|
|
965
1134
|
let result;
|
|
966
1135
|
try {
|
|
@@ -975,99 +1144,14 @@ async function loadConfig(options = {}) {
|
|
|
975
1144
|
"No verbatra configuration found. Create a verbatra.config.ts, a .verbatrarc.json, or a 'verbatra' property in package.json."
|
|
976
1145
|
);
|
|
977
1146
|
}
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
const buffer = Buffer.allocUnsafe(size);
|
|
982
|
-
let offset = 0;
|
|
983
|
-
while (offset < size) {
|
|
984
|
-
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
985
|
-
if (bytesRead === 0) {
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
988
|
-
offset += bytesRead;
|
|
989
|
-
}
|
|
990
|
-
return buffer.toString("utf8", 0, offset);
|
|
991
|
-
}
|
|
992
|
-
async function readBounded(path, maxBytes) {
|
|
993
|
-
let handle;
|
|
994
|
-
try {
|
|
995
|
-
handle = await open(path, "r");
|
|
996
|
-
} catch {
|
|
997
|
-
return { kind: "missing" };
|
|
998
|
-
}
|
|
999
|
-
try {
|
|
1000
|
-
const info = await handle.stat();
|
|
1001
|
-
if (!info.isFile()) {
|
|
1002
|
-
return { kind: "missing" };
|
|
1003
|
-
}
|
|
1004
|
-
if (info.size > maxBytes) {
|
|
1005
|
-
return { kind: "too-large" };
|
|
1006
|
-
}
|
|
1007
|
-
return { kind: "ok", content: await readBoundedUtf8(handle, info.size) };
|
|
1008
|
-
} finally {
|
|
1009
|
-
await handle.close();
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
async function readBoundedBytesInto(handle, size) {
|
|
1013
|
-
const buffer = Buffer.allocUnsafeSlow(size);
|
|
1014
|
-
let offset = 0;
|
|
1015
|
-
while (offset < size) {
|
|
1016
|
-
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
1017
|
-
if (bytesRead === 0) {
|
|
1018
|
-
break;
|
|
1019
|
-
}
|
|
1020
|
-
offset += bytesRead;
|
|
1021
|
-
}
|
|
1022
|
-
return new Uint8Array(buffer.buffer, buffer.byteOffset, offset);
|
|
1023
|
-
}
|
|
1024
|
-
async function readBoundedBytes(path, maxBytes) {
|
|
1025
|
-
let handle;
|
|
1026
|
-
try {
|
|
1027
|
-
handle = await open(path, "r");
|
|
1028
|
-
} catch {
|
|
1029
|
-
return { kind: "missing" };
|
|
1030
|
-
}
|
|
1031
|
-
try {
|
|
1032
|
-
const info = await handle.stat();
|
|
1033
|
-
if (!info.isFile()) {
|
|
1034
|
-
return { kind: "missing" };
|
|
1035
|
-
}
|
|
1036
|
-
if (info.size > maxBytes) {
|
|
1037
|
-
return { kind: "too-large" };
|
|
1038
|
-
}
|
|
1039
|
-
return { kind: "ok", bytes: await readBoundedBytesInto(handle, info.size) };
|
|
1040
|
-
} finally {
|
|
1041
|
-
await handle.close();
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
function tempFileName(path) {
|
|
1045
|
-
return join(dirname(path), `.${basename(path)}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
1147
|
+
const parsed = parseConfig(result.config);
|
|
1148
|
+
const { config, glossary } = await finalizeConfig(parsed, dirname(result.filepath), fs);
|
|
1149
|
+
return { config, source: { kind: "search", filepath: result.filepath }, glossary };
|
|
1046
1150
|
}
|
|
1047
|
-
async function
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
try {
|
|
1051
|
-
await rename(tmp, path);
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
await rm(tmp, { force: true });
|
|
1054
|
-
throw error;
|
|
1055
|
-
}
|
|
1151
|
+
async function loadConfig(options = {}) {
|
|
1152
|
+
const { config } = await loadConfigWithMeta(options);
|
|
1153
|
+
return config;
|
|
1056
1154
|
}
|
|
1057
|
-
var defaultFs = {
|
|
1058
|
-
async fileExists(path) {
|
|
1059
|
-
try {
|
|
1060
|
-
await access(path);
|
|
1061
|
-
return true;
|
|
1062
|
-
} catch {
|
|
1063
|
-
return false;
|
|
1064
|
-
}
|
|
1065
|
-
},
|
|
1066
|
-
readFileBounded: (path, maxBytes) => readBounded(path, maxBytes),
|
|
1067
|
-
readBytesBounded: (path, maxBytes) => readBoundedBytes(path, maxBytes),
|
|
1068
|
-
writeFile: (path, data) => atomicWrite(path, data),
|
|
1069
|
-
writeBytes: (path, data) => atomicWrite(path, data)
|
|
1070
|
-
};
|
|
1071
1155
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1072
1156
|
var CURRENT_VERSION = 1;
|
|
1073
1157
|
var EMPTY_LOCK = { version: CURRENT_VERSION, locales: {} };
|
|
@@ -1152,19 +1236,53 @@ function childMessages(element) {
|
|
|
1152
1236
|
}
|
|
1153
1237
|
return [];
|
|
1154
1238
|
}
|
|
1155
|
-
function
|
|
1239
|
+
function addCount(target, token, count) {
|
|
1240
|
+
target.set(token, (target.get(token) ?? 0) + count);
|
|
1241
|
+
}
|
|
1242
|
+
function combineBranches(branchMultisets) {
|
|
1243
|
+
const combined = /* @__PURE__ */ new Map();
|
|
1244
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1245
|
+
for (const branch of branchMultisets) {
|
|
1246
|
+
for (const token of branch.keys()) {
|
|
1247
|
+
tokens.add(token);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
for (const token of tokens) {
|
|
1251
|
+
const minCount = Math.min(...branchMultisets.map((branch) => branch.get(token) ?? 0));
|
|
1252
|
+
if (minCount > 0) {
|
|
1253
|
+
combined.set(token, minCount);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return combined;
|
|
1257
|
+
}
|
|
1258
|
+
function collect(elements, state) {
|
|
1259
|
+
const result = /* @__PURE__ */ new Map();
|
|
1156
1260
|
for (const element of elements) {
|
|
1157
1261
|
const token = tokenOf(element);
|
|
1158
1262
|
if (token !== void 0) {
|
|
1159
|
-
|
|
1263
|
+
addCount(result, token, 1);
|
|
1160
1264
|
}
|
|
1161
1265
|
if (element.type === TYPE.plural) {
|
|
1162
1266
|
state.isPlural = true;
|
|
1163
1267
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1268
|
+
const branches = childMessages(element);
|
|
1269
|
+
if (branches.length > 0) {
|
|
1270
|
+
const branchMultisets = branches.map((branch) => collect(branch, state));
|
|
1271
|
+
for (const [branchToken, branchCount] of combineBranches(branchMultisets)) {
|
|
1272
|
+
addCount(result, branchToken, branchCount);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return result;
|
|
1277
|
+
}
|
|
1278
|
+
function flatten(multiset) {
|
|
1279
|
+
const flattened = [];
|
|
1280
|
+
for (const [token, count] of multiset) {
|
|
1281
|
+
for (let i = 0; i < count; i += 1) {
|
|
1282
|
+
flattened.push(token);
|
|
1166
1283
|
}
|
|
1167
1284
|
}
|
|
1285
|
+
return flattened;
|
|
1168
1286
|
}
|
|
1169
1287
|
function analyzeIcuValue(value) {
|
|
1170
1288
|
if (!value.includes("{") && !value.includes("<")) {
|
|
@@ -1172,16 +1290,9 @@ function analyzeIcuValue(value) {
|
|
|
1172
1290
|
}
|
|
1173
1291
|
try {
|
|
1174
1292
|
const ast = parse$1(value);
|
|
1175
|
-
const placeholders = [];
|
|
1176
1293
|
const state = { isPlural: false };
|
|
1177
|
-
collect(
|
|
1178
|
-
|
|
1179
|
-
(token) => {
|
|
1180
|
-
placeholders.push(token);
|
|
1181
|
-
},
|
|
1182
|
-
state
|
|
1183
|
-
);
|
|
1184
|
-
return { placeholders, isPlural: state.isPlural, valid: true };
|
|
1294
|
+
const multiset = collect(ast, state);
|
|
1295
|
+
return { placeholders: flatten(multiset), isPlural: state.isPlural, valid: true };
|
|
1185
1296
|
} catch {
|
|
1186
1297
|
return INVALID;
|
|
1187
1298
|
}
|
|
@@ -1443,21 +1554,28 @@ function addEntries(ctx, prefix, node) {
|
|
|
1443
1554
|
}
|
|
1444
1555
|
}
|
|
1445
1556
|
}
|
|
1446
|
-
function addPathEntries(node, prefix, namespace, derive, out) {
|
|
1557
|
+
function addPathEntries(node, prefix, namespace, derive, out, claimedPaths) {
|
|
1447
1558
|
for (const [key, value] of Object.entries(node)) {
|
|
1448
1559
|
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
1560
|
+
if (claimedPaths.has(path)) {
|
|
1561
|
+
throw new AdapterError(
|
|
1562
|
+
"INVALID_STRUCTURE",
|
|
1563
|
+
"A dotted key and a nested key path resolve to the same path."
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
claimedPaths.add(path);
|
|
1449
1567
|
if (typeof value === "string") {
|
|
1450
1568
|
const { placeholders, isPlural } = derive(key, value);
|
|
1451
1569
|
out.set(path, { key: path, namespace, value, placeholders, isPlural });
|
|
1452
1570
|
} else {
|
|
1453
|
-
addPathEntries(value, path, namespace, derive, out);
|
|
1571
|
+
addPathEntries(value, path, namespace, derive, out, claimedPaths);
|
|
1454
1572
|
}
|
|
1455
1573
|
}
|
|
1456
1574
|
}
|
|
1457
1575
|
function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
|
|
1458
1576
|
const out = /* @__PURE__ */ new Map();
|
|
1459
1577
|
if (keyMode === "path-notation") {
|
|
1460
|
-
addPathEntries(tree, "", namespace, derive, out);
|
|
1578
|
+
addPathEntries(tree, "", namespace, derive, out, /* @__PURE__ */ new Set());
|
|
1461
1579
|
return out;
|
|
1462
1580
|
}
|
|
1463
1581
|
addEntries({ namespace, derive, out, claimed: /* @__PURE__ */ new Map() }, [], tree);
|
|
@@ -1579,21 +1697,20 @@ function messagesFromEntries(entries) {
|
|
|
1579
1697
|
}
|
|
1580
1698
|
return out;
|
|
1581
1699
|
}
|
|
1700
|
+
function isEnoent(error) {
|
|
1701
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1702
|
+
}
|
|
1582
1703
|
async function readDestinationPairs(filePath) {
|
|
1583
|
-
let
|
|
1704
|
+
let content;
|
|
1584
1705
|
try {
|
|
1585
|
-
|
|
1586
|
-
|
|
1706
|
+
content = await readFileContent(filePath);
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
if (isEnoent(error)) {
|
|
1587
1709
|
return null;
|
|
1588
1710
|
}
|
|
1589
|
-
|
|
1590
|
-
} catch {
|
|
1591
|
-
return null;
|
|
1592
|
-
}
|
|
1593
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1594
|
-
return null;
|
|
1711
|
+
throw error;
|
|
1595
1712
|
}
|
|
1596
|
-
return Object.entries(
|
|
1713
|
+
return Object.entries(parseArbObject(content));
|
|
1597
1714
|
}
|
|
1598
1715
|
async function buildArbWriteTree(entries, filePath) {
|
|
1599
1716
|
const messages = messagesFromEntries(entries);
|
|
@@ -1694,6 +1811,20 @@ function createNextIntlJsonAdapter() {
|
|
|
1694
1811
|
validateMessage: icuIsValid
|
|
1695
1812
|
});
|
|
1696
1813
|
}
|
|
1814
|
+
function assertNoDottedNestedKey(tree) {
|
|
1815
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
1816
|
+
if (typeof value !== "object") {
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
if (key.includes(".")) {
|
|
1820
|
+
throw new AdapterError(
|
|
1821
|
+
"MIXED_STRUCTURE",
|
|
1822
|
+
"A nested object key contains a literal dot, which is ambiguous with a dotted path."
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
assertNoDottedNestedKey(value);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1697
1828
|
function assertNotMixed(tree) {
|
|
1698
1829
|
let hasNested = false;
|
|
1699
1830
|
let hasFlatDottedKey = false;
|
|
@@ -1710,6 +1841,7 @@ function assertNotMixed(tree) {
|
|
|
1710
1841
|
"The file mixes flat dotted keys with nested objects."
|
|
1711
1842
|
);
|
|
1712
1843
|
}
|
|
1844
|
+
assertNoDottedNestedKey(tree);
|
|
1713
1845
|
}
|
|
1714
1846
|
async function detectStyle(filePath) {
|
|
1715
1847
|
let parsed;
|
|
@@ -2176,6 +2308,40 @@ async function diff(input, deps = {}) {
|
|
|
2176
2308
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
2177
2309
|
}
|
|
2178
2310
|
|
|
2311
|
+
// src/flow/lock-state.ts
|
|
2312
|
+
function toLockLocaleState(locale, keyCount, diff2) {
|
|
2313
|
+
return {
|
|
2314
|
+
locale,
|
|
2315
|
+
keyCount,
|
|
2316
|
+
missing: diff2.missing.length,
|
|
2317
|
+
stale: diff2.changed.length,
|
|
2318
|
+
upToDate: diff2.unchanged.length
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
async function lockState(input, deps = {}) {
|
|
2322
|
+
const config = input.config;
|
|
2323
|
+
const cwd = input.cwd ?? process.cwd();
|
|
2324
|
+
const fs = deps.fs ?? defaultFs;
|
|
2325
|
+
const locales = selectLocales(config, input.locales);
|
|
2326
|
+
const path = lockFilePath(cwd);
|
|
2327
|
+
const exists = await fs.fileExists(path);
|
|
2328
|
+
if (!exists) {
|
|
2329
|
+
return { exists: false };
|
|
2330
|
+
}
|
|
2331
|
+
const lock = await readLockFile(path, fs);
|
|
2332
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
2333
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
2334
|
+
const localeStates = await Promise.all(
|
|
2335
|
+
locales.map(async (locale) => {
|
|
2336
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
2337
|
+
const baseline = baselineFor(lock, locale);
|
|
2338
|
+
const diff2 = diffResources(source.resource, target, { baseline });
|
|
2339
|
+
return toLockLocaleState(locale, baseline.size, diff2);
|
|
2340
|
+
})
|
|
2341
|
+
);
|
|
2342
|
+
return { exists: true, version: lock.version, locales: localeStates };
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2179
2345
|
// src/selection/select-provider.ts
|
|
2180
2346
|
function selectProvider(config, createProvider = buildProvider) {
|
|
2181
2347
|
try {
|
|
@@ -2207,6 +2373,7 @@ function failureSummary(locale, error) {
|
|
|
2207
2373
|
pruned: [],
|
|
2208
2374
|
invalidIcuSource: [],
|
|
2209
2375
|
integrityMismatches: [],
|
|
2376
|
+
providerFailures: [],
|
|
2210
2377
|
generated: [],
|
|
2211
2378
|
notices: [],
|
|
2212
2379
|
error: describeError(error)
|
|
@@ -2218,6 +2385,29 @@ function partition(locales) {
|
|
|
2218
2385
|
return { succeeded, failed };
|
|
2219
2386
|
}
|
|
2220
2387
|
|
|
2388
|
+
// src/flow/batching.ts
|
|
2389
|
+
function chunk(items, size) {
|
|
2390
|
+
const chunks = [];
|
|
2391
|
+
for (let index = 0; index < items.length; index += size) {
|
|
2392
|
+
chunks.push(items.slice(index, index + size));
|
|
2393
|
+
}
|
|
2394
|
+
return chunks;
|
|
2395
|
+
}
|
|
2396
|
+
var GENERIC_PROVIDER_FAILURE_MESSAGE = "The provider call failed.";
|
|
2397
|
+
function classifyProviderFailure(error) {
|
|
2398
|
+
if (error instanceof ProviderError) {
|
|
2399
|
+
return { code: error.code, message: error.message };
|
|
2400
|
+
}
|
|
2401
|
+
return { code: "PROVIDER_CALL_FAILED", message: GENERIC_PROVIDER_FAILURE_MESSAGE };
|
|
2402
|
+
}
|
|
2403
|
+
function subBatchFailedNotice(count, error) {
|
|
2404
|
+
const { code, message } = classifyProviderFailure(error);
|
|
2405
|
+
return {
|
|
2406
|
+
code: "SUB_BATCH_FAILED",
|
|
2407
|
+
message: `A sub-batch of ${count} entries failed (${code}: ${message}) and was withheld; it will be retried next run.`
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2221
2411
|
// src/flow/notices.ts
|
|
2222
2412
|
function isNotice(value) {
|
|
2223
2413
|
return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
|
|
@@ -2400,13 +2590,36 @@ async function generatePluralForms(context) {
|
|
|
2400
2590
|
const plan = planPluralGeneration(context.source, context.targetLocale, context.format);
|
|
2401
2591
|
const stale = staleItems(plan.items, context.baseline);
|
|
2402
2592
|
if (stale.length === 0) {
|
|
2403
|
-
return { accepted: [], withheld: [] };
|
|
2593
|
+
return { accepted: [], withheld: [], providerFailures: [], notices: [] };
|
|
2404
2594
|
}
|
|
2405
|
-
const entries = stale.map(syntheticEntry);
|
|
2406
|
-
const result = await context.provider.translateBatch(buildRequest2(context, entries));
|
|
2407
2595
|
const accepted = [];
|
|
2408
2596
|
const withheld = [];
|
|
2409
|
-
|
|
2597
|
+
const providerFailures = [];
|
|
2598
|
+
const notices = [];
|
|
2599
|
+
for (const batch of chunk(stale, context.maxBatchSize)) {
|
|
2600
|
+
const batchNotices = await runGenerationSubBatch(
|
|
2601
|
+
context,
|
|
2602
|
+
batch,
|
|
2603
|
+
accepted,
|
|
2604
|
+
withheld,
|
|
2605
|
+
providerFailures
|
|
2606
|
+
);
|
|
2607
|
+
notices.push(...batchNotices);
|
|
2608
|
+
}
|
|
2609
|
+
return { accepted, withheld, providerFailures, notices };
|
|
2610
|
+
}
|
|
2611
|
+
async function runGenerationSubBatch(context, batch, accepted, withheld, providerFailures) {
|
|
2612
|
+
let result;
|
|
2613
|
+
try {
|
|
2614
|
+
const entries = batch.map(syntheticEntry);
|
|
2615
|
+
result = await context.provider.translateBatch(buildRequest2(context, entries));
|
|
2616
|
+
} catch (error) {
|
|
2617
|
+
for (const item of batch) {
|
|
2618
|
+
providerFailures.push(item.targetKey);
|
|
2619
|
+
}
|
|
2620
|
+
return [subBatchFailedNotice(batch.length, error)];
|
|
2621
|
+
}
|
|
2622
|
+
for (const item of batch) {
|
|
2410
2623
|
const value = result.values.get(item.targetKey);
|
|
2411
2624
|
const integrity = result.integrity.get(item.targetKey);
|
|
2412
2625
|
if (value !== void 0 && integrity?.matches === true) {
|
|
@@ -2419,7 +2632,7 @@ async function generatePluralForms(context) {
|
|
|
2419
2632
|
withheld.push(item.targetKey);
|
|
2420
2633
|
}
|
|
2421
2634
|
}
|
|
2422
|
-
return
|
|
2635
|
+
return readNotices(result);
|
|
2423
2636
|
}
|
|
2424
2637
|
|
|
2425
2638
|
// src/flow/locale-run.ts
|
|
@@ -2469,6 +2682,7 @@ async function runLocale(params) {
|
|
|
2469
2682
|
translated: toTranslate,
|
|
2470
2683
|
generated: [],
|
|
2471
2684
|
integrityMismatches: [],
|
|
2685
|
+
providerFailures: [],
|
|
2472
2686
|
pruned,
|
|
2473
2687
|
notices: sdkNotices
|
|
2474
2688
|
}),
|
|
@@ -2478,12 +2692,14 @@ async function runLocale(params) {
|
|
|
2478
2692
|
const entries = toTranslate.map((key) => params.source.entries.get(key)).filter((entry) => entry !== void 0);
|
|
2479
2693
|
const accepted = /* @__PURE__ */ new Map();
|
|
2480
2694
|
const integrityMismatches = [];
|
|
2695
|
+
const providerFailures = [];
|
|
2481
2696
|
const subBatchNotices = await translateAndCheck(
|
|
2482
2697
|
provider,
|
|
2483
2698
|
params,
|
|
2484
2699
|
entries,
|
|
2485
2700
|
accepted,
|
|
2486
|
-
integrityMismatches
|
|
2701
|
+
integrityMismatches,
|
|
2702
|
+
providerFailures
|
|
2487
2703
|
);
|
|
2488
2704
|
const merged = new Map(target.entries);
|
|
2489
2705
|
for (const key of pruned) {
|
|
@@ -2507,8 +2723,18 @@ async function runLocale(params) {
|
|
|
2507
2723
|
path
|
|
2508
2724
|
);
|
|
2509
2725
|
const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
|
|
2510
|
-
const notices = [
|
|
2511
|
-
|
|
2726
|
+
const notices = [
|
|
2727
|
+
...pluralNotices,
|
|
2728
|
+
...subBatchNotices,
|
|
2729
|
+
...generation.notices
|
|
2730
|
+
];
|
|
2731
|
+
const withheld = /* @__PURE__ */ new Set([
|
|
2732
|
+
...integrityMismatches,
|
|
2733
|
+
...providerFailures,
|
|
2734
|
+
...invalidIcuSource,
|
|
2735
|
+
...generation.withheld,
|
|
2736
|
+
...generation.providerFailures
|
|
2737
|
+
]);
|
|
2512
2738
|
return {
|
|
2513
2739
|
summary: baseSummary({
|
|
2514
2740
|
locale: params.targetLocale,
|
|
@@ -2519,6 +2745,8 @@ async function runLocale(params) {
|
|
|
2519
2745
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
2520
2746
|
// Withheld generated forms surface alongside withheld translations: both failed integrity.
|
|
2521
2747
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
2748
|
+
// A generation sub-batch whose provider call itself threw is a provider failure, never integrity.
|
|
2749
|
+
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
2522
2750
|
pruned,
|
|
2523
2751
|
notices
|
|
2524
2752
|
}),
|
|
@@ -2527,7 +2755,7 @@ async function runLocale(params) {
|
|
|
2527
2755
|
}
|
|
2528
2756
|
async function runGeneration(params, provider) {
|
|
2529
2757
|
if (!params.generatePlurals || provider.kind !== "llm") {
|
|
2530
|
-
return { accepted: [], withheld: [] };
|
|
2758
|
+
return { accepted: [], withheld: [], providerFailures: [], notices: [] };
|
|
2531
2759
|
}
|
|
2532
2760
|
return generatePluralForms({
|
|
2533
2761
|
source: params.source,
|
|
@@ -2538,7 +2766,8 @@ async function runGeneration(params, provider) {
|
|
|
2538
2766
|
provider,
|
|
2539
2767
|
glossary: params.glossary,
|
|
2540
2768
|
tone: params.tone,
|
|
2541
|
-
baseline: params.baseline
|
|
2769
|
+
baseline: params.baseline,
|
|
2770
|
+
maxBatchSize: params.maxBatchSize
|
|
2542
2771
|
});
|
|
2543
2772
|
}
|
|
2544
2773
|
function pluralNoticeFor(params, merged) {
|
|
@@ -2560,27 +2789,35 @@ function baseSummary(parts) {
|
|
|
2560
2789
|
pruned: parts.pruned,
|
|
2561
2790
|
invalidIcuSource: parts.invalidIcuSource,
|
|
2562
2791
|
integrityMismatches: parts.integrityMismatches,
|
|
2792
|
+
providerFailures: parts.providerFailures,
|
|
2563
2793
|
generated: parts.generated,
|
|
2564
2794
|
notices: parts.notices
|
|
2565
2795
|
};
|
|
2566
2796
|
}
|
|
2567
|
-
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches) {
|
|
2797
|
+
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches, providerFailures) {
|
|
2568
2798
|
const notices = [];
|
|
2569
2799
|
for (const batch of chunk(entries, params.maxBatchSize)) {
|
|
2570
|
-
const subNotices = await runSubBatch(
|
|
2800
|
+
const subNotices = await runSubBatch(
|
|
2801
|
+
provider,
|
|
2802
|
+
params,
|
|
2803
|
+
batch,
|
|
2804
|
+
accepted,
|
|
2805
|
+
integrityMismatches,
|
|
2806
|
+
providerFailures
|
|
2807
|
+
);
|
|
2571
2808
|
notices.push(...subNotices);
|
|
2572
2809
|
}
|
|
2573
2810
|
return notices;
|
|
2574
2811
|
}
|
|
2575
|
-
async function runSubBatch(provider, params, batch, accepted, integrityMismatches) {
|
|
2812
|
+
async function runSubBatch(provider, params, batch, accepted, integrityMismatches, providerFailures) {
|
|
2576
2813
|
let result;
|
|
2577
2814
|
try {
|
|
2578
2815
|
result = await provider.translateBatch(buildRequest3(params, batch));
|
|
2579
|
-
} catch {
|
|
2816
|
+
} catch (error) {
|
|
2580
2817
|
for (const entry of batch) {
|
|
2581
|
-
|
|
2818
|
+
providerFailures.push(entry.key);
|
|
2582
2819
|
}
|
|
2583
|
-
return [subBatchFailedNotice(batch.length)];
|
|
2820
|
+
return [subBatchFailedNotice(batch.length, error)];
|
|
2584
2821
|
}
|
|
2585
2822
|
for (const entry of batch) {
|
|
2586
2823
|
const value = result.values.get(entry.key);
|
|
@@ -2593,19 +2830,6 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
2593
2830
|
}
|
|
2594
2831
|
return readNotices(result);
|
|
2595
2832
|
}
|
|
2596
|
-
function subBatchFailedNotice(count) {
|
|
2597
|
-
return {
|
|
2598
|
-
code: "SUB_BATCH_FAILED",
|
|
2599
|
-
message: `A sub-batch of ${count} entries failed and was withheld; it will be retried next run.`
|
|
2600
|
-
};
|
|
2601
|
-
}
|
|
2602
|
-
function chunk(items, size) {
|
|
2603
|
-
const chunks = [];
|
|
2604
|
-
for (let index = 0; index < items.length; index += size) {
|
|
2605
|
-
chunks.push(items.slice(index, index + size));
|
|
2606
|
-
}
|
|
2607
|
-
return chunks;
|
|
2608
|
-
}
|
|
2609
2833
|
function computeLockEntries(params, merged, withheld, generated) {
|
|
2610
2834
|
const lockEntries = {};
|
|
2611
2835
|
const sourceBaseKeys = sourcePluralBaseKeys(params.source);
|
|
@@ -2753,6 +2977,7 @@ var COLUMN_WIDTHS = {
|
|
|
2753
2977
|
[COLUMN.status]: 12,
|
|
2754
2978
|
[COLUMN.translation]: 50
|
|
2755
2979
|
};
|
|
2980
|
+
var TEXT_NUMBER_FORMAT = "@";
|
|
2756
2981
|
function styleHeader(sheet) {
|
|
2757
2982
|
const header = sheet.getRow(HEADER_ROW);
|
|
2758
2983
|
HEADERS.forEach((label, index) => {
|
|
@@ -2768,6 +2993,7 @@ function applyColumnGeometry(sheet) {
|
|
|
2768
2993
|
sheet.getColumn(Number(column)).width = width;
|
|
2769
2994
|
}
|
|
2770
2995
|
sheet.getColumn(COLUMN.sourceHash).hidden = true;
|
|
2996
|
+
sheet.getColumn(COLUMN.translation).numFmt = TEXT_NUMBER_FORMAT;
|
|
2771
2997
|
sheet.views = [{ state: "frozen", ySplit: HEADER_ROW }];
|
|
2772
2998
|
}
|
|
2773
2999
|
function writeRow(sheet, sheetRow) {
|
|
@@ -2776,7 +3002,9 @@ function writeRow(sheet, sheetRow) {
|
|
|
2776
3002
|
row.getCell(COLUMN.source).value = sheetRow.source;
|
|
2777
3003
|
row.getCell(COLUMN.current).value = sheetRow.currentTarget;
|
|
2778
3004
|
row.getCell(COLUMN.status).value = sheetRow.status;
|
|
2779
|
-
row.getCell(COLUMN.translation)
|
|
3005
|
+
const translationCell = row.getCell(COLUMN.translation);
|
|
3006
|
+
translationCell.numFmt = TEXT_NUMBER_FORMAT;
|
|
3007
|
+
translationCell.value = sheetRow.translation === "" ? null : sheetRow.translation;
|
|
2780
3008
|
row.getCell(COLUMN.sourceHash).value = sheetRow.sourceHash;
|
|
2781
3009
|
for (let column = COLUMN.key; column <= COLUMN.sourceHash; column += 1) {
|
|
2782
3010
|
const cell = row.getCell(column);
|
|
@@ -3128,9 +3356,20 @@ function judge(row, sourceEntry, adapter) {
|
|
|
3128
3356
|
}
|
|
3129
3357
|
return void 0;
|
|
3130
3358
|
}
|
|
3359
|
+
function trackBlankDrift(row, params, buckets) {
|
|
3360
|
+
const sourceEntry = params.source.entries.get(row.key);
|
|
3361
|
+
if (sourceEntry === void 0) {
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
const priorHash = params.baseline.get(row.key);
|
|
3365
|
+
if (priorHash !== void 0 && priorHash !== contentHash(sourceEntry)) {
|
|
3366
|
+
buckets.blankDrifted.add(row.key);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3131
3369
|
function classifyRows(params, buckets) {
|
|
3132
3370
|
for (const row of params.sheet.rows) {
|
|
3133
3371
|
if (row.translation === "") {
|
|
3372
|
+
trackBlankDrift(row, params, buckets);
|
|
3134
3373
|
continue;
|
|
3135
3374
|
}
|
|
3136
3375
|
if (isUnknownKey(row, params.source, params.target)) {
|
|
@@ -3149,9 +3388,20 @@ function classifyRows(params, buckets) {
|
|
|
3149
3388
|
}
|
|
3150
3389
|
}
|
|
3151
3390
|
}
|
|
3391
|
+
function blankRowBaselineNotice(count) {
|
|
3392
|
+
return {
|
|
3393
|
+
code: "BLANK_ROW_BASELINE_RETAINED",
|
|
3394
|
+
message: `${count} row(s) were left blank for a key whose source changed since the row's baseline was recorded; the prior baseline was kept so the drift keeps being reported.`
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3152
3397
|
function importLocale(params) {
|
|
3153
3398
|
const diff2 = diffResources(params.source, params.target, { baseline: params.baseline });
|
|
3154
|
-
const buckets = {
|
|
3399
|
+
const buckets = {
|
|
3400
|
+
accepted: /* @__PURE__ */ new Map(),
|
|
3401
|
+
mismatches: [],
|
|
3402
|
+
withheld: /* @__PURE__ */ new Set(),
|
|
3403
|
+
blankDrifted: /* @__PURE__ */ new Set()
|
|
3404
|
+
};
|
|
3155
3405
|
classifyRows(params, buckets);
|
|
3156
3406
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
3157
3407
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
@@ -3165,9 +3415,11 @@ function importLocale(params) {
|
|
|
3165
3415
|
pruned: [],
|
|
3166
3416
|
invalidIcuSource,
|
|
3167
3417
|
integrityMismatches: [...buckets.mismatches].sort(),
|
|
3418
|
+
// A workbook import never calls a provider, so a provider-call failure cannot occur here.
|
|
3419
|
+
providerFailures: [],
|
|
3168
3420
|
// Plural generation is a translate-flow concern; the manual workbook import never generates forms.
|
|
3169
3421
|
generated: [],
|
|
3170
|
-
notices: []
|
|
3422
|
+
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : []
|
|
3171
3423
|
};
|
|
3172
3424
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
3173
3425
|
}
|
|
@@ -3201,21 +3453,19 @@ function mergeAccepted(target, accepted) {
|
|
|
3201
3453
|
}
|
|
3202
3454
|
return merged;
|
|
3203
3455
|
}
|
|
3204
|
-
function computeLockEntries2(source, merged, baseline,
|
|
3456
|
+
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
3205
3457
|
const entries = {};
|
|
3206
3458
|
for (const key of merged.keys()) {
|
|
3207
3459
|
const sourceEntry = source.entries.get(key);
|
|
3208
3460
|
if (sourceEntry === void 0) {
|
|
3209
3461
|
continue;
|
|
3210
3462
|
}
|
|
3211
|
-
if (
|
|
3212
|
-
|
|
3213
|
-
if (prior !== void 0) {
|
|
3214
|
-
entries[key] = prior;
|
|
3215
|
-
}
|
|
3463
|
+
if (accepted.has(key)) {
|
|
3464
|
+
entries[key] = contentHash(sourceEntry);
|
|
3216
3465
|
continue;
|
|
3217
3466
|
}
|
|
3218
|
-
|
|
3467
|
+
const prior = baseline.get(key);
|
|
3468
|
+
entries[key] = prior !== void 0 ? prior : contentHash(sourceEntry);
|
|
3219
3469
|
}
|
|
3220
3470
|
return entries;
|
|
3221
3471
|
}
|
|
@@ -3228,7 +3478,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
3228
3478
|
}
|
|
3229
3479
|
const target = await readTarget4(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
3230
3480
|
const baseline = baselineFor(lock, sheet.locale);
|
|
3231
|
-
const { summary, accepted
|
|
3481
|
+
const { summary, accepted } = importLocale({
|
|
3232
3482
|
sheet,
|
|
3233
3483
|
source: ctx.source,
|
|
3234
3484
|
target,
|
|
@@ -3252,7 +3502,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
3252
3502
|
path
|
|
3253
3503
|
);
|
|
3254
3504
|
}
|
|
3255
|
-
return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline,
|
|
3505
|
+
return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted) };
|
|
3256
3506
|
}
|
|
3257
3507
|
async function importWorkbook(input, deps = {}) {
|
|
3258
3508
|
const config = input.config;
|
|
@@ -3297,6 +3547,13 @@ async function importWorkbook(input, deps = {}) {
|
|
|
3297
3547
|
return { dryRun, locales: summaries, succeeded, failed };
|
|
3298
3548
|
}
|
|
3299
3549
|
|
|
3550
|
+
// src/lock/load-lock-file.ts
|
|
3551
|
+
async function loadLockFile(input = {}, deps = {}) {
|
|
3552
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3553
|
+
const fs = deps.fs ?? defaultFs;
|
|
3554
|
+
return readLockFile(lockFilePath(cwd), fs);
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3300
3557
|
// src/scaffolding.ts
|
|
3301
3558
|
var scaffoldingMetadata = {
|
|
3302
3559
|
/** Provider id -> the environment variable its API key is read from. Owned by ai-providers. */
|
|
@@ -3412,6 +3669,6 @@ async function watch(input, deps = {}) {
|
|
|
3412
3669
|
return { stop };
|
|
3413
3670
|
}
|
|
3414
3671
|
|
|
3415
|
-
export { DEFAULT_WORKBOOK_PATH, SdkError, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
3672
|
+
export { DEFAULT_WORKBOOK_PATH, LOCK_FILE_NAME, SdkError, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, loadConfigWithMeta, loadLockFile, lockState, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
3416
3673
|
//# sourceMappingURL=index.js.map
|
|
3417
3674
|
//# sourceMappingURL=index.js.map
|