scribe-cms 0.0.9 → 0.0.11
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 +176 -116
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +174 -116
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +171 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +169 -115
- 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/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/src/validate/validate-redirects.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.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var path3 = require('path');
|
|
|
5
5
|
var fs2 = require('fs');
|
|
6
6
|
var matter = require('gray-matter');
|
|
7
7
|
var Database = require('better-sqlite3');
|
|
8
|
+
var remarkMdx = require('remark-mdx');
|
|
9
|
+
var remarkParse = require('remark-parse');
|
|
10
|
+
var unified = require('unified');
|
|
8
11
|
var jiti = require('jiti');
|
|
9
12
|
var crypto = require('crypto');
|
|
10
13
|
var genai = require('@google/genai');
|
|
@@ -15,6 +18,8 @@ var path3__default = /*#__PURE__*/_interopDefault(path3);
|
|
|
15
18
|
var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
16
19
|
var matter__default = /*#__PURE__*/_interopDefault(matter);
|
|
17
20
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
21
|
+
var remarkMdx__default = /*#__PURE__*/_interopDefault(remarkMdx);
|
|
22
|
+
var remarkParse__default = /*#__PURE__*/_interopDefault(remarkParse);
|
|
18
23
|
|
|
19
24
|
// ../../node_modules/tsup/assets/cjs_shims.js
|
|
20
25
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
@@ -698,6 +703,135 @@ function bulkLoadTranslations(db) {
|
|
|
698
703
|
return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
|
|
699
704
|
}
|
|
700
705
|
|
|
706
|
+
// src/translate/normalize-mdx-body.ts
|
|
707
|
+
function needsMdxEscapeNormalization(body) {
|
|
708
|
+
return /\\n[ \t/>]|<[\w.-][^>]*\\n/.test(body);
|
|
709
|
+
}
|
|
710
|
+
function normalizeTranslatedMdxBody(body) {
|
|
711
|
+
if (!needsMdxEscapeNormalization(body)) {
|
|
712
|
+
return { body, adjusted: false };
|
|
713
|
+
}
|
|
714
|
+
const normalized = body.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
715
|
+
return { body: normalized, adjusted: normalized !== body };
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/translate/sanitize-mdx-jsx.ts
|
|
719
|
+
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
720
|
+
let adjusted = false;
|
|
721
|
+
let out = "";
|
|
722
|
+
let i = 0;
|
|
723
|
+
while (i < body.length) {
|
|
724
|
+
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
725
|
+
out += body[i];
|
|
726
|
+
i += 1;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const tagStart = i;
|
|
730
|
+
i += 1;
|
|
731
|
+
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
732
|
+
const tagName = body.slice(tagStart + 1, i);
|
|
733
|
+
let tagOut = `<${tagName}`;
|
|
734
|
+
let tagAdjusted = false;
|
|
735
|
+
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
736
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
737
|
+
tagOut += body[i];
|
|
738
|
+
i += 1;
|
|
739
|
+
}
|
|
740
|
+
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
741
|
+
const attrStart = i;
|
|
742
|
+
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
743
|
+
body.slice(attrStart, i);
|
|
744
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
745
|
+
if (body[i] !== "=") {
|
|
746
|
+
tagOut += body.slice(attrStart, i);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
tagOut += body.slice(attrStart, i);
|
|
750
|
+
tagOut += "=";
|
|
751
|
+
i += 1;
|
|
752
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
753
|
+
const quote = body[i];
|
|
754
|
+
if (quote !== '"' && quote !== "'") {
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (quote === "'") {
|
|
758
|
+
const valStart2 = i + 1;
|
|
759
|
+
i += 1;
|
|
760
|
+
while (i < body.length) {
|
|
761
|
+
if (body[i] === "\\") {
|
|
762
|
+
i += 2;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (body[i] === "'") break;
|
|
766
|
+
i += 1;
|
|
767
|
+
}
|
|
768
|
+
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
769
|
+
i += 1;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const valStart = i + 1;
|
|
773
|
+
i += 1;
|
|
774
|
+
let closeIdx = -1;
|
|
775
|
+
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
776
|
+
if (body[scan] !== '"') continue;
|
|
777
|
+
let j = scan + 1;
|
|
778
|
+
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
779
|
+
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
780
|
+
closeIdx = scan;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (closeIdx === -1) {
|
|
785
|
+
tagOut += body.slice(valStart - 1, i);
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
const value = body.slice(valStart, closeIdx);
|
|
789
|
+
const hasInternalQuote = value.includes('"');
|
|
790
|
+
if (hasInternalQuote) {
|
|
791
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
792
|
+
tagOut += `'${escaped}'`;
|
|
793
|
+
tagAdjusted = true;
|
|
794
|
+
} else {
|
|
795
|
+
tagOut += `"${value}"`;
|
|
796
|
+
}
|
|
797
|
+
i = closeIdx + 1;
|
|
798
|
+
}
|
|
799
|
+
if (tagAdjusted) adjusted = true;
|
|
800
|
+
tagOut += body[i] ?? "";
|
|
801
|
+
out += tagOut;
|
|
802
|
+
i += 1;
|
|
803
|
+
}
|
|
804
|
+
return { body: out, adjusted };
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/translate/validate-mdx-body.ts
|
|
808
|
+
var parser = unified.unified().use(remarkParse__default.default).use(remarkMdx__default.default);
|
|
809
|
+
function prepareTranslatedMdxBody(body) {
|
|
810
|
+
const normalized = normalizeTranslatedMdxBody(body);
|
|
811
|
+
const sanitized = sanitizeMdxJsxAttributeQuotes(normalized.body);
|
|
812
|
+
return {
|
|
813
|
+
body: sanitized.body,
|
|
814
|
+
adjusted: normalized.adjusted || sanitized.adjusted
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function validateMdxBody(body) {
|
|
818
|
+
try {
|
|
819
|
+
parser.parse(body);
|
|
820
|
+
return { ok: true };
|
|
821
|
+
} catch (error) {
|
|
822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
823
|
+
return { ok: false, error: message };
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function assertValidTranslatedMdxBody(body) {
|
|
827
|
+
const prepared = prepareTranslatedMdxBody(body);
|
|
828
|
+
const validated = validateMdxBody(prepared.body);
|
|
829
|
+
if (!validated.ok) {
|
|
830
|
+
throw new Error(`MDX validation failed: ${validated.error}`);
|
|
831
|
+
}
|
|
832
|
+
return prepared;
|
|
833
|
+
}
|
|
834
|
+
|
|
701
835
|
// src/loader/normalize-en.ts
|
|
702
836
|
function normalizeEnFrontmatter(data) {
|
|
703
837
|
const out = { ...data };
|
|
@@ -818,7 +952,7 @@ function buildDocumentFromTranslation(row, enDoc, type, config) {
|
|
|
818
952
|
noindex: seo.noindex,
|
|
819
953
|
canonicalPathOverride: seo.canonicalPathOverride,
|
|
820
954
|
frontmatter,
|
|
821
|
-
content: row.body
|
|
955
|
+
content: prepareTranslatedMdxBody(row.body).body
|
|
822
956
|
};
|
|
823
957
|
}
|
|
824
958
|
var DEV_REVALIDATE_MS = 1500;
|
|
@@ -1782,7 +1916,7 @@ function validateTypeRedirects(project) {
|
|
|
1782
1916
|
}
|
|
1783
1917
|
for (const entry of loaded.entries) {
|
|
1784
1918
|
for (const from of entry.fromSlugs) {
|
|
1785
|
-
if (from === entry.toSlug) {
|
|
1919
|
+
if (entry.kind === "same-type" && from === entry.toSlug) {
|
|
1786
1920
|
issues.push({
|
|
1787
1921
|
level: "error",
|
|
1788
1922
|
contentType: type.id,
|
|
@@ -1876,16 +2010,17 @@ function validateTypeRedirects(project) {
|
|
|
1876
2010
|
if (!targetType?.path) continue;
|
|
1877
2011
|
for (const from of entry.fromSlugs) {
|
|
1878
2012
|
if (globalFrom.get(from)?.typeId !== file.contentTypeId) continue;
|
|
1879
|
-
const
|
|
1880
|
-
if (
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2013
|
+
const targetAsSource = globalFrom.get(entry.toSlug);
|
|
2014
|
+
if (!targetAsSource) continue;
|
|
2015
|
+
const sameSlugCrossType = entry.kind === "cross-type" && entry.fromSlugs.includes(entry.toSlug) && targetAsSource.typeId === file.contentTypeId;
|
|
2016
|
+
if (sameSlugCrossType) continue;
|
|
2017
|
+
issues.push({
|
|
2018
|
+
level: "error",
|
|
2019
|
+
contentType: file.contentTypeId,
|
|
2020
|
+
enSlug: from,
|
|
2021
|
+
field: TYPE_REDIRECTS_FILENAME,
|
|
2022
|
+
message: `Redirect chain detected: "${from}" \u2192 "${entry.toSlug}" \u2192 existing redirect source`
|
|
2023
|
+
});
|
|
1889
2024
|
}
|
|
1890
2025
|
}
|
|
1891
2026
|
}
|
|
@@ -2158,15 +2293,27 @@ function validateProject(config) {
|
|
|
2158
2293
|
message: issue.message
|
|
2159
2294
|
});
|
|
2160
2295
|
}
|
|
2296
|
+
const preparedBody = prepareTranslatedMdxBody(row.body).body;
|
|
2161
2297
|
for (const issue of validateDocumentAssets(config, {
|
|
2162
2298
|
contentType: type.id,
|
|
2163
2299
|
enSlug,
|
|
2164
2300
|
locale,
|
|
2165
2301
|
frontmatter: localeFm,
|
|
2166
|
-
body:
|
|
2302
|
+
body: preparedBody
|
|
2167
2303
|
})) {
|
|
2168
2304
|
issues.push(issue);
|
|
2169
2305
|
}
|
|
2306
|
+
const mdxValidation = validateMdxBody(preparedBody);
|
|
2307
|
+
if (!mdxValidation.ok) {
|
|
2308
|
+
issues.push({
|
|
2309
|
+
level: "error",
|
|
2310
|
+
contentType: type.id,
|
|
2311
|
+
enSlug,
|
|
2312
|
+
locale,
|
|
2313
|
+
field: "body",
|
|
2314
|
+
message: `Invalid MDX: ${mdxValidation.error}`
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2170
2317
|
}
|
|
2171
2318
|
}
|
|
2172
2319
|
try {
|
|
@@ -2498,24 +2645,21 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
|
|
|
2498
2645
|
}
|
|
2499
2646
|
|
|
2500
2647
|
// src/translate/resolve-translate-config.ts
|
|
2501
|
-
|
|
2648
|
+
var BUILTIN_TRANSLATE_RULES = [
|
|
2649
|
+
"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.",
|
|
2650
|
+
"Return the MDX body with real line breaks; do not use JSON escape sequences like \\n or \\t in the body string.",
|
|
2651
|
+
'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 \\".'
|
|
2652
|
+
];
|
|
2653
|
+
function slugStrategyRules(slugStrategy) {
|
|
2502
2654
|
if (slugStrategy === "localized") {
|
|
2503
|
-
|
|
2655
|
+
return [
|
|
2504
2656
|
"Provide a URL slug in JSON field `slug` that is Localized into the target language.",
|
|
2505
2657
|
"Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
|
|
2506
2658
|
"Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
|
|
2507
2659
|
"For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
|
|
2508
2660
|
"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.",
|
|
2509
|
-
|
|
2661
|
+
"Preserve 4-digit years and proper nouns in slugs."
|
|
2510
2662
|
];
|
|
2511
|
-
if (preserveTerms?.length) {
|
|
2512
|
-
rules.push(
|
|
2513
|
-
`Preserve these terms verbatim in slugs (lowercase): ${preserveTerms.join(", ")}.`
|
|
2514
|
-
);
|
|
2515
|
-
} else {
|
|
2516
|
-
rules.push("Preserve 4-digit years and proper nouns in slugs.");
|
|
2517
|
-
}
|
|
2518
|
-
return rules;
|
|
2519
2663
|
}
|
|
2520
2664
|
return ["Do NOT output a slug \u2014 slugStrategy is fixed."];
|
|
2521
2665
|
}
|
|
@@ -2528,9 +2672,10 @@ function mergeTranslateConfig(project, type, slugStrategy) {
|
|
|
2528
2672
|
promptOverride: type?.prompt ?? project?.prompt,
|
|
2529
2673
|
context: mergeContext(project, type),
|
|
2530
2674
|
rules: [
|
|
2675
|
+
...BUILTIN_TRANSLATE_RULES,
|
|
2531
2676
|
...project?.rules ?? [],
|
|
2532
2677
|
...type?.rules ?? [],
|
|
2533
|
-
...slugStrategyRules(slugStrategy
|
|
2678
|
+
...slugStrategyRules(slugStrategy)
|
|
2534
2679
|
],
|
|
2535
2680
|
model: type?.model ?? project?.defaultModel
|
|
2536
2681
|
};
|
|
@@ -2539,95 +2684,6 @@ function resolveTranslateConfig(config, type) {
|
|
|
2539
2684
|
return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
|
|
2540
2685
|
}
|
|
2541
2686
|
|
|
2542
|
-
// src/translate/sanitize-mdx-jsx.ts
|
|
2543
|
-
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
2544
|
-
let adjusted = false;
|
|
2545
|
-
let out = "";
|
|
2546
|
-
let i = 0;
|
|
2547
|
-
while (i < body.length) {
|
|
2548
|
-
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
2549
|
-
out += body[i];
|
|
2550
|
-
i += 1;
|
|
2551
|
-
continue;
|
|
2552
|
-
}
|
|
2553
|
-
const tagStart = i;
|
|
2554
|
-
i += 1;
|
|
2555
|
-
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
2556
|
-
const tagName = body.slice(tagStart + 1, i);
|
|
2557
|
-
let tagOut = `<${tagName}`;
|
|
2558
|
-
let tagAdjusted = false;
|
|
2559
|
-
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
2560
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
2561
|
-
tagOut += body[i];
|
|
2562
|
-
i += 1;
|
|
2563
|
-
}
|
|
2564
|
-
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
2565
|
-
const attrStart = i;
|
|
2566
|
-
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
2567
|
-
body.slice(attrStart, i);
|
|
2568
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2569
|
-
if (body[i] !== "=") {
|
|
2570
|
-
tagOut += body.slice(attrStart, i);
|
|
2571
|
-
continue;
|
|
2572
|
-
}
|
|
2573
|
-
tagOut += body.slice(attrStart, i);
|
|
2574
|
-
tagOut += "=";
|
|
2575
|
-
i += 1;
|
|
2576
|
-
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2577
|
-
const quote = body[i];
|
|
2578
|
-
if (quote !== '"' && quote !== "'") {
|
|
2579
|
-
continue;
|
|
2580
|
-
}
|
|
2581
|
-
if (quote === "'") {
|
|
2582
|
-
const valStart2 = i + 1;
|
|
2583
|
-
i += 1;
|
|
2584
|
-
while (i < body.length) {
|
|
2585
|
-
if (body[i] === "\\") {
|
|
2586
|
-
i += 2;
|
|
2587
|
-
continue;
|
|
2588
|
-
}
|
|
2589
|
-
if (body[i] === "'") break;
|
|
2590
|
-
i += 1;
|
|
2591
|
-
}
|
|
2592
|
-
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
2593
|
-
i += 1;
|
|
2594
|
-
continue;
|
|
2595
|
-
}
|
|
2596
|
-
const valStart = i + 1;
|
|
2597
|
-
i += 1;
|
|
2598
|
-
let closeIdx = -1;
|
|
2599
|
-
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
2600
|
-
if (body[scan] !== '"') continue;
|
|
2601
|
-
let j = scan + 1;
|
|
2602
|
-
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
2603
|
-
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
2604
|
-
closeIdx = scan;
|
|
2605
|
-
break;
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
if (closeIdx === -1) {
|
|
2609
|
-
tagOut += body.slice(valStart - 1, i);
|
|
2610
|
-
break;
|
|
2611
|
-
}
|
|
2612
|
-
const value = body.slice(valStart, closeIdx);
|
|
2613
|
-
const hasInternalQuote = value.includes('"');
|
|
2614
|
-
if (hasInternalQuote) {
|
|
2615
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2616
|
-
tagOut += `'${escaped}'`;
|
|
2617
|
-
tagAdjusted = true;
|
|
2618
|
-
} else {
|
|
2619
|
-
tagOut += `"${value}"`;
|
|
2620
|
-
}
|
|
2621
|
-
i = closeIdx + 1;
|
|
2622
|
-
}
|
|
2623
|
-
if (tagAdjusted) adjusted = true;
|
|
2624
|
-
tagOut += body[i] ?? "";
|
|
2625
|
-
out += tagOut;
|
|
2626
|
-
i += 1;
|
|
2627
|
-
}
|
|
2628
|
-
return { body: out, adjusted };
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
2687
|
// src/translate/validate-translation.ts
|
|
2632
2688
|
function formatZodIssues(error) {
|
|
2633
2689
|
return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
@@ -2761,7 +2817,7 @@ async function translatePage(config, item, options = {}) {
|
|
|
2761
2817
|
if (!validated.ok) {
|
|
2762
2818
|
throw new Error(`Translation validation failed: ${validated.error}`);
|
|
2763
2819
|
}
|
|
2764
|
-
const { body: translatedBody, adjusted: mdxAdjusted } =
|
|
2820
|
+
const { body: translatedBody, adjusted: mdxAdjusted } = assertValidTranslatedMdxBody(
|
|
2765
2821
|
result.parsed.body
|
|
2766
2822
|
);
|
|
2767
2823
|
const writeDb = openStore(config, "readwrite");
|