scribe-cms 0.0.10 → 0.0.12
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 +193 -111
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +191 -111
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +188 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +186 -110
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +118 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +116 -1
- package/dist/runtime.js.map +1 -1
- package/dist/src/core/types.d.ts +0 -2
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/loader/create-loader.d.ts.map +1 -1
- package/dist/src/translate/gemini-client.d.ts +5 -0
- package/dist/src/translate/gemini-client.d.ts.map +1 -1
- package/dist/src/translate/normalize-mdx-body.d.ts +9 -0
- package/dist/src/translate/normalize-mdx-body.d.ts.map +1 -0
- package/dist/src/translate/resolve-translate-config.d.ts.map +1 -1
- package/dist/src/translate/validate-mdx-body.d.ts +16 -0
- package/dist/src/translate/validate-mdx-body.d.ts.map +1 -0
- package/dist/src/validate/validate-project.d.ts.map +1 -1
- package/dist/studio/server.cjs +6 -0
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.js +4 -0
- package/dist/studio/server.js.map +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import path3 from 'path';
|
|
|
3
3
|
import fs2 from 'fs';
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
|
+
import remarkMdx from 'remark-mdx';
|
|
7
|
+
import remarkParse from 'remark-parse';
|
|
8
|
+
import { unified } from 'unified';
|
|
6
9
|
import { createJiti } from 'jiti';
|
|
7
10
|
import { createHash } from 'crypto';
|
|
8
11
|
import { GoogleGenAI } from '@google/genai';
|
|
@@ -687,6 +690,135 @@ function bulkLoadTranslations(db) {
|
|
|
687
690
|
return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
|
|
688
691
|
}
|
|
689
692
|
|
|
693
|
+
// src/translate/normalize-mdx-body.ts
|
|
694
|
+
function needsMdxEscapeNormalization(body) {
|
|
695
|
+
return /\\n[ \t/>]|<[\w.-][^>]*\\n/.test(body);
|
|
696
|
+
}
|
|
697
|
+
function normalizeTranslatedMdxBody(body) {
|
|
698
|
+
if (!needsMdxEscapeNormalization(body)) {
|
|
699
|
+
return { body, adjusted: false };
|
|
700
|
+
}
|
|
701
|
+
const normalized = body.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
702
|
+
return { body: normalized, adjusted: normalized !== body };
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/translate/sanitize-mdx-jsx.ts
|
|
706
|
+
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
707
|
+
let adjusted = false;
|
|
708
|
+
let out = "";
|
|
709
|
+
let i = 0;
|
|
710
|
+
while (i < body.length) {
|
|
711
|
+
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
712
|
+
out += body[i];
|
|
713
|
+
i += 1;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const tagStart = i;
|
|
717
|
+
i += 1;
|
|
718
|
+
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
719
|
+
const tagName = body.slice(tagStart + 1, i);
|
|
720
|
+
let tagOut = `<${tagName}`;
|
|
721
|
+
let tagAdjusted = false;
|
|
722
|
+
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
723
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
724
|
+
tagOut += body[i];
|
|
725
|
+
i += 1;
|
|
726
|
+
}
|
|
727
|
+
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
728
|
+
const attrStart = i;
|
|
729
|
+
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
730
|
+
body.slice(attrStart, i);
|
|
731
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
732
|
+
if (body[i] !== "=") {
|
|
733
|
+
tagOut += body.slice(attrStart, i);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
tagOut += body.slice(attrStart, i);
|
|
737
|
+
tagOut += "=";
|
|
738
|
+
i += 1;
|
|
739
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
740
|
+
const quote = body[i];
|
|
741
|
+
if (quote !== '"' && quote !== "'") {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (quote === "'") {
|
|
745
|
+
const valStart2 = i + 1;
|
|
746
|
+
i += 1;
|
|
747
|
+
while (i < body.length) {
|
|
748
|
+
if (body[i] === "\\") {
|
|
749
|
+
i += 2;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (body[i] === "'") break;
|
|
753
|
+
i += 1;
|
|
754
|
+
}
|
|
755
|
+
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
756
|
+
i += 1;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const valStart = i + 1;
|
|
760
|
+
i += 1;
|
|
761
|
+
let closeIdx = -1;
|
|
762
|
+
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
763
|
+
if (body[scan] !== '"') continue;
|
|
764
|
+
let j = scan + 1;
|
|
765
|
+
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
766
|
+
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
767
|
+
closeIdx = scan;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (closeIdx === -1) {
|
|
772
|
+
tagOut += body.slice(valStart - 1, i);
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
const value = body.slice(valStart, closeIdx);
|
|
776
|
+
const hasInternalQuote = value.includes('"');
|
|
777
|
+
if (hasInternalQuote) {
|
|
778
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
779
|
+
tagOut += `'${escaped}'`;
|
|
780
|
+
tagAdjusted = true;
|
|
781
|
+
} else {
|
|
782
|
+
tagOut += `"${value}"`;
|
|
783
|
+
}
|
|
784
|
+
i = closeIdx + 1;
|
|
785
|
+
}
|
|
786
|
+
if (tagAdjusted) adjusted = true;
|
|
787
|
+
tagOut += body[i] ?? "";
|
|
788
|
+
out += tagOut;
|
|
789
|
+
i += 1;
|
|
790
|
+
}
|
|
791
|
+
return { body: out, adjusted };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/translate/validate-mdx-body.ts
|
|
795
|
+
var parser = unified().use(remarkParse).use(remarkMdx);
|
|
796
|
+
function prepareTranslatedMdxBody(body) {
|
|
797
|
+
const normalized = normalizeTranslatedMdxBody(body);
|
|
798
|
+
const sanitized = sanitizeMdxJsxAttributeQuotes(normalized.body);
|
|
799
|
+
return {
|
|
800
|
+
body: sanitized.body,
|
|
801
|
+
adjusted: normalized.adjusted || sanitized.adjusted
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function validateMdxBody(body) {
|
|
805
|
+
try {
|
|
806
|
+
parser.parse(body);
|
|
807
|
+
return { ok: true };
|
|
808
|
+
} catch (error) {
|
|
809
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
810
|
+
return { ok: false, error: message };
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function assertValidTranslatedMdxBody(body) {
|
|
814
|
+
const prepared = prepareTranslatedMdxBody(body);
|
|
815
|
+
const validated = validateMdxBody(prepared.body);
|
|
816
|
+
if (!validated.ok) {
|
|
817
|
+
throw new Error(`MDX validation failed: ${validated.error}`);
|
|
818
|
+
}
|
|
819
|
+
return prepared;
|
|
820
|
+
}
|
|
821
|
+
|
|
690
822
|
// src/loader/normalize-en.ts
|
|
691
823
|
function normalizeEnFrontmatter(data) {
|
|
692
824
|
const out = { ...data };
|
|
@@ -807,7 +939,7 @@ function buildDocumentFromTranslation(row, enDoc, type, config) {
|
|
|
807
939
|
noindex: seo.noindex,
|
|
808
940
|
canonicalPathOverride: seo.canonicalPathOverride,
|
|
809
941
|
frontmatter,
|
|
810
|
-
content: row.body
|
|
942
|
+
content: prepareTranslatedMdxBody(row.body).body
|
|
811
943
|
};
|
|
812
944
|
}
|
|
813
945
|
var DEV_REVALIDATE_MS = 1500;
|
|
@@ -2075,6 +2207,20 @@ function listEnSlugs3(rootDir, contentDir) {
|
|
|
2075
2207
|
if (!fs2.existsSync(dir)) return [];
|
|
2076
2208
|
return fs2.readdirSync(dir).filter(isPublishableContentFile).map((f) => f.replace(/\.(md|mdx)$/, ""));
|
|
2077
2209
|
}
|
|
2210
|
+
function validateDocumentMdxBody(issues, input) {
|
|
2211
|
+
const preparedBody = prepareTranslatedMdxBody(input.body).body;
|
|
2212
|
+
const mdxValidation = validateMdxBody(preparedBody);
|
|
2213
|
+
if (!mdxValidation.ok) {
|
|
2214
|
+
issues.push({
|
|
2215
|
+
level: "error",
|
|
2216
|
+
contentType: input.contentType,
|
|
2217
|
+
enSlug: input.enSlug,
|
|
2218
|
+
locale: input.locale,
|
|
2219
|
+
field: "body",
|
|
2220
|
+
message: `Invalid MDX: ${mdxValidation.error}`
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2078
2224
|
function validateProject(config) {
|
|
2079
2225
|
const issues = [];
|
|
2080
2226
|
const project = createProject(config);
|
|
@@ -2133,6 +2279,12 @@ function validateProject(config) {
|
|
|
2133
2279
|
})) {
|
|
2134
2280
|
issues.push(issue);
|
|
2135
2281
|
}
|
|
2282
|
+
validateDocumentMdxBody(issues, {
|
|
2283
|
+
contentType: type.id,
|
|
2284
|
+
enSlug,
|
|
2285
|
+
locale: config.defaultLocale,
|
|
2286
|
+
body: enDoc.content
|
|
2287
|
+
});
|
|
2136
2288
|
for (const locale of config.locales) {
|
|
2137
2289
|
if (locale === config.defaultLocale) continue;
|
|
2138
2290
|
const row = getTranslation(db, type.id, enSlug, locale);
|
|
@@ -2148,15 +2300,22 @@ function validateProject(config) {
|
|
|
2148
2300
|
message: issue.message
|
|
2149
2301
|
});
|
|
2150
2302
|
}
|
|
2303
|
+
const preparedBody = prepareTranslatedMdxBody(row.body).body;
|
|
2151
2304
|
for (const issue of validateDocumentAssets(config, {
|
|
2152
2305
|
contentType: type.id,
|
|
2153
2306
|
enSlug,
|
|
2154
2307
|
locale,
|
|
2155
2308
|
frontmatter: localeFm,
|
|
2156
|
-
body:
|
|
2309
|
+
body: preparedBody
|
|
2157
2310
|
})) {
|
|
2158
2311
|
issues.push(issue);
|
|
2159
2312
|
}
|
|
2313
|
+
validateDocumentMdxBody(issues, {
|
|
2314
|
+
contentType: type.id,
|
|
2315
|
+
enSlug,
|
|
2316
|
+
locale,
|
|
2317
|
+
body: row.body
|
|
2318
|
+
});
|
|
2160
2319
|
}
|
|
2161
2320
|
}
|
|
2162
2321
|
try {
|
|
@@ -2305,12 +2464,20 @@ function normalizeGeminiDisplayName(model) {
|
|
|
2305
2464
|
// src/translate/gemini-client.ts
|
|
2306
2465
|
var DEFAULT_MODEL = DEFAULT_GEMINI_MODEL;
|
|
2307
2466
|
function extractJson(text) {
|
|
2308
|
-
const
|
|
2467
|
+
const trimmed = text.trim();
|
|
2468
|
+
const fenced = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```$/);
|
|
2309
2469
|
if (fenced?.[1]) return fenced[1].trim();
|
|
2310
|
-
const start =
|
|
2311
|
-
const end =
|
|
2312
|
-
if (start >= 0 && end > start) return
|
|
2313
|
-
return
|
|
2470
|
+
const start = trimmed.indexOf("{");
|
|
2471
|
+
const end = trimmed.lastIndexOf("}");
|
|
2472
|
+
if (start >= 0 && end > start) return trimmed.slice(start, end + 1);
|
|
2473
|
+
return trimmed;
|
|
2474
|
+
}
|
|
2475
|
+
function parseGeminiResponse(text) {
|
|
2476
|
+
try {
|
|
2477
|
+
return JSON.parse(text.trim());
|
|
2478
|
+
} catch {
|
|
2479
|
+
return JSON.parse(extractJson(text));
|
|
2480
|
+
}
|
|
2314
2481
|
}
|
|
2315
2482
|
async function translatePageWithGemini(input) {
|
|
2316
2483
|
const apiKey = input.apiKey ?? process.env.GEMINI_API_KEY;
|
|
@@ -2331,7 +2498,7 @@ async function translatePageWithGemini(input) {
|
|
|
2331
2498
|
}
|
|
2332
2499
|
});
|
|
2333
2500
|
const raw = response.text ?? "";
|
|
2334
|
-
const parsed =
|
|
2501
|
+
const parsed = parseGeminiResponse(raw);
|
|
2335
2502
|
if (!parsed.frontmatter || typeof parsed.body !== "string") {
|
|
2336
2503
|
throw new Error("Gemini response missing frontmatter/body");
|
|
2337
2504
|
}
|
|
@@ -2488,24 +2655,21 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
|
|
|
2488
2655
|
}
|
|
2489
2656
|
|
|
2490
2657
|
// src/translate/resolve-translate-config.ts
|
|
2491
|
-
|
|
2658
|
+
var BUILTIN_TRANSLATE_RULES = [
|
|
2659
|
+
"Do not translate brand or product names unless the brand has a well-known localized name in the target market (rare). Keep the original spelling and capitalization.",
|
|
2660
|
+
"Return the MDX body with real line breaks; do not use JSON escape sequences like \\n or \\t in the body string.",
|
|
2661
|
+
'In JSX attributes (e.g. FaqItem question="..."), use single quotes when the value contains double-quote characters (e.g. Hebrew \u05D3\u05D5\u05D0"\u05DC), or escape them as \\".'
|
|
2662
|
+
];
|
|
2663
|
+
function slugStrategyRules(slugStrategy) {
|
|
2492
2664
|
if (slugStrategy === "localized") {
|
|
2493
|
-
|
|
2665
|
+
return [
|
|
2494
2666
|
"Provide a URL slug in JSON field `slug` that is Localized into the target language.",
|
|
2495
2667
|
"Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
|
|
2496
2668
|
"Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
|
|
2497
2669
|
"For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
|
|
2498
2670
|
"Do NOT append locale codes to the slug (e.g. -fr, -he, -zh-cn). Locale routing is handled by the URL prefix, not the slug.",
|
|
2499
|
-
|
|
2671
|
+
"Preserve 4-digit years and proper nouns in slugs."
|
|
2500
2672
|
];
|
|
2501
|
-
if (preserveTerms?.length) {
|
|
2502
|
-
rules.push(
|
|
2503
|
-
`Preserve these terms verbatim in slugs (lowercase): ${preserveTerms.join(", ")}.`
|
|
2504
|
-
);
|
|
2505
|
-
} else {
|
|
2506
|
-
rules.push("Preserve 4-digit years and proper nouns in slugs.");
|
|
2507
|
-
}
|
|
2508
|
-
return rules;
|
|
2509
2673
|
}
|
|
2510
2674
|
return ["Do NOT output a slug \u2014 slugStrategy is fixed."];
|
|
2511
2675
|
}
|
|
@@ -2518,9 +2682,10 @@ function mergeTranslateConfig(project, type, slugStrategy) {
|
|
|
2518
2682
|
promptOverride: type?.prompt ?? project?.prompt,
|
|
2519
2683
|
context: mergeContext(project, type),
|
|
2520
2684
|
rules: [
|
|
2685
|
+
...BUILTIN_TRANSLATE_RULES,
|
|
2521
2686
|
...project?.rules ?? [],
|
|
2522
2687
|
...type?.rules ?? [],
|
|
2523
|
-
...slugStrategyRules(slugStrategy
|
|
2688
|
+
...slugStrategyRules(slugStrategy)
|
|
2524
2689
|
],
|
|
2525
2690
|
model: type?.model ?? project?.defaultModel
|
|
2526
2691
|
};
|
|
@@ -2529,95 +2694,6 @@ function resolveTranslateConfig(config, type) {
|
|
|
2529
2694
|
return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
|
|
2530
2695
|
}
|
|
2531
2696
|
|
|
2532
|
-
// src/translate/sanitize-mdx-jsx.ts
|
|
2533
|
-
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
2534
|
-
let adjusted = false;
|
|
2535
|
-
let out = "";
|
|
2536
|
-
let i = 0;
|
|
2537
|
-
while (i < body.length) {
|
|
2538
|
-
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
2539
|
-
out += body[i];
|
|
2540
|
-
i += 1;
|
|
2541
|
-
continue;
|
|
2542
|
-
}
|
|
2543
|
-
const tagStart = i;
|
|
2544
|
-
i += 1;
|
|
2545
|
-
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
2546
|
-
const tagName = body.slice(tagStart + 1, i);
|
|
2547
|
-
let tagOut = `<${tagName}`;
|
|
2548
|
-
let tagAdjusted = false;
|
|
2549
|
-
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
2550
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
2551
|
-
tagOut += body[i];
|
|
2552
|
-
i += 1;
|
|
2553
|
-
}
|
|
2554
|
-
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
2555
|
-
const attrStart = i;
|
|
2556
|
-
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
2557
|
-
body.slice(attrStart, i);
|
|
2558
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2559
|
-
if (body[i] !== "=") {
|
|
2560
|
-
tagOut += body.slice(attrStart, i);
|
|
2561
|
-
continue;
|
|
2562
|
-
}
|
|
2563
|
-
tagOut += body.slice(attrStart, i);
|
|
2564
|
-
tagOut += "=";
|
|
2565
|
-
i += 1;
|
|
2566
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2567
|
-
const quote = body[i];
|
|
2568
|
-
if (quote !== '"' && quote !== "'") {
|
|
2569
|
-
continue;
|
|
2570
|
-
}
|
|
2571
|
-
if (quote === "'") {
|
|
2572
|
-
const valStart2 = i + 1;
|
|
2573
|
-
i += 1;
|
|
2574
|
-
while (i < body.length) {
|
|
2575
|
-
if (body[i] === "\\") {
|
|
2576
|
-
i += 2;
|
|
2577
|
-
continue;
|
|
2578
|
-
}
|
|
2579
|
-
if (body[i] === "'") break;
|
|
2580
|
-
i += 1;
|
|
2581
|
-
}
|
|
2582
|
-
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
2583
|
-
i += 1;
|
|
2584
|
-
continue;
|
|
2585
|
-
}
|
|
2586
|
-
const valStart = i + 1;
|
|
2587
|
-
i += 1;
|
|
2588
|
-
let closeIdx = -1;
|
|
2589
|
-
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
2590
|
-
if (body[scan] !== '"') continue;
|
|
2591
|
-
let j = scan + 1;
|
|
2592
|
-
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
2593
|
-
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
2594
|
-
closeIdx = scan;
|
|
2595
|
-
break;
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
if (closeIdx === -1) {
|
|
2599
|
-
tagOut += body.slice(valStart - 1, i);
|
|
2600
|
-
break;
|
|
2601
|
-
}
|
|
2602
|
-
const value = body.slice(valStart, closeIdx);
|
|
2603
|
-
const hasInternalQuote = value.includes('"');
|
|
2604
|
-
if (hasInternalQuote) {
|
|
2605
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2606
|
-
tagOut += `'${escaped}'`;
|
|
2607
|
-
tagAdjusted = true;
|
|
2608
|
-
} else {
|
|
2609
|
-
tagOut += `"${value}"`;
|
|
2610
|
-
}
|
|
2611
|
-
i = closeIdx + 1;
|
|
2612
|
-
}
|
|
2613
|
-
if (tagAdjusted) adjusted = true;
|
|
2614
|
-
tagOut += body[i] ?? "";
|
|
2615
|
-
out += tagOut;
|
|
2616
|
-
i += 1;
|
|
2617
|
-
}
|
|
2618
|
-
return { body: out, adjusted };
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
2697
|
// src/translate/validate-translation.ts
|
|
2622
2698
|
function formatZodIssues(error) {
|
|
2623
2699
|
return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
@@ -2751,7 +2827,7 @@ async function translatePage(config, item, options = {}) {
|
|
|
2751
2827
|
if (!validated.ok) {
|
|
2752
2828
|
throw new Error(`Translation validation failed: ${validated.error}`);
|
|
2753
2829
|
}
|
|
2754
|
-
const { body: translatedBody, adjusted: mdxAdjusted } =
|
|
2830
|
+
const { body: translatedBody, adjusted: mdxAdjusted } = assertValidTranslatedMdxBody(
|
|
2755
2831
|
result.parsed.body
|
|
2756
2832
|
);
|
|
2757
2833
|
const writeDb = openStore(config, "readwrite");
|