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.cjs
CHANGED
|
@@ -2554,6 +2554,13 @@ function buildOutputFormatLine(hasFrontmatter, slugStrategy) {
|
|
|
2554
2554
|
}
|
|
2555
2555
|
return slugStrategy === "localized" ? "`frontmatter` (object with translated frontmatter fields), `body` (string, full MDX body), `slug` (string)." : "`frontmatter` (object), `body` (string).";
|
|
2556
2556
|
}
|
|
2557
|
+
function buildRetryContextSection(previousError) {
|
|
2558
|
+
return [
|
|
2559
|
+
"",
|
|
2560
|
+
"## Previous attempt",
|
|
2561
|
+
`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).`
|
|
2562
|
+
];
|
|
2563
|
+
}
|
|
2557
2564
|
function buildPageTranslationPrompt(input) {
|
|
2558
2565
|
const localeName = LOCALE_NAMES[input.targetLocale] ?? input.targetLocale;
|
|
2559
2566
|
const localizationPrompt = input.resolved.promptOverride ?? defaultLocalizationPrompt(localeName, input.targetLocale);
|
|
@@ -2584,7 +2591,10 @@ function buildPageTranslationPrompt(input) {
|
|
|
2584
2591
|
buildOutputFormatLine(
|
|
2585
2592
|
Object.keys(input.translatableFrontmatter).length > 0,
|
|
2586
2593
|
input.slugStrategy
|
|
2587
|
-
)
|
|
2594
|
+
),
|
|
2595
|
+
// Retry context is locale-specific suffix material: it MUST stay after the
|
|
2596
|
+
// EN body so the cacheable prefix is byte-identical with and without it.
|
|
2597
|
+
...input.previousError ? buildRetryContextSection(input.previousError) : []
|
|
2588
2598
|
];
|
|
2589
2599
|
return [...prefix, ...suffix].join("\n");
|
|
2590
2600
|
}
|
|
@@ -2804,7 +2814,8 @@ function prepareTranslation(config, item, options, startedAt) {
|
|
|
2804
2814
|
contextLabel: resolveContextLabel(enDoc, item.enSlug),
|
|
2805
2815
|
translatableFrontmatter: payload.frontmatter,
|
|
2806
2816
|
enBody: payload.body,
|
|
2807
|
-
slugStrategy: type.slugStrategy
|
|
2817
|
+
slugStrategy: type.slugStrategy,
|
|
2818
|
+
previousError: item.previousError
|
|
2808
2819
|
});
|
|
2809
2820
|
const responseSchema = buildGeminiResponseSchema(type.schema, type.slugStrategy);
|
|
2810
2821
|
return {
|
|
@@ -3216,6 +3227,82 @@ async function runPool(items, concurrency, worker) {
|
|
|
3216
3227
|
function labelForItem(item) {
|
|
3217
3228
|
return `${item.contentType}/${item.enSlug}@${item.locale}`;
|
|
3218
3229
|
}
|
|
3230
|
+
function buildRetryWorklist(failed, originalByKey) {
|
|
3231
|
+
return failed.map((result) => {
|
|
3232
|
+
const key = translationItemKey(result);
|
|
3233
|
+
const original = originalByKey.get(key);
|
|
3234
|
+
return {
|
|
3235
|
+
contentType: result.contentType,
|
|
3236
|
+
enSlug: result.enSlug,
|
|
3237
|
+
locale: result.locale,
|
|
3238
|
+
reason: original?.reason ?? "missing",
|
|
3239
|
+
currentEnHash: original?.currentEnHash ?? "",
|
|
3240
|
+
storedEnHash: original?.storedEnHash,
|
|
3241
|
+
previousError: result.error ?? "Translation failed"
|
|
3242
|
+
};
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
async function runRetryRound(config, retryItems, options) {
|
|
3246
|
+
const startedAt = Date.now();
|
|
3247
|
+
const translateOne = options.translateOne ?? ((item) => translatePage(config, item, { model: options.model, force: true }));
|
|
3248
|
+
if (options.mode === "direct") {
|
|
3249
|
+
const active = /* @__PURE__ */ new Set();
|
|
3250
|
+
await runPool(retryItems, options.concurrency, async (item) => {
|
|
3251
|
+
const label = labelForItem(item);
|
|
3252
|
+
active.add(label);
|
|
3253
|
+
options.onProgress?.({ type: "item-start", item, active: [...active] });
|
|
3254
|
+
const result = await translateOne(item);
|
|
3255
|
+
active.delete(label);
|
|
3256
|
+
options.onResult(result);
|
|
3257
|
+
});
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
const entries = [];
|
|
3261
|
+
for (const item of retryItems) {
|
|
3262
|
+
const itemStartedAt = Date.now();
|
|
3263
|
+
try {
|
|
3264
|
+
const outcome = prepareTranslation(config, item, { model: options.model, force: true }, itemStartedAt);
|
|
3265
|
+
if (outcome.status === "done") {
|
|
3266
|
+
options.onResult(outcome.result);
|
|
3267
|
+
} else {
|
|
3268
|
+
entries.push({
|
|
3269
|
+
apiModel: resolveGeminiModelId(displayModelFor(outcome.prepared)),
|
|
3270
|
+
prompt: outcome.prepared.prompt,
|
|
3271
|
+
prepared: outcome.prepared
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
} catch (error) {
|
|
3275
|
+
options.onResult(failedResultForItem(item, error, itemStartedAt));
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
const plans = planBatchJobs(entries);
|
|
3279
|
+
const submitted = await Promise.all(
|
|
3280
|
+
plans.map(async (plan, planIndex) => {
|
|
3281
|
+
try {
|
|
3282
|
+
return await submitBatchJobPlan(config, {
|
|
3283
|
+
plan,
|
|
3284
|
+
displayModel: normalizeGeminiDisplayName(plan.apiModel),
|
|
3285
|
+
jobIndex: planIndex,
|
|
3286
|
+
jobCount: plans.length,
|
|
3287
|
+
onProgress: options.onProgress
|
|
3288
|
+
});
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
for (const entry of plan.entries) {
|
|
3291
|
+
options.onResult(failedResultForItem(entry.prepared.item, error, startedAt));
|
|
3292
|
+
}
|
|
3293
|
+
return void 0;
|
|
3294
|
+
}
|
|
3295
|
+
})
|
|
3296
|
+
);
|
|
3297
|
+
const jobsToPoll = submitted.filter((row) => row !== void 0);
|
|
3298
|
+
if (jobsToPoll.length > 0) {
|
|
3299
|
+
await pollAndIngestBatchJobs(config, jobsToPoll, {
|
|
3300
|
+
jobCount: jobsToPoll.length,
|
|
3301
|
+
onProgress: options.onProgress,
|
|
3302
|
+
onResult: options.onResult
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3219
3306
|
async function translatePage(config, item, options = {}) {
|
|
3220
3307
|
const startedAt = Date.now();
|
|
3221
3308
|
let outcome;
|
|
@@ -3388,9 +3475,40 @@ async function translateWorklist(config, items, options = {}) {
|
|
|
3388
3475
|
});
|
|
3389
3476
|
}
|
|
3390
3477
|
}
|
|
3391
|
-
const
|
|
3478
|
+
const mainResults = collector.assemble();
|
|
3479
|
+
const failed = mainResults.filter((result) => result.failed);
|
|
3480
|
+
if (failed.length === 0) {
|
|
3481
|
+
const totals2 = summarizeResults(mainResults, Date.now() - startedAt);
|
|
3482
|
+
options.onProgress?.({ type: "done", results: mainResults, totals: totals2 });
|
|
3483
|
+
return mainResults;
|
|
3484
|
+
}
|
|
3485
|
+
options.onProgress?.({ type: "retry-start", failed });
|
|
3486
|
+
const originalByKey = new Map(
|
|
3487
|
+
items.map((item) => [translationItemKey(item), item])
|
|
3488
|
+
);
|
|
3489
|
+
const retryItems = buildRetryWorklist(failed, originalByKey);
|
|
3490
|
+
const retryByKey = /* @__PURE__ */ new Map();
|
|
3491
|
+
await runRetryRound(config, retryItems, {
|
|
3492
|
+
model: options.model,
|
|
3493
|
+
force: options.force,
|
|
3494
|
+
concurrency,
|
|
3495
|
+
mode,
|
|
3496
|
+
onProgress: options.onProgress,
|
|
3497
|
+
onResult: (result) => {
|
|
3498
|
+
retryByKey.set(translationItemKey(result), result);
|
|
3499
|
+
options.onProgress?.({ type: "item-done", result });
|
|
3500
|
+
}
|
|
3501
|
+
});
|
|
3502
|
+
const results = mainResults.map((result) => {
|
|
3503
|
+
if (!result.failed) return result;
|
|
3504
|
+
return retryByKey.get(translationItemKey(result)) ?? result;
|
|
3505
|
+
});
|
|
3506
|
+
const retriedTranslated = failed.filter((original) => {
|
|
3507
|
+
const retried = retryByKey.get(translationItemKey(original));
|
|
3508
|
+
return retried !== void 0 && !retried.failed && !retried.skipped;
|
|
3509
|
+
}).length;
|
|
3392
3510
|
const totals = summarizeResults(results, Date.now() - startedAt);
|
|
3393
|
-
options.onProgress?.({ type: "done", results, totals });
|
|
3511
|
+
options.onProgress?.({ type: "done", results, totals, retriedTranslated });
|
|
3394
3512
|
return results;
|
|
3395
3513
|
}
|
|
3396
3514
|
function countPendingItems(config, jobs) {
|
|
@@ -4212,10 +4330,11 @@ function detailForResult(result) {
|
|
|
4212
4330
|
if (result.error) parts.push(red(result.error));
|
|
4213
4331
|
return parts.length > 0 ? dim(parts.join(" \xB7 ")) : "";
|
|
4214
4332
|
}
|
|
4215
|
-
function formatSummaryLine(totals, dryRun) {
|
|
4333
|
+
function formatSummaryLine(totals, dryRun, retriedTranslated = 0) {
|
|
4216
4334
|
const action = dryRun ? "Would translate" : "Translated";
|
|
4335
|
+
const base = totals.translated - retriedTranslated;
|
|
4217
4336
|
const parts = [
|
|
4218
|
-
`${action} ${totals.translated}`,
|
|
4337
|
+
retriedTranslated > 0 ? `${action} ${base} +${retriedTranslated} on retry` : `${action} ${totals.translated}`,
|
|
4219
4338
|
`skipped ${totals.skipped}`
|
|
4220
4339
|
];
|
|
4221
4340
|
if (totals.failed > 0) parts.push(red(`failed ${totals.failed}`));
|
|
@@ -4264,8 +4383,18 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
4264
4383
|
}
|
|
4265
4384
|
return;
|
|
4266
4385
|
}
|
|
4386
|
+
if (event.type === "retry-start") {
|
|
4387
|
+
for (const result of event.failed) {
|
|
4388
|
+
console.log(`${labelForResult(result)}: ${result.error ?? "failed"}`);
|
|
4389
|
+
}
|
|
4390
|
+
console.log(`Retrying ${event.failed.length} failed items with validation context...`);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4267
4393
|
if (event.type === "done") {
|
|
4268
|
-
console.log(formatSummaryLine(event.totals, dryRun));
|
|
4394
|
+
console.log(formatSummaryLine(event.totals, dryRun, event.retriedTranslated));
|
|
4395
|
+
for (const result of event.results.filter((entry) => entry.failed)) {
|
|
4396
|
+
console.error(`${labelForResult(result)}: ${result.error ?? "failed"}`);
|
|
4397
|
+
}
|
|
4269
4398
|
}
|
|
4270
4399
|
},
|
|
4271
4400
|
finish() {
|
|
@@ -4432,12 +4561,25 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
4432
4561
|
render();
|
|
4433
4562
|
break;
|
|
4434
4563
|
}
|
|
4564
|
+
case "retry-start": {
|
|
4565
|
+
total += event.failed.length;
|
|
4566
|
+
printPersistent([
|
|
4567
|
+
...event.failed.map(
|
|
4568
|
+
(result) => red(`${labelForResult(result)}: ${result.error ?? "failed"}`)
|
|
4569
|
+
),
|
|
4570
|
+
cyan(`Retrying ${event.failed.length} failed items with validation context...`)
|
|
4571
|
+
]);
|
|
4572
|
+
render();
|
|
4573
|
+
break;
|
|
4574
|
+
}
|
|
4435
4575
|
case "done":
|
|
4436
4576
|
showCursor();
|
|
4437
4577
|
if (renderedLines > 0) {
|
|
4438
4578
|
process.stdout.write(`\x1B[${renderedLines}A`);
|
|
4439
4579
|
}
|
|
4440
|
-
process.stdout.write(
|
|
4580
|
+
process.stdout.write(
|
|
4581
|
+
"\x1B[2K" + green("Done") + " \xB7 " + formatSummaryLine(event.totals, dryRun, event.retriedTranslated) + "\n"
|
|
4582
|
+
);
|
|
4441
4583
|
renderedLines = 0;
|
|
4442
4584
|
for (const result of event.results.filter((entry) => entry.failed)) {
|
|
4443
4585
|
process.stdout.write("\x1B[2K" + red(`${labelForResult(result)}: ${result.error ?? "failed"}`) + "\n");
|