scribe-cms 0.0.13 → 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/README.md +1 -1
- package/dist/cli/index.cjs +170 -41
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +170 -41
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/translate-progress.d.ts.map +1 -1
- package/dist/index.cjs +142 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +142 -37
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +20 -33
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +20 -33
- package/dist/runtime.js.map +1 -1
- package/dist/src/config/resolve-config.d.ts.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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate-progress.d.ts","sourceRoot":"","sources":["../../cli/translate-progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"translate-progress.d.ts","sourceRoot":"","sources":["../../cli/translate-progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,qCAAqC,CAAC;AA4G7C,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACjD,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,+BAA+B,CAAC,OAAO,GAAE;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,yBAAyB,CA8SjC"}
|
package/dist/index.cjs
CHANGED
|
@@ -257,39 +257,7 @@ function resolveConfig(input, baseDir) {
|
|
|
257
257
|
if (localeRouting.strategy === "search-param" && !localeRouting.param) {
|
|
258
258
|
throw new Error('scribe config: localeRouting search-param requires a "param" name');
|
|
259
259
|
}
|
|
260
|
-
const localeFallbacks = {};
|
|
261
|
-
for (const [locale, chain] of Object.entries(raw.localeFallbacks ?? {})) {
|
|
262
|
-
if (!raw.locales.includes(locale)) {
|
|
263
|
-
throw new Error(
|
|
264
|
-
`scribe config: localeFallbacks key "${locale}" is not in locales [${raw.locales.join(", ")}]`
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
const seen = /* @__PURE__ */ new Set();
|
|
268
|
-
for (const fallback of chain) {
|
|
269
|
-
if (!raw.locales.includes(fallback)) {
|
|
270
|
-
throw new Error(
|
|
271
|
-
`scribe config: localeFallbacks["${locale}"] entry "${fallback}" is not in locales [${raw.locales.join(", ")}]`
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
if (fallback === locale) {
|
|
275
|
-
throw new Error(
|
|
276
|
-
`scribe config: localeFallbacks["${locale}"] must not contain its own key "${locale}"`
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
if (fallback === defaultLocale) {
|
|
280
|
-
throw new Error(
|
|
281
|
-
`scribe config: localeFallbacks["${locale}"] must not contain the defaultLocale "${defaultLocale}" \u2014 it is always the final fallback; remove it`
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
if (seen.has(fallback)) {
|
|
285
|
-
throw new Error(
|
|
286
|
-
`scribe config: localeFallbacks["${locale}"] contains duplicate entry "${fallback}"`
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
seen.add(fallback);
|
|
290
|
-
}
|
|
291
|
-
localeFallbacks[locale] = [...chain];
|
|
292
|
-
}
|
|
260
|
+
const localeFallbacks = raw.localeFallbacks === false ? {} : deriveLocaleFallbacks(raw.locales, defaultLocale);
|
|
293
261
|
const projectRoot = path3__default.default.resolve(baseDir ?? process.cwd(), raw.rootDir);
|
|
294
262
|
const contentRoot = path3__default.default.resolve(projectRoot, raw.contentDir ?? "content");
|
|
295
263
|
const storePath = path3__default.default.resolve(projectRoot, raw.store ?? ".scribe/store.sqlite");
|
|
@@ -329,6 +297,25 @@ function resolveConfig(input, baseDir) {
|
|
|
329
297
|
});
|
|
330
298
|
return config;
|
|
331
299
|
}
|
|
300
|
+
function deriveLocaleFallbacks(locales, defaultLocale) {
|
|
301
|
+
const byLowercase = new Map(locales.map((l) => [l.toLowerCase(), l]));
|
|
302
|
+
const fallbacks = {};
|
|
303
|
+
for (const locale of locales) {
|
|
304
|
+
const subtags = locale.split("-");
|
|
305
|
+
if (subtags.length < 2) continue;
|
|
306
|
+
const chain = [];
|
|
307
|
+
for (let end = subtags.length - 1; end >= 1; end--) {
|
|
308
|
+
const prefix = byLowercase.get(subtags.slice(0, end).join("-").toLowerCase());
|
|
309
|
+
if (prefix && prefix !== locale && prefix !== defaultLocale) {
|
|
310
|
+
chain.push(prefix);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (chain.length > 0) {
|
|
314
|
+
fallbacks[locale] = chain;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return fallbacks;
|
|
318
|
+
}
|
|
332
319
|
|
|
333
320
|
// src/core/introspect-schema.ts
|
|
334
321
|
function getArrayElement(schema) {
|
|
@@ -2853,6 +2840,13 @@ function buildOutputFormatLine(hasFrontmatter, slugStrategy) {
|
|
|
2853
2840
|
}
|
|
2854
2841
|
return slugStrategy === "localized" ? "`frontmatter` (object with translated frontmatter fields), `body` (string, full MDX body), `slug` (string)." : "`frontmatter` (object), `body` (string).";
|
|
2855
2842
|
}
|
|
2843
|
+
function buildRetryContextSection(previousError) {
|
|
2844
|
+
return [
|
|
2845
|
+
"",
|
|
2846
|
+
"## Previous attempt",
|
|
2847
|
+
`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).`
|
|
2848
|
+
];
|
|
2849
|
+
}
|
|
2856
2850
|
function buildPageTranslationPrompt(input) {
|
|
2857
2851
|
const localeName = LOCALE_NAMES[input.targetLocale] ?? input.targetLocale;
|
|
2858
2852
|
const localizationPrompt = input.resolved.promptOverride ?? defaultLocalizationPrompt(localeName, input.targetLocale);
|
|
@@ -2883,7 +2877,10 @@ function buildPageTranslationPrompt(input) {
|
|
|
2883
2877
|
buildOutputFormatLine(
|
|
2884
2878
|
Object.keys(input.translatableFrontmatter).length > 0,
|
|
2885
2879
|
input.slugStrategy
|
|
2886
|
-
)
|
|
2880
|
+
),
|
|
2881
|
+
// Retry context is locale-specific suffix material: it MUST stay after the
|
|
2882
|
+
// EN body so the cacheable prefix is byte-identical with and without it.
|
|
2883
|
+
...input.previousError ? buildRetryContextSection(input.previousError) : []
|
|
2887
2884
|
];
|
|
2888
2885
|
return [...prefix, ...suffix].join("\n");
|
|
2889
2886
|
}
|
|
@@ -3098,7 +3095,8 @@ function prepareTranslation(config, item, options, startedAt) {
|
|
|
3098
3095
|
contextLabel: resolveContextLabel(enDoc, item.enSlug),
|
|
3099
3096
|
translatableFrontmatter: payload.frontmatter,
|
|
3100
3097
|
enBody: payload.body,
|
|
3101
|
-
slugStrategy: type.slugStrategy
|
|
3098
|
+
slugStrategy: type.slugStrategy,
|
|
3099
|
+
previousError: item.previousError
|
|
3102
3100
|
});
|
|
3103
3101
|
const responseSchema = buildGeminiResponseSchema(type.schema, type.slugStrategy);
|
|
3104
3102
|
return {
|
|
@@ -3510,6 +3508,82 @@ async function runPool(items, concurrency, worker) {
|
|
|
3510
3508
|
function labelForItem(item) {
|
|
3511
3509
|
return `${item.contentType}/${item.enSlug}@${item.locale}`;
|
|
3512
3510
|
}
|
|
3511
|
+
function buildRetryWorklist(failed, originalByKey) {
|
|
3512
|
+
return failed.map((result) => {
|
|
3513
|
+
const key = translationItemKey(result);
|
|
3514
|
+
const original = originalByKey.get(key);
|
|
3515
|
+
return {
|
|
3516
|
+
contentType: result.contentType,
|
|
3517
|
+
enSlug: result.enSlug,
|
|
3518
|
+
locale: result.locale,
|
|
3519
|
+
reason: original?.reason ?? "missing",
|
|
3520
|
+
currentEnHash: original?.currentEnHash ?? "",
|
|
3521
|
+
storedEnHash: original?.storedEnHash,
|
|
3522
|
+
previousError: result.error ?? "Translation failed"
|
|
3523
|
+
};
|
|
3524
|
+
});
|
|
3525
|
+
}
|
|
3526
|
+
async function runRetryRound(config, retryItems, options) {
|
|
3527
|
+
const startedAt = Date.now();
|
|
3528
|
+
const translateOne = options.translateOne ?? ((item) => translatePage(config, item, { model: options.model, force: true }));
|
|
3529
|
+
if (options.mode === "direct") {
|
|
3530
|
+
const active = /* @__PURE__ */ new Set();
|
|
3531
|
+
await runPool(retryItems, options.concurrency, async (item) => {
|
|
3532
|
+
const label = labelForItem(item);
|
|
3533
|
+
active.add(label);
|
|
3534
|
+
options.onProgress?.({ type: "item-start", item, active: [...active] });
|
|
3535
|
+
const result = await translateOne(item);
|
|
3536
|
+
active.delete(label);
|
|
3537
|
+
options.onResult(result);
|
|
3538
|
+
});
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
const entries = [];
|
|
3542
|
+
for (const item of retryItems) {
|
|
3543
|
+
const itemStartedAt = Date.now();
|
|
3544
|
+
try {
|
|
3545
|
+
const outcome = prepareTranslation(config, item, { model: options.model, force: true }, itemStartedAt);
|
|
3546
|
+
if (outcome.status === "done") {
|
|
3547
|
+
options.onResult(outcome.result);
|
|
3548
|
+
} else {
|
|
3549
|
+
entries.push({
|
|
3550
|
+
apiModel: resolveGeminiModelId(displayModelFor(outcome.prepared)),
|
|
3551
|
+
prompt: outcome.prepared.prompt,
|
|
3552
|
+
prepared: outcome.prepared
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
3555
|
+
} catch (error) {
|
|
3556
|
+
options.onResult(failedResultForItem(item, error, itemStartedAt));
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
const plans = planBatchJobs(entries);
|
|
3560
|
+
const submitted = await Promise.all(
|
|
3561
|
+
plans.map(async (plan, planIndex) => {
|
|
3562
|
+
try {
|
|
3563
|
+
return await submitBatchJobPlan(config, {
|
|
3564
|
+
plan,
|
|
3565
|
+
displayModel: normalizeGeminiDisplayName(plan.apiModel),
|
|
3566
|
+
jobIndex: planIndex,
|
|
3567
|
+
jobCount: plans.length,
|
|
3568
|
+
onProgress: options.onProgress
|
|
3569
|
+
});
|
|
3570
|
+
} catch (error) {
|
|
3571
|
+
for (const entry of plan.entries) {
|
|
3572
|
+
options.onResult(failedResultForItem(entry.prepared.item, error, startedAt));
|
|
3573
|
+
}
|
|
3574
|
+
return void 0;
|
|
3575
|
+
}
|
|
3576
|
+
})
|
|
3577
|
+
);
|
|
3578
|
+
const jobsToPoll = submitted.filter((row) => row !== void 0);
|
|
3579
|
+
if (jobsToPoll.length > 0) {
|
|
3580
|
+
await pollAndIngestBatchJobs(config, jobsToPoll, {
|
|
3581
|
+
jobCount: jobsToPoll.length,
|
|
3582
|
+
onProgress: options.onProgress,
|
|
3583
|
+
onResult: options.onResult
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3513
3587
|
async function translatePage(config, item, options = {}) {
|
|
3514
3588
|
const startedAt = Date.now();
|
|
3515
3589
|
let outcome;
|
|
@@ -3682,9 +3756,40 @@ async function translateWorklist(config, items, options = {}) {
|
|
|
3682
3756
|
});
|
|
3683
3757
|
}
|
|
3684
3758
|
}
|
|
3685
|
-
const
|
|
3759
|
+
const mainResults = collector.assemble();
|
|
3760
|
+
const failed = mainResults.filter((result) => result.failed);
|
|
3761
|
+
if (failed.length === 0) {
|
|
3762
|
+
const totals2 = summarizeResults(mainResults, Date.now() - startedAt);
|
|
3763
|
+
options.onProgress?.({ type: "done", results: mainResults, totals: totals2 });
|
|
3764
|
+
return mainResults;
|
|
3765
|
+
}
|
|
3766
|
+
options.onProgress?.({ type: "retry-start", failed });
|
|
3767
|
+
const originalByKey = new Map(
|
|
3768
|
+
items.map((item) => [translationItemKey(item), item])
|
|
3769
|
+
);
|
|
3770
|
+
const retryItems = buildRetryWorklist(failed, originalByKey);
|
|
3771
|
+
const retryByKey = /* @__PURE__ */ new Map();
|
|
3772
|
+
await runRetryRound(config, retryItems, {
|
|
3773
|
+
model: options.model,
|
|
3774
|
+
force: options.force,
|
|
3775
|
+
concurrency,
|
|
3776
|
+
mode,
|
|
3777
|
+
onProgress: options.onProgress,
|
|
3778
|
+
onResult: (result) => {
|
|
3779
|
+
retryByKey.set(translationItemKey(result), result);
|
|
3780
|
+
options.onProgress?.({ type: "item-done", result });
|
|
3781
|
+
}
|
|
3782
|
+
});
|
|
3783
|
+
const results = mainResults.map((result) => {
|
|
3784
|
+
if (!result.failed) return result;
|
|
3785
|
+
return retryByKey.get(translationItemKey(result)) ?? result;
|
|
3786
|
+
});
|
|
3787
|
+
const retriedTranslated = failed.filter((original) => {
|
|
3788
|
+
const retried = retryByKey.get(translationItemKey(original));
|
|
3789
|
+
return retried !== void 0 && !retried.failed && !retried.skipped;
|
|
3790
|
+
}).length;
|
|
3686
3791
|
const totals = summarizeResults(results, Date.now() - startedAt);
|
|
3687
|
-
options.onProgress?.({ type: "done", results, totals });
|
|
3792
|
+
options.onProgress?.({ type: "done", results, totals, retriedTranslated });
|
|
3688
3793
|
return results;
|
|
3689
3794
|
}
|
|
3690
3795
|
function countPendingItems(config, jobs) {
|