@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.cjs
CHANGED
|
@@ -4,6 +4,8 @@ var fs = require('fs');
|
|
|
4
4
|
var path = require('path');
|
|
5
5
|
var cosmiconfig = require('cosmiconfig');
|
|
6
6
|
var cosmiconfigTypescriptLoader = require('cosmiconfig-typescript-loader');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
var promises = require('fs/promises');
|
|
7
9
|
var zod = require('zod');
|
|
8
10
|
var Anthropic = require('@anthropic-ai/sdk');
|
|
9
11
|
var deepl = require('deepl-node');
|
|
@@ -11,8 +13,6 @@ var module$1 = require('module');
|
|
|
11
13
|
var log = require('loglevel');
|
|
12
14
|
var genai = require('@google/genai');
|
|
13
15
|
var OpenAI = require('openai');
|
|
14
|
-
var crypto = require('crypto');
|
|
15
|
-
var promises = require('fs/promises');
|
|
16
16
|
var icuMessageformatParser = require('@formatjs/icu-messageformat-parser');
|
|
17
17
|
var xmldom = require('@xmldom/xmldom');
|
|
18
18
|
var yaml = require('yaml');
|
|
@@ -68,6 +68,155 @@ var SdkError = class extends Error {
|
|
|
68
68
|
this.code = code;
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
+
async function readBoundedUtf8(handle, size) {
|
|
72
|
+
const buffer = Buffer.allocUnsafe(size);
|
|
73
|
+
let offset = 0;
|
|
74
|
+
while (offset < size) {
|
|
75
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
76
|
+
if (bytesRead === 0) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
offset += bytesRead;
|
|
80
|
+
}
|
|
81
|
+
return buffer.toString("utf8", 0, offset);
|
|
82
|
+
}
|
|
83
|
+
async function readBounded(path, maxBytes) {
|
|
84
|
+
let handle;
|
|
85
|
+
try {
|
|
86
|
+
handle = await promises.open(path, "r");
|
|
87
|
+
} catch {
|
|
88
|
+
return { kind: "missing" };
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const info = await handle.stat();
|
|
92
|
+
if (!info.isFile()) {
|
|
93
|
+
return { kind: "missing" };
|
|
94
|
+
}
|
|
95
|
+
if (info.size > maxBytes) {
|
|
96
|
+
return { kind: "too-large" };
|
|
97
|
+
}
|
|
98
|
+
return { kind: "ok", content: await readBoundedUtf8(handle, info.size) };
|
|
99
|
+
} finally {
|
|
100
|
+
await handle.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function readBoundedBytesInto(handle, size) {
|
|
104
|
+
const buffer = Buffer.allocUnsafeSlow(size);
|
|
105
|
+
let offset = 0;
|
|
106
|
+
while (offset < size) {
|
|
107
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
108
|
+
if (bytesRead === 0) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
offset += bytesRead;
|
|
112
|
+
}
|
|
113
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, offset);
|
|
114
|
+
}
|
|
115
|
+
async function readBoundedBytes(path, maxBytes) {
|
|
116
|
+
let handle;
|
|
117
|
+
try {
|
|
118
|
+
handle = await promises.open(path, "r");
|
|
119
|
+
} catch {
|
|
120
|
+
return { kind: "missing" };
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const info = await handle.stat();
|
|
124
|
+
if (!info.isFile()) {
|
|
125
|
+
return { kind: "missing" };
|
|
126
|
+
}
|
|
127
|
+
if (info.size > maxBytes) {
|
|
128
|
+
return { kind: "too-large" };
|
|
129
|
+
}
|
|
130
|
+
return { kind: "ok", bytes: await readBoundedBytesInto(handle, info.size) };
|
|
131
|
+
} finally {
|
|
132
|
+
await handle.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function tempFileName(path$1) {
|
|
136
|
+
return path.join(path.dirname(path$1), `.${path.basename(path$1)}.tmp-${process.pid}-${Date.now()}-${crypto.randomUUID()}`);
|
|
137
|
+
}
|
|
138
|
+
async function atomicWrite(path, data) {
|
|
139
|
+
const tmp = tempFileName(path);
|
|
140
|
+
await (typeof data === "string" ? promises.writeFile(tmp, data, "utf8") : promises.writeFile(tmp, data));
|
|
141
|
+
try {
|
|
142
|
+
await promises.rename(tmp, path);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
await promises.rm(tmp, { force: true });
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
var defaultFs = {
|
|
149
|
+
async fileExists(path) {
|
|
150
|
+
try {
|
|
151
|
+
await promises.access(path);
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
readFileBounded: (path, maxBytes) => readBounded(path, maxBytes),
|
|
158
|
+
readBytesBounded: (path, maxBytes) => readBoundedBytes(path, maxBytes),
|
|
159
|
+
writeFile: (path, data) => atomicWrite(path, data),
|
|
160
|
+
writeBytes: (path, data) => atomicWrite(path, data)
|
|
161
|
+
};
|
|
162
|
+
var MAX_GLOSSARY_FILE_BYTES = 1024 * 1024;
|
|
163
|
+
var glossaryRecordSchema = zod.z.record(zod.z.string(), zod.z.string());
|
|
164
|
+
var BOM = "\uFEFF";
|
|
165
|
+
var REPLACEMENT_CHARACTER = "\uFFFD";
|
|
166
|
+
var NUL = "\0";
|
|
167
|
+
function stripBom(content) {
|
|
168
|
+
return content.startsWith(BOM) ? content.slice(BOM.length) : content;
|
|
169
|
+
}
|
|
170
|
+
function looksLikeInvalidEncoding(content) {
|
|
171
|
+
return content.startsWith(REPLACEMENT_CHARACTER) || content.includes(NUL);
|
|
172
|
+
}
|
|
173
|
+
async function readGlossaryFile(resolvedPath, fs) {
|
|
174
|
+
const read = await fs.readFileBounded(resolvedPath, MAX_GLOSSARY_FILE_BYTES);
|
|
175
|
+
if (read.kind === "missing") {
|
|
176
|
+
throw new SdkError(
|
|
177
|
+
"CONFIG_INVALID",
|
|
178
|
+
`The glossary file at ${resolvedPath} was not found or could not be read.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (read.kind === "too-large") {
|
|
182
|
+
throw new SdkError(
|
|
183
|
+
"CONFIG_INVALID",
|
|
184
|
+
`The glossary file at ${resolvedPath} exceeds the maximum allowed size of ${MAX_GLOSSARY_FILE_BYTES} bytes.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const content = stripBom(read.content);
|
|
188
|
+
if (looksLikeInvalidEncoding(content)) {
|
|
189
|
+
throw new SdkError(
|
|
190
|
+
"CONFIG_INVALID",
|
|
191
|
+
`The glossary file at ${resolvedPath} must be UTF-8 encoded.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
let parsed;
|
|
195
|
+
try {
|
|
196
|
+
parsed = JSON.parse(content);
|
|
197
|
+
} catch {
|
|
198
|
+
throw new SdkError("CONFIG_INVALID", `The glossary file at ${resolvedPath} is not valid JSON.`);
|
|
199
|
+
}
|
|
200
|
+
const result = glossaryRecordSchema.safeParse(parsed);
|
|
201
|
+
if (!result.success) {
|
|
202
|
+
throw new SdkError(
|
|
203
|
+
"CONFIG_INVALID",
|
|
204
|
+
`The glossary file at ${resolvedPath} must contain a flat object of string keys to string values.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return result.data;
|
|
208
|
+
}
|
|
209
|
+
async function resolveGlossary(glossary, baseDir, fs) {
|
|
210
|
+
if (glossary === void 0) {
|
|
211
|
+
return { glossary: void 0, provenance: { source: "none" } };
|
|
212
|
+
}
|
|
213
|
+
if (typeof glossary === "string") {
|
|
214
|
+
const resolvedPath = path.resolve(baseDir, glossary);
|
|
215
|
+
const record = await readGlossaryFile(resolvedPath, fs);
|
|
216
|
+
return { glossary: record, provenance: { source: "file", path: resolvedPath } };
|
|
217
|
+
}
|
|
218
|
+
return { glossary, provenance: { source: "inline" } };
|
|
219
|
+
}
|
|
71
220
|
var FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
72
221
|
var FNV_PRIME = 1099511628211n;
|
|
73
222
|
var U64_MASK = (1n << 64n) - 1n;
|
|
@@ -903,7 +1052,13 @@ var verbatraConfigSchema = zod.z.strictObject({
|
|
|
903
1052
|
pattern: zod.z.string().min(1)
|
|
904
1053
|
}),
|
|
905
1054
|
provider: providerConfigSchema,
|
|
906
|
-
|
|
1055
|
+
/**
|
|
1056
|
+
* A glossary of source terms to preferred target terms, either inline or as a path to a JSON file
|
|
1057
|
+
* carrying the same shape. The two forms are mutually exclusive by type; there is no merge or
|
|
1058
|
+
* precedence between them. A path is resolved and validated by the loader, not by this schema; see
|
|
1059
|
+
* `resolve-glossary.ts`.
|
|
1060
|
+
*/
|
|
1061
|
+
glossary: zod.z.union([zod.z.record(zod.z.string(), zod.z.string()), zod.z.string().min(1)]).optional(),
|
|
907
1062
|
tone: zod.z.enum(["formal", "informal", "neutral"]).optional(),
|
|
908
1063
|
/**
|
|
909
1064
|
* Opt-in orphan pruning, off by default. When true, keys present in a target file but absent from
|
|
@@ -957,7 +1112,7 @@ function formatIssues(error) {
|
|
|
957
1112
|
return issue.code === "unrecognized_keys" ? `${base} (API keys are read from the environment, not the config)` : base;
|
|
958
1113
|
}).join("; ");
|
|
959
1114
|
}
|
|
960
|
-
function
|
|
1115
|
+
function parseConfig(input) {
|
|
961
1116
|
const parsed = verbatraConfigSchema.safeParse(input);
|
|
962
1117
|
if (!parsed.success) {
|
|
963
1118
|
throw new SdkError(
|
|
@@ -967,7 +1122,16 @@ function validate(input) {
|
|
|
967
1122
|
}
|
|
968
1123
|
return parsed.data;
|
|
969
1124
|
}
|
|
970
|
-
async function
|
|
1125
|
+
async function finalizeConfig(parsed, baseDir, fs) {
|
|
1126
|
+
const { glossary: glossaryInput, ...rest } = parsed;
|
|
1127
|
+
const resolved = await resolveGlossary(glossaryInput, baseDir, fs);
|
|
1128
|
+
const config = {
|
|
1129
|
+
...rest,
|
|
1130
|
+
...resolved.glossary !== void 0 ? { glossary: resolved.glossary } : {}
|
|
1131
|
+
};
|
|
1132
|
+
return { config, glossary: resolved.provenance };
|
|
1133
|
+
}
|
|
1134
|
+
async function loadExplicitWithMeta(explorer, configPath, cwd, fs$1) {
|
|
971
1135
|
const resolved = path.resolve(cwd ?? process.cwd(), configPath);
|
|
972
1136
|
if (!fs.existsSync(resolved)) {
|
|
973
1137
|
throw new SdkError("CONFIG_NOT_FOUND", `No verbatra configuration file at ${resolved}.`);
|
|
@@ -979,18 +1143,23 @@ async function loadExplicit(explorer, configPath, cwd) {
|
|
|
979
1143
|
const detail = error instanceof Error ? error.message : String(error);
|
|
980
1144
|
throw new SdkError("CONFIG_INVALID", `Failed to load the verbatra configuration: ${detail}`);
|
|
981
1145
|
}
|
|
982
|
-
|
|
1146
|
+
const parsed = parseConfig(result?.config);
|
|
1147
|
+
const { config, glossary } = await finalizeConfig(parsed, path.dirname(resolved), fs$1);
|
|
1148
|
+
return { config, source: { kind: "explicit", filepath: resolved }, glossary };
|
|
983
1149
|
}
|
|
984
|
-
async function
|
|
1150
|
+
async function loadConfigWithMeta(options = {}) {
|
|
1151
|
+
const fs = options.fs ?? defaultFs;
|
|
985
1152
|
if (options.configOverride !== void 0) {
|
|
986
|
-
|
|
1153
|
+
const parsed2 = parseConfig(options.configOverride);
|
|
1154
|
+
const { config: config2, glossary: glossary2 } = await finalizeConfig(parsed2, options.cwd ?? process.cwd(), fs);
|
|
1155
|
+
return { config: config2, source: { kind: "override" }, glossary: glossary2 };
|
|
987
1156
|
}
|
|
988
1157
|
const explorer = cosmiconfig.cosmiconfig(MODULE_NAME, {
|
|
989
1158
|
searchPlaces: SEARCH_PLACES,
|
|
990
1159
|
loaders: { ".ts": cosmiconfigTypescriptLoader.TypeScriptLoader() }
|
|
991
1160
|
});
|
|
992
1161
|
if (options.configPath !== void 0) {
|
|
993
|
-
return
|
|
1162
|
+
return loadExplicitWithMeta(explorer, options.configPath, options.cwd, fs);
|
|
994
1163
|
}
|
|
995
1164
|
let result;
|
|
996
1165
|
try {
|
|
@@ -1005,99 +1174,14 @@ async function loadConfig(options = {}) {
|
|
|
1005
1174
|
"No verbatra configuration found. Create a verbatra.config.ts, a .verbatrarc.json, or a 'verbatra' property in package.json."
|
|
1006
1175
|
);
|
|
1007
1176
|
}
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const buffer = Buffer.allocUnsafe(size);
|
|
1012
|
-
let offset = 0;
|
|
1013
|
-
while (offset < size) {
|
|
1014
|
-
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
1015
|
-
if (bytesRead === 0) {
|
|
1016
|
-
break;
|
|
1017
|
-
}
|
|
1018
|
-
offset += bytesRead;
|
|
1019
|
-
}
|
|
1020
|
-
return buffer.toString("utf8", 0, offset);
|
|
1021
|
-
}
|
|
1022
|
-
async function readBounded(path, maxBytes) {
|
|
1023
|
-
let handle;
|
|
1024
|
-
try {
|
|
1025
|
-
handle = await promises.open(path, "r");
|
|
1026
|
-
} catch {
|
|
1027
|
-
return { kind: "missing" };
|
|
1028
|
-
}
|
|
1029
|
-
try {
|
|
1030
|
-
const info = await handle.stat();
|
|
1031
|
-
if (!info.isFile()) {
|
|
1032
|
-
return { kind: "missing" };
|
|
1033
|
-
}
|
|
1034
|
-
if (info.size > maxBytes) {
|
|
1035
|
-
return { kind: "too-large" };
|
|
1036
|
-
}
|
|
1037
|
-
return { kind: "ok", content: await readBoundedUtf8(handle, info.size) };
|
|
1038
|
-
} finally {
|
|
1039
|
-
await handle.close();
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
async function readBoundedBytesInto(handle, size) {
|
|
1043
|
-
const buffer = Buffer.allocUnsafeSlow(size);
|
|
1044
|
-
let offset = 0;
|
|
1045
|
-
while (offset < size) {
|
|
1046
|
-
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
1047
|
-
if (bytesRead === 0) {
|
|
1048
|
-
break;
|
|
1049
|
-
}
|
|
1050
|
-
offset += bytesRead;
|
|
1051
|
-
}
|
|
1052
|
-
return new Uint8Array(buffer.buffer, buffer.byteOffset, offset);
|
|
1053
|
-
}
|
|
1054
|
-
async function readBoundedBytes(path, maxBytes) {
|
|
1055
|
-
let handle;
|
|
1056
|
-
try {
|
|
1057
|
-
handle = await promises.open(path, "r");
|
|
1058
|
-
} catch {
|
|
1059
|
-
return { kind: "missing" };
|
|
1060
|
-
}
|
|
1061
|
-
try {
|
|
1062
|
-
const info = await handle.stat();
|
|
1063
|
-
if (!info.isFile()) {
|
|
1064
|
-
return { kind: "missing" };
|
|
1065
|
-
}
|
|
1066
|
-
if (info.size > maxBytes) {
|
|
1067
|
-
return { kind: "too-large" };
|
|
1068
|
-
}
|
|
1069
|
-
return { kind: "ok", bytes: await readBoundedBytesInto(handle, info.size) };
|
|
1070
|
-
} finally {
|
|
1071
|
-
await handle.close();
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
function tempFileName(path$1) {
|
|
1075
|
-
return path.join(path.dirname(path$1), `.${path.basename(path$1)}.tmp-${process.pid}-${Date.now()}-${crypto.randomUUID()}`);
|
|
1177
|
+
const parsed = parseConfig(result.config);
|
|
1178
|
+
const { config, glossary } = await finalizeConfig(parsed, path.dirname(result.filepath), fs);
|
|
1179
|
+
return { config, source: { kind: "search", filepath: result.filepath }, glossary };
|
|
1076
1180
|
}
|
|
1077
|
-
async function
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
try {
|
|
1081
|
-
await promises.rename(tmp, path);
|
|
1082
|
-
} catch (error) {
|
|
1083
|
-
await promises.rm(tmp, { force: true });
|
|
1084
|
-
throw error;
|
|
1085
|
-
}
|
|
1181
|
+
async function loadConfig(options = {}) {
|
|
1182
|
+
const { config } = await loadConfigWithMeta(options);
|
|
1183
|
+
return config;
|
|
1086
1184
|
}
|
|
1087
|
-
var defaultFs = {
|
|
1088
|
-
async fileExists(path) {
|
|
1089
|
-
try {
|
|
1090
|
-
await promises.access(path);
|
|
1091
|
-
return true;
|
|
1092
|
-
} catch {
|
|
1093
|
-
return false;
|
|
1094
|
-
}
|
|
1095
|
-
},
|
|
1096
|
-
readFileBounded: (path, maxBytes) => readBounded(path, maxBytes),
|
|
1097
|
-
readBytesBounded: (path, maxBytes) => readBoundedBytes(path, maxBytes),
|
|
1098
|
-
writeFile: (path, data) => atomicWrite(path, data),
|
|
1099
|
-
writeBytes: (path, data) => atomicWrite(path, data)
|
|
1100
|
-
};
|
|
1101
1185
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1102
1186
|
var CURRENT_VERSION = 1;
|
|
1103
1187
|
var EMPTY_LOCK = { version: CURRENT_VERSION, locales: {} };
|
|
@@ -1182,19 +1266,53 @@ function childMessages(element) {
|
|
|
1182
1266
|
}
|
|
1183
1267
|
return [];
|
|
1184
1268
|
}
|
|
1185
|
-
function
|
|
1269
|
+
function addCount(target, token, count) {
|
|
1270
|
+
target.set(token, (target.get(token) ?? 0) + count);
|
|
1271
|
+
}
|
|
1272
|
+
function combineBranches(branchMultisets) {
|
|
1273
|
+
const combined = /* @__PURE__ */ new Map();
|
|
1274
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1275
|
+
for (const branch of branchMultisets) {
|
|
1276
|
+
for (const token of branch.keys()) {
|
|
1277
|
+
tokens.add(token);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
for (const token of tokens) {
|
|
1281
|
+
const minCount = Math.min(...branchMultisets.map((branch) => branch.get(token) ?? 0));
|
|
1282
|
+
if (minCount > 0) {
|
|
1283
|
+
combined.set(token, minCount);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
return combined;
|
|
1287
|
+
}
|
|
1288
|
+
function collect(elements, state) {
|
|
1289
|
+
const result = /* @__PURE__ */ new Map();
|
|
1186
1290
|
for (const element of elements) {
|
|
1187
1291
|
const token = tokenOf(element);
|
|
1188
1292
|
if (token !== void 0) {
|
|
1189
|
-
|
|
1293
|
+
addCount(result, token, 1);
|
|
1190
1294
|
}
|
|
1191
1295
|
if (element.type === icuMessageformatParser.TYPE.plural) {
|
|
1192
1296
|
state.isPlural = true;
|
|
1193
1297
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1298
|
+
const branches = childMessages(element);
|
|
1299
|
+
if (branches.length > 0) {
|
|
1300
|
+
const branchMultisets = branches.map((branch) => collect(branch, state));
|
|
1301
|
+
for (const [branchToken, branchCount] of combineBranches(branchMultisets)) {
|
|
1302
|
+
addCount(result, branchToken, branchCount);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return result;
|
|
1307
|
+
}
|
|
1308
|
+
function flatten(multiset) {
|
|
1309
|
+
const flattened = [];
|
|
1310
|
+
for (const [token, count] of multiset) {
|
|
1311
|
+
for (let i = 0; i < count; i += 1) {
|
|
1312
|
+
flattened.push(token);
|
|
1196
1313
|
}
|
|
1197
1314
|
}
|
|
1315
|
+
return flattened;
|
|
1198
1316
|
}
|
|
1199
1317
|
function analyzeIcuValue(value) {
|
|
1200
1318
|
if (!value.includes("{") && !value.includes("<")) {
|
|
@@ -1202,16 +1320,9 @@ function analyzeIcuValue(value) {
|
|
|
1202
1320
|
}
|
|
1203
1321
|
try {
|
|
1204
1322
|
const ast = icuMessageformatParser.parse(value);
|
|
1205
|
-
const placeholders = [];
|
|
1206
1323
|
const state = { isPlural: false };
|
|
1207
|
-
collect(
|
|
1208
|
-
|
|
1209
|
-
(token) => {
|
|
1210
|
-
placeholders.push(token);
|
|
1211
|
-
},
|
|
1212
|
-
state
|
|
1213
|
-
);
|
|
1214
|
-
return { placeholders, isPlural: state.isPlural, valid: true };
|
|
1324
|
+
const multiset = collect(ast, state);
|
|
1325
|
+
return { placeholders: flatten(multiset), isPlural: state.isPlural, valid: true };
|
|
1215
1326
|
} catch {
|
|
1216
1327
|
return INVALID;
|
|
1217
1328
|
}
|
|
@@ -1473,21 +1584,28 @@ function addEntries(ctx, prefix, node) {
|
|
|
1473
1584
|
}
|
|
1474
1585
|
}
|
|
1475
1586
|
}
|
|
1476
|
-
function addPathEntries(node, prefix, namespace, derive, out) {
|
|
1587
|
+
function addPathEntries(node, prefix, namespace, derive, out, claimedPaths) {
|
|
1477
1588
|
for (const [key, value] of Object.entries(node)) {
|
|
1478
1589
|
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
1590
|
+
if (claimedPaths.has(path)) {
|
|
1591
|
+
throw new AdapterError(
|
|
1592
|
+
"INVALID_STRUCTURE",
|
|
1593
|
+
"A dotted key and a nested key path resolve to the same path."
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
claimedPaths.add(path);
|
|
1479
1597
|
if (typeof value === "string") {
|
|
1480
1598
|
const { placeholders, isPlural } = derive(key, value);
|
|
1481
1599
|
out.set(path, { key: path, namespace, value, placeholders, isPlural });
|
|
1482
1600
|
} else {
|
|
1483
|
-
addPathEntries(value, path, namespace, derive, out);
|
|
1601
|
+
addPathEntries(value, path, namespace, derive, out, claimedPaths);
|
|
1484
1602
|
}
|
|
1485
1603
|
}
|
|
1486
1604
|
}
|
|
1487
1605
|
function flattenTree(tree, namespace, derive, keyMode = "literal-leaf") {
|
|
1488
1606
|
const out = /* @__PURE__ */ new Map();
|
|
1489
1607
|
if (keyMode === "path-notation") {
|
|
1490
|
-
addPathEntries(tree, "", namespace, derive, out);
|
|
1608
|
+
addPathEntries(tree, "", namespace, derive, out, /* @__PURE__ */ new Set());
|
|
1491
1609
|
return out;
|
|
1492
1610
|
}
|
|
1493
1611
|
addEntries({ namespace, derive, out, claimed: /* @__PURE__ */ new Map() }, [], tree);
|
|
@@ -1609,21 +1727,20 @@ function messagesFromEntries(entries) {
|
|
|
1609
1727
|
}
|
|
1610
1728
|
return out;
|
|
1611
1729
|
}
|
|
1730
|
+
function isEnoent(error) {
|
|
1731
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1732
|
+
}
|
|
1612
1733
|
async function readDestinationPairs(filePath) {
|
|
1613
|
-
let
|
|
1734
|
+
let content;
|
|
1614
1735
|
try {
|
|
1615
|
-
|
|
1616
|
-
|
|
1736
|
+
content = await readFileContent(filePath);
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
if (isEnoent(error)) {
|
|
1617
1739
|
return null;
|
|
1618
1740
|
}
|
|
1619
|
-
|
|
1620
|
-
} catch {
|
|
1621
|
-
return null;
|
|
1622
|
-
}
|
|
1623
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1624
|
-
return null;
|
|
1741
|
+
throw error;
|
|
1625
1742
|
}
|
|
1626
|
-
return Object.entries(
|
|
1743
|
+
return Object.entries(parseArbObject(content));
|
|
1627
1744
|
}
|
|
1628
1745
|
async function buildArbWriteTree(entries, filePath) {
|
|
1629
1746
|
const messages = messagesFromEntries(entries);
|
|
@@ -1724,6 +1841,20 @@ function createNextIntlJsonAdapter() {
|
|
|
1724
1841
|
validateMessage: icuIsValid
|
|
1725
1842
|
});
|
|
1726
1843
|
}
|
|
1844
|
+
function assertNoDottedNestedKey(tree) {
|
|
1845
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
1846
|
+
if (typeof value !== "object") {
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
if (key.includes(".")) {
|
|
1850
|
+
throw new AdapterError(
|
|
1851
|
+
"MIXED_STRUCTURE",
|
|
1852
|
+
"A nested object key contains a literal dot, which is ambiguous with a dotted path."
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
assertNoDottedNestedKey(value);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1727
1858
|
function assertNotMixed(tree) {
|
|
1728
1859
|
let hasNested = false;
|
|
1729
1860
|
let hasFlatDottedKey = false;
|
|
@@ -1740,6 +1871,7 @@ function assertNotMixed(tree) {
|
|
|
1740
1871
|
"The file mixes flat dotted keys with nested objects."
|
|
1741
1872
|
);
|
|
1742
1873
|
}
|
|
1874
|
+
assertNoDottedNestedKey(tree);
|
|
1743
1875
|
}
|
|
1744
1876
|
async function detectStyle(filePath) {
|
|
1745
1877
|
let parsed;
|
|
@@ -2206,6 +2338,40 @@ async function diff(input, deps = {}) {
|
|
|
2206
2338
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
2207
2339
|
}
|
|
2208
2340
|
|
|
2341
|
+
// src/flow/lock-state.ts
|
|
2342
|
+
function toLockLocaleState(locale, keyCount, diff2) {
|
|
2343
|
+
return {
|
|
2344
|
+
locale,
|
|
2345
|
+
keyCount,
|
|
2346
|
+
missing: diff2.missing.length,
|
|
2347
|
+
stale: diff2.changed.length,
|
|
2348
|
+
upToDate: diff2.unchanged.length
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
async function lockState(input, deps = {}) {
|
|
2352
|
+
const config = input.config;
|
|
2353
|
+
const cwd = input.cwd ?? process.cwd();
|
|
2354
|
+
const fs = deps.fs ?? defaultFs;
|
|
2355
|
+
const locales = selectLocales(config, input.locales);
|
|
2356
|
+
const path = lockFilePath(cwd);
|
|
2357
|
+
const exists = await fs.fileExists(path);
|
|
2358
|
+
if (!exists) {
|
|
2359
|
+
return { exists: false };
|
|
2360
|
+
}
|
|
2361
|
+
const lock = await readLockFile(path, fs);
|
|
2362
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
2363
|
+
const source = await readSource(config, cwd, fs, adapter);
|
|
2364
|
+
const localeStates = await Promise.all(
|
|
2365
|
+
locales.map(async (locale) => {
|
|
2366
|
+
const target = await readTarget(cwd, config, adapter, fs, locale);
|
|
2367
|
+
const baseline = baselineFor(lock, locale);
|
|
2368
|
+
const diff2 = diffResources(source.resource, target, { baseline });
|
|
2369
|
+
return toLockLocaleState(locale, baseline.size, diff2);
|
|
2370
|
+
})
|
|
2371
|
+
);
|
|
2372
|
+
return { exists: true, version: lock.version, locales: localeStates };
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2209
2375
|
// src/selection/select-provider.ts
|
|
2210
2376
|
function selectProvider(config, createProvider = buildProvider) {
|
|
2211
2377
|
try {
|
|
@@ -2237,6 +2403,7 @@ function failureSummary(locale, error) {
|
|
|
2237
2403
|
pruned: [],
|
|
2238
2404
|
invalidIcuSource: [],
|
|
2239
2405
|
integrityMismatches: [],
|
|
2406
|
+
providerFailures: [],
|
|
2240
2407
|
generated: [],
|
|
2241
2408
|
notices: [],
|
|
2242
2409
|
error: describeError(error)
|
|
@@ -2248,6 +2415,29 @@ function partition(locales) {
|
|
|
2248
2415
|
return { succeeded, failed };
|
|
2249
2416
|
}
|
|
2250
2417
|
|
|
2418
|
+
// src/flow/batching.ts
|
|
2419
|
+
function chunk(items, size) {
|
|
2420
|
+
const chunks = [];
|
|
2421
|
+
for (let index = 0; index < items.length; index += size) {
|
|
2422
|
+
chunks.push(items.slice(index, index + size));
|
|
2423
|
+
}
|
|
2424
|
+
return chunks;
|
|
2425
|
+
}
|
|
2426
|
+
var GENERIC_PROVIDER_FAILURE_MESSAGE = "The provider call failed.";
|
|
2427
|
+
function classifyProviderFailure(error) {
|
|
2428
|
+
if (error instanceof ProviderError) {
|
|
2429
|
+
return { code: error.code, message: error.message };
|
|
2430
|
+
}
|
|
2431
|
+
return { code: "PROVIDER_CALL_FAILED", message: GENERIC_PROVIDER_FAILURE_MESSAGE };
|
|
2432
|
+
}
|
|
2433
|
+
function subBatchFailedNotice(count, error) {
|
|
2434
|
+
const { code, message } = classifyProviderFailure(error);
|
|
2435
|
+
return {
|
|
2436
|
+
code: "SUB_BATCH_FAILED",
|
|
2437
|
+
message: `A sub-batch of ${count} entries failed (${code}: ${message}) and was withheld; it will be retried next run.`
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2251
2441
|
// src/flow/notices.ts
|
|
2252
2442
|
function isNotice(value) {
|
|
2253
2443
|
return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
|
|
@@ -2430,13 +2620,36 @@ async function generatePluralForms(context) {
|
|
|
2430
2620
|
const plan = planPluralGeneration(context.source, context.targetLocale, context.format);
|
|
2431
2621
|
const stale = staleItems(plan.items, context.baseline);
|
|
2432
2622
|
if (stale.length === 0) {
|
|
2433
|
-
return { accepted: [], withheld: [] };
|
|
2623
|
+
return { accepted: [], withheld: [], providerFailures: [], notices: [] };
|
|
2434
2624
|
}
|
|
2435
|
-
const entries = stale.map(syntheticEntry);
|
|
2436
|
-
const result = await context.provider.translateBatch(buildRequest2(context, entries));
|
|
2437
2625
|
const accepted = [];
|
|
2438
2626
|
const withheld = [];
|
|
2439
|
-
|
|
2627
|
+
const providerFailures = [];
|
|
2628
|
+
const notices = [];
|
|
2629
|
+
for (const batch of chunk(stale, context.maxBatchSize)) {
|
|
2630
|
+
const batchNotices = await runGenerationSubBatch(
|
|
2631
|
+
context,
|
|
2632
|
+
batch,
|
|
2633
|
+
accepted,
|
|
2634
|
+
withheld,
|
|
2635
|
+
providerFailures
|
|
2636
|
+
);
|
|
2637
|
+
notices.push(...batchNotices);
|
|
2638
|
+
}
|
|
2639
|
+
return { accepted, withheld, providerFailures, notices };
|
|
2640
|
+
}
|
|
2641
|
+
async function runGenerationSubBatch(context, batch, accepted, withheld, providerFailures) {
|
|
2642
|
+
let result;
|
|
2643
|
+
try {
|
|
2644
|
+
const entries = batch.map(syntheticEntry);
|
|
2645
|
+
result = await context.provider.translateBatch(buildRequest2(context, entries));
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
for (const item of batch) {
|
|
2648
|
+
providerFailures.push(item.targetKey);
|
|
2649
|
+
}
|
|
2650
|
+
return [subBatchFailedNotice(batch.length, error)];
|
|
2651
|
+
}
|
|
2652
|
+
for (const item of batch) {
|
|
2440
2653
|
const value = result.values.get(item.targetKey);
|
|
2441
2654
|
const integrity = result.integrity.get(item.targetKey);
|
|
2442
2655
|
if (value !== void 0 && integrity?.matches === true) {
|
|
@@ -2449,7 +2662,7 @@ async function generatePluralForms(context) {
|
|
|
2449
2662
|
withheld.push(item.targetKey);
|
|
2450
2663
|
}
|
|
2451
2664
|
}
|
|
2452
|
-
return
|
|
2665
|
+
return readNotices(result);
|
|
2453
2666
|
}
|
|
2454
2667
|
|
|
2455
2668
|
// src/flow/locale-run.ts
|
|
@@ -2499,6 +2712,7 @@ async function runLocale(params) {
|
|
|
2499
2712
|
translated: toTranslate,
|
|
2500
2713
|
generated: [],
|
|
2501
2714
|
integrityMismatches: [],
|
|
2715
|
+
providerFailures: [],
|
|
2502
2716
|
pruned,
|
|
2503
2717
|
notices: sdkNotices
|
|
2504
2718
|
}),
|
|
@@ -2508,12 +2722,14 @@ async function runLocale(params) {
|
|
|
2508
2722
|
const entries = toTranslate.map((key) => params.source.entries.get(key)).filter((entry) => entry !== void 0);
|
|
2509
2723
|
const accepted = /* @__PURE__ */ new Map();
|
|
2510
2724
|
const integrityMismatches = [];
|
|
2725
|
+
const providerFailures = [];
|
|
2511
2726
|
const subBatchNotices = await translateAndCheck(
|
|
2512
2727
|
provider,
|
|
2513
2728
|
params,
|
|
2514
2729
|
entries,
|
|
2515
2730
|
accepted,
|
|
2516
|
-
integrityMismatches
|
|
2731
|
+
integrityMismatches,
|
|
2732
|
+
providerFailures
|
|
2517
2733
|
);
|
|
2518
2734
|
const merged = new Map(target.entries);
|
|
2519
2735
|
for (const key of pruned) {
|
|
@@ -2537,8 +2753,18 @@ async function runLocale(params) {
|
|
|
2537
2753
|
path
|
|
2538
2754
|
);
|
|
2539
2755
|
const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
|
|
2540
|
-
const notices = [
|
|
2541
|
-
|
|
2756
|
+
const notices = [
|
|
2757
|
+
...pluralNotices,
|
|
2758
|
+
...subBatchNotices,
|
|
2759
|
+
...generation.notices
|
|
2760
|
+
];
|
|
2761
|
+
const withheld = /* @__PURE__ */ new Set([
|
|
2762
|
+
...integrityMismatches,
|
|
2763
|
+
...providerFailures,
|
|
2764
|
+
...invalidIcuSource,
|
|
2765
|
+
...generation.withheld,
|
|
2766
|
+
...generation.providerFailures
|
|
2767
|
+
]);
|
|
2542
2768
|
return {
|
|
2543
2769
|
summary: baseSummary({
|
|
2544
2770
|
locale: params.targetLocale,
|
|
@@ -2549,6 +2775,8 @@ async function runLocale(params) {
|
|
|
2549
2775
|
generated: generation.accepted.map((form) => form.targetKey).sort(),
|
|
2550
2776
|
// Withheld generated forms surface alongside withheld translations: both failed integrity.
|
|
2551
2777
|
integrityMismatches: [...integrityMismatches, ...generation.withheld].sort(),
|
|
2778
|
+
// A generation sub-batch whose provider call itself threw is a provider failure, never integrity.
|
|
2779
|
+
providerFailures: [...providerFailures, ...generation.providerFailures].sort(),
|
|
2552
2780
|
pruned,
|
|
2553
2781
|
notices
|
|
2554
2782
|
}),
|
|
@@ -2557,7 +2785,7 @@ async function runLocale(params) {
|
|
|
2557
2785
|
}
|
|
2558
2786
|
async function runGeneration(params, provider) {
|
|
2559
2787
|
if (!params.generatePlurals || provider.kind !== "llm") {
|
|
2560
|
-
return { accepted: [], withheld: [] };
|
|
2788
|
+
return { accepted: [], withheld: [], providerFailures: [], notices: [] };
|
|
2561
2789
|
}
|
|
2562
2790
|
return generatePluralForms({
|
|
2563
2791
|
source: params.source,
|
|
@@ -2568,7 +2796,8 @@ async function runGeneration(params, provider) {
|
|
|
2568
2796
|
provider,
|
|
2569
2797
|
glossary: params.glossary,
|
|
2570
2798
|
tone: params.tone,
|
|
2571
|
-
baseline: params.baseline
|
|
2799
|
+
baseline: params.baseline,
|
|
2800
|
+
maxBatchSize: params.maxBatchSize
|
|
2572
2801
|
});
|
|
2573
2802
|
}
|
|
2574
2803
|
function pluralNoticeFor(params, merged) {
|
|
@@ -2590,27 +2819,35 @@ function baseSummary(parts) {
|
|
|
2590
2819
|
pruned: parts.pruned,
|
|
2591
2820
|
invalidIcuSource: parts.invalidIcuSource,
|
|
2592
2821
|
integrityMismatches: parts.integrityMismatches,
|
|
2822
|
+
providerFailures: parts.providerFailures,
|
|
2593
2823
|
generated: parts.generated,
|
|
2594
2824
|
notices: parts.notices
|
|
2595
2825
|
};
|
|
2596
2826
|
}
|
|
2597
|
-
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches) {
|
|
2827
|
+
async function translateAndCheck(provider, params, entries, accepted, integrityMismatches, providerFailures) {
|
|
2598
2828
|
const notices = [];
|
|
2599
2829
|
for (const batch of chunk(entries, params.maxBatchSize)) {
|
|
2600
|
-
const subNotices = await runSubBatch(
|
|
2830
|
+
const subNotices = await runSubBatch(
|
|
2831
|
+
provider,
|
|
2832
|
+
params,
|
|
2833
|
+
batch,
|
|
2834
|
+
accepted,
|
|
2835
|
+
integrityMismatches,
|
|
2836
|
+
providerFailures
|
|
2837
|
+
);
|
|
2601
2838
|
notices.push(...subNotices);
|
|
2602
2839
|
}
|
|
2603
2840
|
return notices;
|
|
2604
2841
|
}
|
|
2605
|
-
async function runSubBatch(provider, params, batch, accepted, integrityMismatches) {
|
|
2842
|
+
async function runSubBatch(provider, params, batch, accepted, integrityMismatches, providerFailures) {
|
|
2606
2843
|
let result;
|
|
2607
2844
|
try {
|
|
2608
2845
|
result = await provider.translateBatch(buildRequest3(params, batch));
|
|
2609
|
-
} catch {
|
|
2846
|
+
} catch (error) {
|
|
2610
2847
|
for (const entry of batch) {
|
|
2611
|
-
|
|
2848
|
+
providerFailures.push(entry.key);
|
|
2612
2849
|
}
|
|
2613
|
-
return [subBatchFailedNotice(batch.length)];
|
|
2850
|
+
return [subBatchFailedNotice(batch.length, error)];
|
|
2614
2851
|
}
|
|
2615
2852
|
for (const entry of batch) {
|
|
2616
2853
|
const value = result.values.get(entry.key);
|
|
@@ -2623,19 +2860,6 @@ async function runSubBatch(provider, params, batch, accepted, integrityMismatche
|
|
|
2623
2860
|
}
|
|
2624
2861
|
return readNotices(result);
|
|
2625
2862
|
}
|
|
2626
|
-
function subBatchFailedNotice(count) {
|
|
2627
|
-
return {
|
|
2628
|
-
code: "SUB_BATCH_FAILED",
|
|
2629
|
-
message: `A sub-batch of ${count} entries failed and was withheld; it will be retried next run.`
|
|
2630
|
-
};
|
|
2631
|
-
}
|
|
2632
|
-
function chunk(items, size) {
|
|
2633
|
-
const chunks = [];
|
|
2634
|
-
for (let index = 0; index < items.length; index += size) {
|
|
2635
|
-
chunks.push(items.slice(index, index + size));
|
|
2636
|
-
}
|
|
2637
|
-
return chunks;
|
|
2638
|
-
}
|
|
2639
2863
|
function computeLockEntries(params, merged, withheld, generated) {
|
|
2640
2864
|
const lockEntries = {};
|
|
2641
2865
|
const sourceBaseKeys = sourcePluralBaseKeys(params.source);
|
|
@@ -2783,6 +3007,7 @@ var COLUMN_WIDTHS = {
|
|
|
2783
3007
|
[COLUMN.status]: 12,
|
|
2784
3008
|
[COLUMN.translation]: 50
|
|
2785
3009
|
};
|
|
3010
|
+
var TEXT_NUMBER_FORMAT = "@";
|
|
2786
3011
|
function styleHeader(sheet) {
|
|
2787
3012
|
const header = sheet.getRow(HEADER_ROW);
|
|
2788
3013
|
HEADERS.forEach((label, index) => {
|
|
@@ -2798,6 +3023,7 @@ function applyColumnGeometry(sheet) {
|
|
|
2798
3023
|
sheet.getColumn(Number(column)).width = width;
|
|
2799
3024
|
}
|
|
2800
3025
|
sheet.getColumn(COLUMN.sourceHash).hidden = true;
|
|
3026
|
+
sheet.getColumn(COLUMN.translation).numFmt = TEXT_NUMBER_FORMAT;
|
|
2801
3027
|
sheet.views = [{ state: "frozen", ySplit: HEADER_ROW }];
|
|
2802
3028
|
}
|
|
2803
3029
|
function writeRow(sheet, sheetRow) {
|
|
@@ -2806,7 +3032,9 @@ function writeRow(sheet, sheetRow) {
|
|
|
2806
3032
|
row.getCell(COLUMN.source).value = sheetRow.source;
|
|
2807
3033
|
row.getCell(COLUMN.current).value = sheetRow.currentTarget;
|
|
2808
3034
|
row.getCell(COLUMN.status).value = sheetRow.status;
|
|
2809
|
-
row.getCell(COLUMN.translation)
|
|
3035
|
+
const translationCell = row.getCell(COLUMN.translation);
|
|
3036
|
+
translationCell.numFmt = TEXT_NUMBER_FORMAT;
|
|
3037
|
+
translationCell.value = sheetRow.translation === "" ? null : sheetRow.translation;
|
|
2810
3038
|
row.getCell(COLUMN.sourceHash).value = sheetRow.sourceHash;
|
|
2811
3039
|
for (let column = COLUMN.key; column <= COLUMN.sourceHash; column += 1) {
|
|
2812
3040
|
const cell = row.getCell(column);
|
|
@@ -3158,9 +3386,20 @@ function judge(row, sourceEntry, adapter) {
|
|
|
3158
3386
|
}
|
|
3159
3387
|
return void 0;
|
|
3160
3388
|
}
|
|
3389
|
+
function trackBlankDrift(row, params, buckets) {
|
|
3390
|
+
const sourceEntry = params.source.entries.get(row.key);
|
|
3391
|
+
if (sourceEntry === void 0) {
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
const priorHash = params.baseline.get(row.key);
|
|
3395
|
+
if (priorHash !== void 0 && priorHash !== contentHash(sourceEntry)) {
|
|
3396
|
+
buckets.blankDrifted.add(row.key);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3161
3399
|
function classifyRows(params, buckets) {
|
|
3162
3400
|
for (const row of params.sheet.rows) {
|
|
3163
3401
|
if (row.translation === "") {
|
|
3402
|
+
trackBlankDrift(row, params, buckets);
|
|
3164
3403
|
continue;
|
|
3165
3404
|
}
|
|
3166
3405
|
if (isUnknownKey(row, params.source, params.target)) {
|
|
@@ -3179,9 +3418,20 @@ function classifyRows(params, buckets) {
|
|
|
3179
3418
|
}
|
|
3180
3419
|
}
|
|
3181
3420
|
}
|
|
3421
|
+
function blankRowBaselineNotice(count) {
|
|
3422
|
+
return {
|
|
3423
|
+
code: "BLANK_ROW_BASELINE_RETAINED",
|
|
3424
|
+
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.`
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3182
3427
|
function importLocale(params) {
|
|
3183
3428
|
const diff2 = diffResources(params.source, params.target, { baseline: params.baseline });
|
|
3184
|
-
const buckets = {
|
|
3429
|
+
const buckets = {
|
|
3430
|
+
accepted: /* @__PURE__ */ new Map(),
|
|
3431
|
+
mismatches: [],
|
|
3432
|
+
withheld: /* @__PURE__ */ new Set(),
|
|
3433
|
+
blankDrifted: /* @__PURE__ */ new Set()
|
|
3434
|
+
};
|
|
3185
3435
|
classifyRows(params, buckets);
|
|
3186
3436
|
const rowKeys = new Set(params.sheet.rows.map((row) => row.key));
|
|
3187
3437
|
const invalidIcuSource = [...new Set(params.sourceInvalidIcuKeys)].filter((key) => rowKeys.has(key)).sort();
|
|
@@ -3195,9 +3445,11 @@ function importLocale(params) {
|
|
|
3195
3445
|
pruned: [],
|
|
3196
3446
|
invalidIcuSource,
|
|
3197
3447
|
integrityMismatches: [...buckets.mismatches].sort(),
|
|
3448
|
+
// A workbook import never calls a provider, so a provider-call failure cannot occur here.
|
|
3449
|
+
providerFailures: [],
|
|
3198
3450
|
// Plural generation is a translate-flow concern; the manual workbook import never generates forms.
|
|
3199
3451
|
generated: [],
|
|
3200
|
-
notices: []
|
|
3452
|
+
notices: buckets.blankDrifted.size > 0 ? [blankRowBaselineNotice(buckets.blankDrifted.size)] : []
|
|
3201
3453
|
};
|
|
3202
3454
|
return { summary, accepted: buckets.accepted, withheld: buckets.withheld };
|
|
3203
3455
|
}
|
|
@@ -3231,21 +3483,19 @@ function mergeAccepted(target, accepted) {
|
|
|
3231
3483
|
}
|
|
3232
3484
|
return merged;
|
|
3233
3485
|
}
|
|
3234
|
-
function computeLockEntries2(source, merged, baseline,
|
|
3486
|
+
function computeLockEntries2(source, merged, baseline, accepted) {
|
|
3235
3487
|
const entries = {};
|
|
3236
3488
|
for (const key of merged.keys()) {
|
|
3237
3489
|
const sourceEntry = source.entries.get(key);
|
|
3238
3490
|
if (sourceEntry === void 0) {
|
|
3239
3491
|
continue;
|
|
3240
3492
|
}
|
|
3241
|
-
if (
|
|
3242
|
-
|
|
3243
|
-
if (prior !== void 0) {
|
|
3244
|
-
entries[key] = prior;
|
|
3245
|
-
}
|
|
3493
|
+
if (accepted.has(key)) {
|
|
3494
|
+
entries[key] = contentHash(sourceEntry);
|
|
3246
3495
|
continue;
|
|
3247
3496
|
}
|
|
3248
|
-
|
|
3497
|
+
const prior = baseline.get(key);
|
|
3498
|
+
entries[key] = prior !== void 0 ? prior : contentHash(sourceEntry);
|
|
3249
3499
|
}
|
|
3250
3500
|
return entries;
|
|
3251
3501
|
}
|
|
@@ -3258,7 +3508,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
3258
3508
|
}
|
|
3259
3509
|
const target = await readTarget4(ctx.cwd, ctx.config, ctx.adapter, ctx.fs, sheet.locale);
|
|
3260
3510
|
const baseline = baselineFor(lock, sheet.locale);
|
|
3261
|
-
const { summary, accepted
|
|
3511
|
+
const { summary, accepted } = importLocale({
|
|
3262
3512
|
sheet,
|
|
3263
3513
|
source: ctx.source,
|
|
3264
3514
|
target,
|
|
@@ -3282,7 +3532,7 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
3282
3532
|
path
|
|
3283
3533
|
);
|
|
3284
3534
|
}
|
|
3285
|
-
return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline,
|
|
3535
|
+
return { summary, lockEntries: computeLockEntries2(ctx.source, merged, baseline, accepted) };
|
|
3286
3536
|
}
|
|
3287
3537
|
async function importWorkbook(input, deps = {}) {
|
|
3288
3538
|
const config = input.config;
|
|
@@ -3327,6 +3577,13 @@ async function importWorkbook(input, deps = {}) {
|
|
|
3327
3577
|
return { dryRun, locales: summaries, succeeded, failed };
|
|
3328
3578
|
}
|
|
3329
3579
|
|
|
3580
|
+
// src/lock/load-lock-file.ts
|
|
3581
|
+
async function loadLockFile(input = {}, deps = {}) {
|
|
3582
|
+
const cwd = input.cwd ?? process.cwd();
|
|
3583
|
+
const fs = deps.fs ?? defaultFs;
|
|
3584
|
+
return readLockFile(lockFilePath(cwd), fs);
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3330
3587
|
// src/scaffolding.ts
|
|
3331
3588
|
var scaffoldingMetadata = {
|
|
3332
3589
|
/** Provider id -> the environment variable its API key is read from. Owned by ai-providers. */
|
|
@@ -3443,6 +3700,7 @@ async function watch(input, deps = {}) {
|
|
|
3443
3700
|
}
|
|
3444
3701
|
|
|
3445
3702
|
exports.DEFAULT_WORKBOOK_PATH = DEFAULT_WORKBOOK_PATH;
|
|
3703
|
+
exports.LOCK_FILE_NAME = LOCK_FILE_NAME;
|
|
3446
3704
|
exports.SdkError = SdkError;
|
|
3447
3705
|
exports.check = check;
|
|
3448
3706
|
exports.defineConfig = defineConfig;
|
|
@@ -3450,6 +3708,9 @@ exports.diff = diff;
|
|
|
3450
3708
|
exports.exportWorkbook = exportWorkbook;
|
|
3451
3709
|
exports.importWorkbook = importWorkbook;
|
|
3452
3710
|
exports.loadConfig = loadConfig;
|
|
3711
|
+
exports.loadConfigWithMeta = loadConfigWithMeta;
|
|
3712
|
+
exports.loadLockFile = loadLockFile;
|
|
3713
|
+
exports.lockState = lockState;
|
|
3453
3714
|
exports.scaffoldingMetadata = scaffoldingMetadata;
|
|
3454
3715
|
exports.translate = translate2;
|
|
3455
3716
|
exports.verbatraConfigSchema = verbatraConfigSchema;
|