scribe-cms 0.0.14 → 0.0.15
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/dist/cli/index.cjs +150 -8
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +150 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/translate-progress.d.ts.map +1 -1
- package/dist/index.cjs +122 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +122 -4
- package/dist/index.js.map +1 -1
- package/dist/src/translate/page-translator.d.ts +28 -0
- package/dist/src/translate/page-translator.d.ts.map +1 -1
- package/dist/src/translate/prompts/translation-prompt.d.ts +2 -6
- package/dist/src/translate/prompts/translation-prompt.d.ts.map +1 -1
- package/dist/src/translate/translate-core.d.ts +9 -0
- package/dist/src/translate/translate-core.d.ts.map +1 -1
- package/dist/src/translate/worklist.d.ts +6 -0
- package/dist/src/translate/worklist.d.ts.map +1 -1
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2537,6 +2537,13 @@ function buildOutputFormatLine(hasFrontmatter, slugStrategy) {
|
|
|
2537
2537
|
}
|
|
2538
2538
|
return slugStrategy === "localized" ? "`frontmatter` (object with translated frontmatter fields), `body` (string, full MDX body), `slug` (string)." : "`frontmatter` (object), `body` (string).";
|
|
2539
2539
|
}
|
|
2540
|
+
function buildRetryContextSection(previousError) {
|
|
2541
|
+
return [
|
|
2542
|
+
"",
|
|
2543
|
+
"## Previous attempt",
|
|
2544
|
+
`A previous attempt at this translation was rejected with the following validation errors: ${previousError}. Produce a corrected translation that fixes these issues while re-checking every schema constraint (required fields, array minimums, maximum lengths).`
|
|
2545
|
+
];
|
|
2546
|
+
}
|
|
2540
2547
|
function buildPageTranslationPrompt(input) {
|
|
2541
2548
|
const localeName = LOCALE_NAMES[input.targetLocale] ?? input.targetLocale;
|
|
2542
2549
|
const localizationPrompt = input.resolved.promptOverride ?? defaultLocalizationPrompt(localeName, input.targetLocale);
|
|
@@ -2567,7 +2574,10 @@ function buildPageTranslationPrompt(input) {
|
|
|
2567
2574
|
buildOutputFormatLine(
|
|
2568
2575
|
Object.keys(input.translatableFrontmatter).length > 0,
|
|
2569
2576
|
input.slugStrategy
|
|
2570
|
-
)
|
|
2577
|
+
),
|
|
2578
|
+
// Retry context is locale-specific suffix material: it MUST stay after the
|
|
2579
|
+
// EN body so the cacheable prefix is byte-identical with and without it.
|
|
2580
|
+
...input.previousError ? buildRetryContextSection(input.previousError) : []
|
|
2571
2581
|
];
|
|
2572
2582
|
return [...prefix, ...suffix].join("\n");
|
|
2573
2583
|
}
|
|
@@ -2787,7 +2797,8 @@ function prepareTranslation(config, item, options, startedAt) {
|
|
|
2787
2797
|
contextLabel: resolveContextLabel(enDoc, item.enSlug),
|
|
2788
2798
|
translatableFrontmatter: payload.frontmatter,
|
|
2789
2799
|
enBody: payload.body,
|
|
2790
|
-
slugStrategy: type.slugStrategy
|
|
2800
|
+
slugStrategy: type.slugStrategy,
|
|
2801
|
+
previousError: item.previousError
|
|
2791
2802
|
});
|
|
2792
2803
|
const responseSchema = buildGeminiResponseSchema(type.schema, type.slugStrategy);
|
|
2793
2804
|
return {
|
|
@@ -3199,6 +3210,82 @@ async function runPool(items, concurrency, worker) {
|
|
|
3199
3210
|
function labelForItem(item) {
|
|
3200
3211
|
return `${item.contentType}/${item.enSlug}@${item.locale}`;
|
|
3201
3212
|
}
|
|
3213
|
+
function buildRetryWorklist(failed, originalByKey) {
|
|
3214
|
+
return failed.map((result) => {
|
|
3215
|
+
const key = translationItemKey(result);
|
|
3216
|
+
const original = originalByKey.get(key);
|
|
3217
|
+
return {
|
|
3218
|
+
contentType: result.contentType,
|
|
3219
|
+
enSlug: result.enSlug,
|
|
3220
|
+
locale: result.locale,
|
|
3221
|
+
reason: original?.reason ?? "missing",
|
|
3222
|
+
currentEnHash: original?.currentEnHash ?? "",
|
|
3223
|
+
storedEnHash: original?.storedEnHash,
|
|
3224
|
+
previousError: result.error ?? "Translation failed"
|
|
3225
|
+
};
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
async function runRetryRound(config, retryItems, options) {
|
|
3229
|
+
const startedAt = Date.now();
|
|
3230
|
+
const translateOne = options.translateOne ?? ((item) => translatePage(config, item, { model: options.model, force: true }));
|
|
3231
|
+
if (options.mode === "direct") {
|
|
3232
|
+
const active = /* @__PURE__ */ new Set();
|
|
3233
|
+
await runPool(retryItems, options.concurrency, async (item) => {
|
|
3234
|
+
const label = labelForItem(item);
|
|
3235
|
+
active.add(label);
|
|
3236
|
+
options.onProgress?.({ type: "item-start", item, active: [...active] });
|
|
3237
|
+
const result = await translateOne(item);
|
|
3238
|
+
active.delete(label);
|
|
3239
|
+
options.onResult(result);
|
|
3240
|
+
});
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
const entries = [];
|
|
3244
|
+
for (const item of retryItems) {
|
|
3245
|
+
const itemStartedAt = Date.now();
|
|
3246
|
+
try {
|
|
3247
|
+
const outcome = prepareTranslation(config, item, { model: options.model, force: true }, itemStartedAt);
|
|
3248
|
+
if (outcome.status === "done") {
|
|
3249
|
+
options.onResult(outcome.result);
|
|
3250
|
+
} else {
|
|
3251
|
+
entries.push({
|
|
3252
|
+
apiModel: resolveGeminiModelId(displayModelFor(outcome.prepared)),
|
|
3253
|
+
prompt: outcome.prepared.prompt,
|
|
3254
|
+
prepared: outcome.prepared
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
} catch (error) {
|
|
3258
|
+
options.onResult(failedResultForItem(item, error, itemStartedAt));
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
const plans = planBatchJobs(entries);
|
|
3262
|
+
const submitted = await Promise.all(
|
|
3263
|
+
plans.map(async (plan, planIndex) => {
|
|
3264
|
+
try {
|
|
3265
|
+
return await submitBatchJobPlan(config, {
|
|
3266
|
+
plan,
|
|
3267
|
+
displayModel: normalizeGeminiDisplayName(plan.apiModel),
|
|
3268
|
+
jobIndex: planIndex,
|
|
3269
|
+
jobCount: plans.length,
|
|
3270
|
+
onProgress: options.onProgress
|
|
3271
|
+
});
|
|
3272
|
+
} catch (error) {
|
|
3273
|
+
for (const entry of plan.entries) {
|
|
3274
|
+
options.onResult(failedResultForItem(entry.prepared.item, error, startedAt));
|
|
3275
|
+
}
|
|
3276
|
+
return void 0;
|
|
3277
|
+
}
|
|
3278
|
+
})
|
|
3279
|
+
);
|
|
3280
|
+
const jobsToPoll = submitted.filter((row) => row !== void 0);
|
|
3281
|
+
if (jobsToPoll.length > 0) {
|
|
3282
|
+
await pollAndIngestBatchJobs(config, jobsToPoll, {
|
|
3283
|
+
jobCount: jobsToPoll.length,
|
|
3284
|
+
onProgress: options.onProgress,
|
|
3285
|
+
onResult: options.onResult
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3202
3289
|
async function translatePage(config, item, options = {}) {
|
|
3203
3290
|
const startedAt = Date.now();
|
|
3204
3291
|
let outcome;
|
|
@@ -3371,9 +3458,40 @@ async function translateWorklist(config, items, options = {}) {
|
|
|
3371
3458
|
});
|
|
3372
3459
|
}
|
|
3373
3460
|
}
|
|
3374
|
-
const
|
|
3461
|
+
const mainResults = collector.assemble();
|
|
3462
|
+
const failed = mainResults.filter((result) => result.failed);
|
|
3463
|
+
if (failed.length === 0) {
|
|
3464
|
+
const totals2 = summarizeResults(mainResults, Date.now() - startedAt);
|
|
3465
|
+
options.onProgress?.({ type: "done", results: mainResults, totals: totals2 });
|
|
3466
|
+
return mainResults;
|
|
3467
|
+
}
|
|
3468
|
+
options.onProgress?.({ type: "retry-start", failed });
|
|
3469
|
+
const originalByKey = new Map(
|
|
3470
|
+
items.map((item) => [translationItemKey(item), item])
|
|
3471
|
+
);
|
|
3472
|
+
const retryItems = buildRetryWorklist(failed, originalByKey);
|
|
3473
|
+
const retryByKey = /* @__PURE__ */ new Map();
|
|
3474
|
+
await runRetryRound(config, retryItems, {
|
|
3475
|
+
model: options.model,
|
|
3476
|
+
force: options.force,
|
|
3477
|
+
concurrency,
|
|
3478
|
+
mode,
|
|
3479
|
+
onProgress: options.onProgress,
|
|
3480
|
+
onResult: (result) => {
|
|
3481
|
+
retryByKey.set(translationItemKey(result), result);
|
|
3482
|
+
options.onProgress?.({ type: "item-done", result });
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3485
|
+
const results = mainResults.map((result) => {
|
|
3486
|
+
if (!result.failed) return result;
|
|
3487
|
+
return retryByKey.get(translationItemKey(result)) ?? result;
|
|
3488
|
+
});
|
|
3489
|
+
const retriedTranslated = failed.filter((original) => {
|
|
3490
|
+
const retried = retryByKey.get(translationItemKey(original));
|
|
3491
|
+
return retried !== void 0 && !retried.failed && !retried.skipped;
|
|
3492
|
+
}).length;
|
|
3375
3493
|
const totals = summarizeResults(results, Date.now() - startedAt);
|
|
3376
|
-
options.onProgress?.({ type: "done", results, totals });
|
|
3494
|
+
options.onProgress?.({ type: "done", results, totals, retriedTranslated });
|
|
3377
3495
|
return results;
|
|
3378
3496
|
}
|
|
3379
3497
|
function countPendingItems(config, jobs) {
|
|
@@ -4195,10 +4313,11 @@ function detailForResult(result) {
|
|
|
4195
4313
|
if (result.error) parts.push(red(result.error));
|
|
4196
4314
|
return parts.length > 0 ? dim(parts.join(" \xB7 ")) : "";
|
|
4197
4315
|
}
|
|
4198
|
-
function formatSummaryLine(totals, dryRun) {
|
|
4316
|
+
function formatSummaryLine(totals, dryRun, retriedTranslated = 0) {
|
|
4199
4317
|
const action = dryRun ? "Would translate" : "Translated";
|
|
4318
|
+
const base = totals.translated - retriedTranslated;
|
|
4200
4319
|
const parts = [
|
|
4201
|
-
`${action} ${totals.translated}`,
|
|
4320
|
+
retriedTranslated > 0 ? `${action} ${base} +${retriedTranslated} on retry` : `${action} ${totals.translated}`,
|
|
4202
4321
|
`skipped ${totals.skipped}`
|
|
4203
4322
|
];
|
|
4204
4323
|
if (totals.failed > 0) parts.push(red(`failed ${totals.failed}`));
|
|
@@ -4247,8 +4366,18 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
4247
4366
|
}
|
|
4248
4367
|
return;
|
|
4249
4368
|
}
|
|
4369
|
+
if (event.type === "retry-start") {
|
|
4370
|
+
for (const result of event.failed) {
|
|
4371
|
+
console.log(`${labelForResult(result)}: ${result.error ?? "failed"}`);
|
|
4372
|
+
}
|
|
4373
|
+
console.log(`Retrying ${event.failed.length} failed items with validation context...`);
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4250
4376
|
if (event.type === "done") {
|
|
4251
|
-
console.log(formatSummaryLine(event.totals, dryRun));
|
|
4377
|
+
console.log(formatSummaryLine(event.totals, dryRun, event.retriedTranslated));
|
|
4378
|
+
for (const result of event.results.filter((entry) => entry.failed)) {
|
|
4379
|
+
console.error(`${labelForResult(result)}: ${result.error ?? "failed"}`);
|
|
4380
|
+
}
|
|
4252
4381
|
}
|
|
4253
4382
|
},
|
|
4254
4383
|
finish() {
|
|
@@ -4415,12 +4544,25 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
4415
4544
|
render();
|
|
4416
4545
|
break;
|
|
4417
4546
|
}
|
|
4547
|
+
case "retry-start": {
|
|
4548
|
+
total += event.failed.length;
|
|
4549
|
+
printPersistent([
|
|
4550
|
+
...event.failed.map(
|
|
4551
|
+
(result) => red(`${labelForResult(result)}: ${result.error ?? "failed"}`)
|
|
4552
|
+
),
|
|
4553
|
+
cyan(`Retrying ${event.failed.length} failed items with validation context...`)
|
|
4554
|
+
]);
|
|
4555
|
+
render();
|
|
4556
|
+
break;
|
|
4557
|
+
}
|
|
4418
4558
|
case "done":
|
|
4419
4559
|
showCursor();
|
|
4420
4560
|
if (renderedLines > 0) {
|
|
4421
4561
|
process.stdout.write(`\x1B[${renderedLines}A`);
|
|
4422
4562
|
}
|
|
4423
|
-
process.stdout.write(
|
|
4563
|
+
process.stdout.write(
|
|
4564
|
+
"\x1B[2K" + green("Done") + " \xB7 " + formatSummaryLine(event.totals, dryRun, event.retriedTranslated) + "\n"
|
|
4565
|
+
);
|
|
4424
4566
|
renderedLines = 0;
|
|
4425
4567
|
for (const result of event.results.filter((entry) => entry.failed)) {
|
|
4426
4568
|
process.stdout.write("\x1B[2K" + red(`${labelForResult(result)}: ${result.error ?? "failed"}`) + "\n");
|