scribe-cms 0.0.14 → 0.0.16
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 +162 -9
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +162 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/translate-progress.d.ts.map +1 -1
- package/dist/index.cjs +134 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +134 -5
- 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 +4 -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/index.js
CHANGED
|
@@ -2820,6 +2820,13 @@ function defaultLocalizationPrompt(localeName, locale) {
|
|
|
2820
2820
|
"Write as if a native speaker authored it for the target market."
|
|
2821
2821
|
].join(" ");
|
|
2822
2822
|
}
|
|
2823
|
+
function buildLocalizationPrompt(promptOverride, localeName, locale) {
|
|
2824
|
+
const localeDirective = defaultLocalizationPrompt(localeName, locale);
|
|
2825
|
+
if (!promptOverride) return localeDirective;
|
|
2826
|
+
return `${promptOverride}
|
|
2827
|
+
|
|
2828
|
+
${localeDirective}`;
|
|
2829
|
+
}
|
|
2823
2830
|
var TASK_FRAMING = "You are localizing the content below into a target language specified at the end of this prompt.";
|
|
2824
2831
|
function buildOutputFormatLine(hasFrontmatter, slugStrategy) {
|
|
2825
2832
|
if (!hasFrontmatter) {
|
|
@@ -2827,9 +2834,20 @@ function buildOutputFormatLine(hasFrontmatter, slugStrategy) {
|
|
|
2827
2834
|
}
|
|
2828
2835
|
return slugStrategy === "localized" ? "`frontmatter` (object with translated frontmatter fields), `body` (string, full MDX body), `slug` (string)." : "`frontmatter` (object), `body` (string).";
|
|
2829
2836
|
}
|
|
2837
|
+
function buildRetryContextSection(previousError) {
|
|
2838
|
+
return [
|
|
2839
|
+
"",
|
|
2840
|
+
"## Previous attempt",
|
|
2841
|
+
`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).`
|
|
2842
|
+
];
|
|
2843
|
+
}
|
|
2830
2844
|
function buildPageTranslationPrompt(input) {
|
|
2831
2845
|
const localeName = LOCALE_NAMES[input.targetLocale] ?? input.targetLocale;
|
|
2832
|
-
const localizationPrompt =
|
|
2846
|
+
const localizationPrompt = buildLocalizationPrompt(
|
|
2847
|
+
input.resolved.promptOverride,
|
|
2848
|
+
localeName,
|
|
2849
|
+
input.targetLocale
|
|
2850
|
+
);
|
|
2833
2851
|
const prefix = [
|
|
2834
2852
|
TASK_FRAMING,
|
|
2835
2853
|
"",
|
|
@@ -2857,7 +2875,10 @@ function buildPageTranslationPrompt(input) {
|
|
|
2857
2875
|
buildOutputFormatLine(
|
|
2858
2876
|
Object.keys(input.translatableFrontmatter).length > 0,
|
|
2859
2877
|
input.slugStrategy
|
|
2860
|
-
)
|
|
2878
|
+
),
|
|
2879
|
+
// Retry context is locale-specific suffix material: it MUST stay after the
|
|
2880
|
+
// EN body so the cacheable prefix is byte-identical with and without it.
|
|
2881
|
+
...input.previousError ? buildRetryContextSection(input.previousError) : []
|
|
2861
2882
|
];
|
|
2862
2883
|
return [...prefix, ...suffix].join("\n");
|
|
2863
2884
|
}
|
|
@@ -3072,7 +3093,8 @@ function prepareTranslation(config, item, options, startedAt) {
|
|
|
3072
3093
|
contextLabel: resolveContextLabel(enDoc, item.enSlug),
|
|
3073
3094
|
translatableFrontmatter: payload.frontmatter,
|
|
3074
3095
|
enBody: payload.body,
|
|
3075
|
-
slugStrategy: type.slugStrategy
|
|
3096
|
+
slugStrategy: type.slugStrategy,
|
|
3097
|
+
previousError: item.previousError
|
|
3076
3098
|
});
|
|
3077
3099
|
const responseSchema = buildGeminiResponseSchema(type.schema, type.slugStrategy);
|
|
3078
3100
|
return {
|
|
@@ -3484,6 +3506,82 @@ async function runPool(items, concurrency, worker) {
|
|
|
3484
3506
|
function labelForItem(item) {
|
|
3485
3507
|
return `${item.contentType}/${item.enSlug}@${item.locale}`;
|
|
3486
3508
|
}
|
|
3509
|
+
function buildRetryWorklist(failed, originalByKey) {
|
|
3510
|
+
return failed.map((result) => {
|
|
3511
|
+
const key = translationItemKey(result);
|
|
3512
|
+
const original = originalByKey.get(key);
|
|
3513
|
+
return {
|
|
3514
|
+
contentType: result.contentType,
|
|
3515
|
+
enSlug: result.enSlug,
|
|
3516
|
+
locale: result.locale,
|
|
3517
|
+
reason: original?.reason ?? "missing",
|
|
3518
|
+
currentEnHash: original?.currentEnHash ?? "",
|
|
3519
|
+
storedEnHash: original?.storedEnHash,
|
|
3520
|
+
previousError: result.error ?? "Translation failed"
|
|
3521
|
+
};
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3524
|
+
async function runRetryRound(config, retryItems, options) {
|
|
3525
|
+
const startedAt = Date.now();
|
|
3526
|
+
const translateOne = options.translateOne ?? ((item) => translatePage(config, item, { model: options.model, force: true }));
|
|
3527
|
+
if (options.mode === "direct") {
|
|
3528
|
+
const active = /* @__PURE__ */ new Set();
|
|
3529
|
+
await runPool(retryItems, options.concurrency, async (item) => {
|
|
3530
|
+
const label = labelForItem(item);
|
|
3531
|
+
active.add(label);
|
|
3532
|
+
options.onProgress?.({ type: "item-start", item, active: [...active] });
|
|
3533
|
+
const result = await translateOne(item);
|
|
3534
|
+
active.delete(label);
|
|
3535
|
+
options.onResult(result);
|
|
3536
|
+
});
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3539
|
+
const entries = [];
|
|
3540
|
+
for (const item of retryItems) {
|
|
3541
|
+
const itemStartedAt = Date.now();
|
|
3542
|
+
try {
|
|
3543
|
+
const outcome = prepareTranslation(config, item, { model: options.model, force: true }, itemStartedAt);
|
|
3544
|
+
if (outcome.status === "done") {
|
|
3545
|
+
options.onResult(outcome.result);
|
|
3546
|
+
} else {
|
|
3547
|
+
entries.push({
|
|
3548
|
+
apiModel: resolveGeminiModelId(displayModelFor(outcome.prepared)),
|
|
3549
|
+
prompt: outcome.prepared.prompt,
|
|
3550
|
+
prepared: outcome.prepared
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
} catch (error) {
|
|
3554
|
+
options.onResult(failedResultForItem(item, error, itemStartedAt));
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
const plans = planBatchJobs(entries);
|
|
3558
|
+
const submitted = await Promise.all(
|
|
3559
|
+
plans.map(async (plan, planIndex) => {
|
|
3560
|
+
try {
|
|
3561
|
+
return await submitBatchJobPlan(config, {
|
|
3562
|
+
plan,
|
|
3563
|
+
displayModel: normalizeGeminiDisplayName(plan.apiModel),
|
|
3564
|
+
jobIndex: planIndex,
|
|
3565
|
+
jobCount: plans.length,
|
|
3566
|
+
onProgress: options.onProgress
|
|
3567
|
+
});
|
|
3568
|
+
} catch (error) {
|
|
3569
|
+
for (const entry of plan.entries) {
|
|
3570
|
+
options.onResult(failedResultForItem(entry.prepared.item, error, startedAt));
|
|
3571
|
+
}
|
|
3572
|
+
return void 0;
|
|
3573
|
+
}
|
|
3574
|
+
})
|
|
3575
|
+
);
|
|
3576
|
+
const jobsToPoll = submitted.filter((row) => row !== void 0);
|
|
3577
|
+
if (jobsToPoll.length > 0) {
|
|
3578
|
+
await pollAndIngestBatchJobs(config, jobsToPoll, {
|
|
3579
|
+
jobCount: jobsToPoll.length,
|
|
3580
|
+
onProgress: options.onProgress,
|
|
3581
|
+
onResult: options.onResult
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3487
3585
|
async function translatePage(config, item, options = {}) {
|
|
3488
3586
|
const startedAt = Date.now();
|
|
3489
3587
|
let outcome;
|
|
@@ -3656,9 +3754,40 @@ async function translateWorklist(config, items, options = {}) {
|
|
|
3656
3754
|
});
|
|
3657
3755
|
}
|
|
3658
3756
|
}
|
|
3659
|
-
const
|
|
3757
|
+
const mainResults = collector.assemble();
|
|
3758
|
+
const failed = mainResults.filter((result) => result.failed);
|
|
3759
|
+
if (failed.length === 0) {
|
|
3760
|
+
const totals2 = summarizeResults(mainResults, Date.now() - startedAt);
|
|
3761
|
+
options.onProgress?.({ type: "done", results: mainResults, totals: totals2 });
|
|
3762
|
+
return mainResults;
|
|
3763
|
+
}
|
|
3764
|
+
options.onProgress?.({ type: "retry-start", failed });
|
|
3765
|
+
const originalByKey = new Map(
|
|
3766
|
+
items.map((item) => [translationItemKey(item), item])
|
|
3767
|
+
);
|
|
3768
|
+
const retryItems = buildRetryWorklist(failed, originalByKey);
|
|
3769
|
+
const retryByKey = /* @__PURE__ */ new Map();
|
|
3770
|
+
await runRetryRound(config, retryItems, {
|
|
3771
|
+
model: options.model,
|
|
3772
|
+
force: options.force,
|
|
3773
|
+
concurrency,
|
|
3774
|
+
mode,
|
|
3775
|
+
onProgress: options.onProgress,
|
|
3776
|
+
onResult: (result) => {
|
|
3777
|
+
retryByKey.set(translationItemKey(result), result);
|
|
3778
|
+
options.onProgress?.({ type: "item-done", result });
|
|
3779
|
+
}
|
|
3780
|
+
});
|
|
3781
|
+
const results = mainResults.map((result) => {
|
|
3782
|
+
if (!result.failed) return result;
|
|
3783
|
+
return retryByKey.get(translationItemKey(result)) ?? result;
|
|
3784
|
+
});
|
|
3785
|
+
const retriedTranslated = failed.filter((original) => {
|
|
3786
|
+
const retried = retryByKey.get(translationItemKey(original));
|
|
3787
|
+
return retried !== void 0 && !retried.failed && !retried.skipped;
|
|
3788
|
+
}).length;
|
|
3660
3789
|
const totals = summarizeResults(results, Date.now() - startedAt);
|
|
3661
|
-
options.onProgress?.({ type: "done", results, totals });
|
|
3790
|
+
options.onProgress?.({ type: "done", results, totals, retriedTranslated });
|
|
3662
3791
|
return results;
|
|
3663
3792
|
}
|
|
3664
3793
|
function countPendingItems(config, jobs) {
|