scribe-cms 0.0.6 → 0.0.8
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 +170 -30
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +169 -29
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/translate-progress.d.ts.map +1 -1
- package/dist/index.cjs +145 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +145 -18
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +5 -0
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +5 -1
- package/dist/runtime.js.map +1 -1
- package/dist/src/core/localized-slug.d.ts +8 -0
- package/dist/src/core/localized-slug.d.ts.map +1 -0
- package/dist/src/core/types.d.ts +3 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/create-project.d.ts.map +1 -1
- package/dist/src/create-scribe.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/translate/page-translator.d.ts +6 -0
- package/dist/src/translate/page-translator.d.ts.map +1 -1
- package/dist/src/translate/prompts/translation-prompt.d.ts.map +1 -1
- package/dist/src/translate/resolve-translate-config.d.ts.map +1 -1
- package/dist/src/translate/sanitize-mdx-jsx.d.ts +9 -0
- package/dist/src/translate/sanitize-mdx-jsx.d.ts.map +1 -0
- package/dist/src/validate/validate-slug-suffix.d.ts.map +1 -1
- package/package.json +2 -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;AAiE7C,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,CAmJjC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1187,6 +1187,9 @@ function createProject(config) {
|
|
|
1187
1187
|
getType: getRuntime,
|
|
1188
1188
|
listTypes() {
|
|
1189
1189
|
return Array.from(runtimes.values());
|
|
1190
|
+
},
|
|
1191
|
+
listRoutableTypes() {
|
|
1192
|
+
return Array.from(runtimes.values()).filter((runtime) => isRoutableType(runtime.config));
|
|
1190
1193
|
}
|
|
1191
1194
|
};
|
|
1192
1195
|
}
|
|
@@ -1682,6 +1685,7 @@ function createScribe(input) {
|
|
|
1682
1685
|
project,
|
|
1683
1686
|
getType: project.getType,
|
|
1684
1687
|
listTypes: project.listTypes,
|
|
1688
|
+
listRoutableTypes: project.listRoutableTypes,
|
|
1685
1689
|
sitemap(options) {
|
|
1686
1690
|
return generateSitemap(project, options);
|
|
1687
1691
|
}
|
|
@@ -1880,23 +1884,43 @@ function validateDocumentAssets(config, input) {
|
|
|
1880
1884
|
return issues;
|
|
1881
1885
|
}
|
|
1882
1886
|
|
|
1887
|
+
// src/core/localized-slug.ts
|
|
1888
|
+
function findLocaleSuffixInSlug(slug, localeCodes) {
|
|
1889
|
+
const sorted = [...localeCodes].sort((a, b) => b.length - a.length);
|
|
1890
|
+
for (const code of sorted) {
|
|
1891
|
+
if (slug.endsWith(`-${code}`)) {
|
|
1892
|
+
return code;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return void 0;
|
|
1896
|
+
}
|
|
1897
|
+
function stripLocaleSuffixFromSlug(slug, localeCodes) {
|
|
1898
|
+
const matchedCode = findLocaleSuffixInSlug(slug, localeCodes);
|
|
1899
|
+
if (!matchedCode) {
|
|
1900
|
+
return { slug, stripped: false };
|
|
1901
|
+
}
|
|
1902
|
+
return {
|
|
1903
|
+
slug: slug.slice(0, -(matchedCode.length + 1)),
|
|
1904
|
+
stripped: true,
|
|
1905
|
+
matchedCode
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1883
1909
|
// src/validate/validate-slug-suffix.ts
|
|
1884
1910
|
function validateTranslationSlugSuffixes(config, db) {
|
|
1885
1911
|
const issues = [];
|
|
1886
1912
|
const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
|
|
1887
1913
|
for (const row of bulkLoadTranslations(db)) {
|
|
1888
1914
|
if (row.locale === config.defaultLocale) continue;
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
break;
|
|
1899
|
-
}
|
|
1915
|
+
const matchedCode = findLocaleSuffixInSlug(row.slug, localeCodes);
|
|
1916
|
+
if (matchedCode) {
|
|
1917
|
+
issues.push({
|
|
1918
|
+
contentTypeId: row.content_type,
|
|
1919
|
+
enSlug: row.en_slug,
|
|
1920
|
+
locale: row.locale,
|
|
1921
|
+
slug: row.slug,
|
|
1922
|
+
message: `Translation slug "${row.slug}" ends with locale code "-${matchedCode}"`
|
|
1923
|
+
});
|
|
1900
1924
|
}
|
|
1901
1925
|
}
|
|
1902
1926
|
return issues;
|
|
@@ -2044,7 +2068,7 @@ function validateProject(config) {
|
|
|
2044
2068
|
try {
|
|
2045
2069
|
for (const issue of validateTranslationSlugSuffixes(config, dbForSuffix)) {
|
|
2046
2070
|
issues.push({
|
|
2047
|
-
level: "
|
|
2071
|
+
level: "warning",
|
|
2048
2072
|
contentType: issue.contentTypeId,
|
|
2049
2073
|
enSlug: issue.enSlug,
|
|
2050
2074
|
locale: issue.locale,
|
|
@@ -2263,6 +2287,9 @@ function buildPageTranslationPrompt(input) {
|
|
|
2263
2287
|
...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
|
|
2264
2288
|
"## Rules",
|
|
2265
2289
|
...input.resolved.rules.map((rule) => `- ${rule}`),
|
|
2290
|
+
...input.slugStrategy === "localized" ? [
|
|
2291
|
+
`- The slug MUST be written in ${localeName}, derived from the ${localeName} title and its meaning \u2014 never the English slug. Transliterate non-Latin ${localeName} into ASCII Latin.`
|
|
2292
|
+
] : [],
|
|
2266
2293
|
"",
|
|
2267
2294
|
"## Output format",
|
|
2268
2295
|
"Return ONLY valid JSON with keys:",
|
|
@@ -2346,9 +2373,12 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
|
|
|
2346
2373
|
function slugStrategyRules(slugStrategy, preserveTerms) {
|
|
2347
2374
|
if (slugStrategy === "localized") {
|
|
2348
2375
|
const rules = [
|
|
2349
|
-
"Provide a
|
|
2376
|
+
"Provide a URL slug in JSON field `slug` that is Localized into the target language.",
|
|
2377
|
+
"Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
|
|
2350
2378
|
"Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
|
|
2351
|
-
"For non-Latin
|
|
2379
|
+
"For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
|
|
2380
|
+
"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.",
|
|
2381
|
+
'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 \\".'
|
|
2352
2382
|
];
|
|
2353
2383
|
if (preserveTerms?.length) {
|
|
2354
2384
|
rules.push(
|
|
@@ -2381,6 +2411,95 @@ function resolveTranslateConfig(config, type) {
|
|
|
2381
2411
|
return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
|
|
2382
2412
|
}
|
|
2383
2413
|
|
|
2414
|
+
// src/translate/sanitize-mdx-jsx.ts
|
|
2415
|
+
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
2416
|
+
let adjusted = false;
|
|
2417
|
+
let out = "";
|
|
2418
|
+
let i = 0;
|
|
2419
|
+
while (i < body.length) {
|
|
2420
|
+
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
2421
|
+
out += body[i];
|
|
2422
|
+
i += 1;
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
const tagStart = i;
|
|
2426
|
+
i += 1;
|
|
2427
|
+
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
2428
|
+
const tagName = body.slice(tagStart + 1, i);
|
|
2429
|
+
let tagOut = `<${tagName}`;
|
|
2430
|
+
let tagAdjusted = false;
|
|
2431
|
+
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
2432
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
2433
|
+
tagOut += body[i];
|
|
2434
|
+
i += 1;
|
|
2435
|
+
}
|
|
2436
|
+
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
2437
|
+
const attrStart = i;
|
|
2438
|
+
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
2439
|
+
body.slice(attrStart, i);
|
|
2440
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2441
|
+
if (body[i] !== "=") {
|
|
2442
|
+
tagOut += body.slice(attrStart, i);
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
tagOut += body.slice(attrStart, i);
|
|
2446
|
+
tagOut += "=";
|
|
2447
|
+
i += 1;
|
|
2448
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2449
|
+
const quote = body[i];
|
|
2450
|
+
if (quote !== '"' && quote !== "'") {
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (quote === "'") {
|
|
2454
|
+
const valStart2 = i + 1;
|
|
2455
|
+
i += 1;
|
|
2456
|
+
while (i < body.length) {
|
|
2457
|
+
if (body[i] === "\\") {
|
|
2458
|
+
i += 2;
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
if (body[i] === "'") break;
|
|
2462
|
+
i += 1;
|
|
2463
|
+
}
|
|
2464
|
+
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
2465
|
+
i += 1;
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
const valStart = i + 1;
|
|
2469
|
+
i += 1;
|
|
2470
|
+
let closeIdx = -1;
|
|
2471
|
+
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
2472
|
+
if (body[scan] !== '"') continue;
|
|
2473
|
+
let j = scan + 1;
|
|
2474
|
+
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
2475
|
+
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
2476
|
+
closeIdx = scan;
|
|
2477
|
+
break;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
if (closeIdx === -1) {
|
|
2481
|
+
tagOut += body.slice(valStart - 1, i);
|
|
2482
|
+
break;
|
|
2483
|
+
}
|
|
2484
|
+
const value = body.slice(valStart, closeIdx);
|
|
2485
|
+
const hasInternalQuote = value.includes('"');
|
|
2486
|
+
if (hasInternalQuote) {
|
|
2487
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2488
|
+
tagOut += `'${escaped}'`;
|
|
2489
|
+
tagAdjusted = true;
|
|
2490
|
+
} else {
|
|
2491
|
+
tagOut += `"${value}"`;
|
|
2492
|
+
}
|
|
2493
|
+
i = closeIdx + 1;
|
|
2494
|
+
}
|
|
2495
|
+
if (tagAdjusted) adjusted = true;
|
|
2496
|
+
tagOut += body[i] ?? "";
|
|
2497
|
+
out += tagOut;
|
|
2498
|
+
i += 1;
|
|
2499
|
+
}
|
|
2500
|
+
return { body: out, adjusted };
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2384
2503
|
// src/translate/validate-translation.ts
|
|
2385
2504
|
function formatZodIssues(error) {
|
|
2386
2505
|
return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
@@ -2506,11 +2625,17 @@ async function translatePage(config, item, options = {}) {
|
|
|
2506
2625
|
model,
|
|
2507
2626
|
responseSchema: responseSchema ?? void 0
|
|
2508
2627
|
});
|
|
2509
|
-
const
|
|
2628
|
+
const rawSlug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
|
|
2629
|
+
const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
|
|
2630
|
+
const { slug, stripped, matchedCode } = stripLocaleSuffixFromSlug(rawSlug, localeCodes);
|
|
2631
|
+
const slugAdjusted = stripped && matchedCode ? { from: rawSlug, to: slug, matchedCode } : void 0;
|
|
2510
2632
|
const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
|
|
2511
2633
|
if (!validated.ok) {
|
|
2512
2634
|
throw new Error(`Translation validation failed: ${validated.error}`);
|
|
2513
2635
|
}
|
|
2636
|
+
const { body: translatedBody, adjusted: mdxAdjusted } = sanitizeMdxJsxAttributeQuotes(
|
|
2637
|
+
result.parsed.body
|
|
2638
|
+
);
|
|
2514
2639
|
const writeDb = openStore(config, "readwrite");
|
|
2515
2640
|
const snapshotId = recordEnSnapshot(
|
|
2516
2641
|
config,
|
|
@@ -2529,7 +2654,7 @@ async function translatePage(config, item, options = {}) {
|
|
|
2529
2654
|
locale: item.locale,
|
|
2530
2655
|
slug,
|
|
2531
2656
|
frontmatter: validated.frontmatter,
|
|
2532
|
-
body:
|
|
2657
|
+
body: translatedBody,
|
|
2533
2658
|
enHash: currentEnHash,
|
|
2534
2659
|
translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2535
2660
|
model: result.model,
|
|
@@ -2547,7 +2672,9 @@ async function translatePage(config, item, options = {}) {
|
|
|
2547
2672
|
model: result.model,
|
|
2548
2673
|
usage: result.usage,
|
|
2549
2674
|
estimatedCostUsd,
|
|
2550
|
-
durationMs: Date.now() - startedAt
|
|
2675
|
+
durationMs: Date.now() - startedAt,
|
|
2676
|
+
slugAdjusted,
|
|
2677
|
+
mdxAdjusted: mdxAdjusted || void 0
|
|
2551
2678
|
};
|
|
2552
2679
|
} catch (error) {
|
|
2553
2680
|
return {
|
|
@@ -2698,6 +2825,7 @@ exports.getRedirectSourceSlugs = getRedirectSourceSlugs;
|
|
|
2698
2825
|
exports.getRelationTarget = getRelationTarget;
|
|
2699
2826
|
exports.getStaticExportRoots = getStaticExportRoots;
|
|
2700
2827
|
exports.isResolvedConfig = isResolvedConfig;
|
|
2828
|
+
exports.isRoutableType = isRoutableType;
|
|
2701
2829
|
exports.loadConfigSync = loadConfigSync;
|
|
2702
2830
|
exports.resolveConfig = resolveConfig;
|
|
2703
2831
|
exports.resolveLocalesFromPreset = resolveLocalesFromPreset;
|