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
package/dist/index.js
CHANGED
|
@@ -1176,6 +1176,9 @@ function createProject(config) {
|
|
|
1176
1176
|
getType: getRuntime,
|
|
1177
1177
|
listTypes() {
|
|
1178
1178
|
return Array.from(runtimes.values());
|
|
1179
|
+
},
|
|
1180
|
+
listRoutableTypes() {
|
|
1181
|
+
return Array.from(runtimes.values()).filter((runtime) => isRoutableType(runtime.config));
|
|
1179
1182
|
}
|
|
1180
1183
|
};
|
|
1181
1184
|
}
|
|
@@ -1671,6 +1674,7 @@ function createScribe(input) {
|
|
|
1671
1674
|
project,
|
|
1672
1675
|
getType: project.getType,
|
|
1673
1676
|
listTypes: project.listTypes,
|
|
1677
|
+
listRoutableTypes: project.listRoutableTypes,
|
|
1674
1678
|
sitemap(options) {
|
|
1675
1679
|
return generateSitemap(project, options);
|
|
1676
1680
|
}
|
|
@@ -1869,23 +1873,43 @@ function validateDocumentAssets(config, input) {
|
|
|
1869
1873
|
return issues;
|
|
1870
1874
|
}
|
|
1871
1875
|
|
|
1876
|
+
// src/core/localized-slug.ts
|
|
1877
|
+
function findLocaleSuffixInSlug(slug, localeCodes) {
|
|
1878
|
+
const sorted = [...localeCodes].sort((a, b) => b.length - a.length);
|
|
1879
|
+
for (const code of sorted) {
|
|
1880
|
+
if (slug.endsWith(`-${code}`)) {
|
|
1881
|
+
return code;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return void 0;
|
|
1885
|
+
}
|
|
1886
|
+
function stripLocaleSuffixFromSlug(slug, localeCodes) {
|
|
1887
|
+
const matchedCode = findLocaleSuffixInSlug(slug, localeCodes);
|
|
1888
|
+
if (!matchedCode) {
|
|
1889
|
+
return { slug, stripped: false };
|
|
1890
|
+
}
|
|
1891
|
+
return {
|
|
1892
|
+
slug: slug.slice(0, -(matchedCode.length + 1)),
|
|
1893
|
+
stripped: true,
|
|
1894
|
+
matchedCode
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1872
1898
|
// src/validate/validate-slug-suffix.ts
|
|
1873
1899
|
function validateTranslationSlugSuffixes(config, db) {
|
|
1874
1900
|
const issues = [];
|
|
1875
1901
|
const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
|
|
1876
1902
|
for (const row of bulkLoadTranslations(db)) {
|
|
1877
1903
|
if (row.locale === config.defaultLocale) continue;
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
break;
|
|
1888
|
-
}
|
|
1904
|
+
const matchedCode = findLocaleSuffixInSlug(row.slug, localeCodes);
|
|
1905
|
+
if (matchedCode) {
|
|
1906
|
+
issues.push({
|
|
1907
|
+
contentTypeId: row.content_type,
|
|
1908
|
+
enSlug: row.en_slug,
|
|
1909
|
+
locale: row.locale,
|
|
1910
|
+
slug: row.slug,
|
|
1911
|
+
message: `Translation slug "${row.slug}" ends with locale code "-${matchedCode}"`
|
|
1912
|
+
});
|
|
1889
1913
|
}
|
|
1890
1914
|
}
|
|
1891
1915
|
return issues;
|
|
@@ -2033,7 +2057,7 @@ function validateProject(config) {
|
|
|
2033
2057
|
try {
|
|
2034
2058
|
for (const issue of validateTranslationSlugSuffixes(config, dbForSuffix)) {
|
|
2035
2059
|
issues.push({
|
|
2036
|
-
level: "
|
|
2060
|
+
level: "warning",
|
|
2037
2061
|
contentType: issue.contentTypeId,
|
|
2038
2062
|
enSlug: issue.enSlug,
|
|
2039
2063
|
locale: issue.locale,
|
|
@@ -2252,6 +2276,9 @@ function buildPageTranslationPrompt(input) {
|
|
|
2252
2276
|
...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
|
|
2253
2277
|
"## Rules",
|
|
2254
2278
|
...input.resolved.rules.map((rule) => `- ${rule}`),
|
|
2279
|
+
...input.slugStrategy === "localized" ? [
|
|
2280
|
+
`- 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.`
|
|
2281
|
+
] : [],
|
|
2255
2282
|
"",
|
|
2256
2283
|
"## Output format",
|
|
2257
2284
|
"Return ONLY valid JSON with keys:",
|
|
@@ -2335,9 +2362,12 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
|
|
|
2335
2362
|
function slugStrategyRules(slugStrategy, preserveTerms) {
|
|
2336
2363
|
if (slugStrategy === "localized") {
|
|
2337
2364
|
const rules = [
|
|
2338
|
-
"Provide a
|
|
2365
|
+
"Provide a URL slug in JSON field `slug` that is Localized into the target language.",
|
|
2366
|
+
"Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
|
|
2339
2367
|
"Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
|
|
2340
|
-
"For non-Latin
|
|
2368
|
+
"For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
|
|
2369
|
+
"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.",
|
|
2370
|
+
'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 \\".'
|
|
2341
2371
|
];
|
|
2342
2372
|
if (preserveTerms?.length) {
|
|
2343
2373
|
rules.push(
|
|
@@ -2370,6 +2400,95 @@ function resolveTranslateConfig(config, type) {
|
|
|
2370
2400
|
return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
|
|
2371
2401
|
}
|
|
2372
2402
|
|
|
2403
|
+
// src/translate/sanitize-mdx-jsx.ts
|
|
2404
|
+
function sanitizeMdxJsxAttributeQuotes(body) {
|
|
2405
|
+
let adjusted = false;
|
|
2406
|
+
let out = "";
|
|
2407
|
+
let i = 0;
|
|
2408
|
+
while (i < body.length) {
|
|
2409
|
+
if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
|
|
2410
|
+
out += body[i];
|
|
2411
|
+
i += 1;
|
|
2412
|
+
continue;
|
|
2413
|
+
}
|
|
2414
|
+
const tagStart = i;
|
|
2415
|
+
i += 1;
|
|
2416
|
+
while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
|
|
2417
|
+
const tagName = body.slice(tagStart + 1, i);
|
|
2418
|
+
let tagOut = `<${tagName}`;
|
|
2419
|
+
let tagAdjusted = false;
|
|
2420
|
+
while (i < body.length && body[i] !== ">" && body[i] !== "/") {
|
|
2421
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) {
|
|
2422
|
+
tagOut += body[i];
|
|
2423
|
+
i += 1;
|
|
2424
|
+
}
|
|
2425
|
+
if (i >= body.length || body[i] === ">" || body[i] === "/") break;
|
|
2426
|
+
const attrStart = i;
|
|
2427
|
+
while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
|
|
2428
|
+
body.slice(attrStart, i);
|
|
2429
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2430
|
+
if (body[i] !== "=") {
|
|
2431
|
+
tagOut += body.slice(attrStart, i);
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
tagOut += body.slice(attrStart, i);
|
|
2435
|
+
tagOut += "=";
|
|
2436
|
+
i += 1;
|
|
2437
|
+
while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
|
|
2438
|
+
const quote = body[i];
|
|
2439
|
+
if (quote !== '"' && quote !== "'") {
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
if (quote === "'") {
|
|
2443
|
+
const valStart2 = i + 1;
|
|
2444
|
+
i += 1;
|
|
2445
|
+
while (i < body.length) {
|
|
2446
|
+
if (body[i] === "\\") {
|
|
2447
|
+
i += 2;
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
if (body[i] === "'") break;
|
|
2451
|
+
i += 1;
|
|
2452
|
+
}
|
|
2453
|
+
tagOut += body.slice(valStart2 - 1, i + 1);
|
|
2454
|
+
i += 1;
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
const valStart = i + 1;
|
|
2458
|
+
i += 1;
|
|
2459
|
+
let closeIdx = -1;
|
|
2460
|
+
for (let scan = valStart; scan < body.length; scan += 1) {
|
|
2461
|
+
if (body[scan] !== '"') continue;
|
|
2462
|
+
let j = scan + 1;
|
|
2463
|
+
while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
|
|
2464
|
+
if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
|
|
2465
|
+
closeIdx = scan;
|
|
2466
|
+
break;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
if (closeIdx === -1) {
|
|
2470
|
+
tagOut += body.slice(valStart - 1, i);
|
|
2471
|
+
break;
|
|
2472
|
+
}
|
|
2473
|
+
const value = body.slice(valStart, closeIdx);
|
|
2474
|
+
const hasInternalQuote = value.includes('"');
|
|
2475
|
+
if (hasInternalQuote) {
|
|
2476
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2477
|
+
tagOut += `'${escaped}'`;
|
|
2478
|
+
tagAdjusted = true;
|
|
2479
|
+
} else {
|
|
2480
|
+
tagOut += `"${value}"`;
|
|
2481
|
+
}
|
|
2482
|
+
i = closeIdx + 1;
|
|
2483
|
+
}
|
|
2484
|
+
if (tagAdjusted) adjusted = true;
|
|
2485
|
+
tagOut += body[i] ?? "";
|
|
2486
|
+
out += tagOut;
|
|
2487
|
+
i += 1;
|
|
2488
|
+
}
|
|
2489
|
+
return { body: out, adjusted };
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2373
2492
|
// src/translate/validate-translation.ts
|
|
2374
2493
|
function formatZodIssues(error) {
|
|
2375
2494
|
return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
@@ -2495,11 +2614,17 @@ async function translatePage(config, item, options = {}) {
|
|
|
2495
2614
|
model,
|
|
2496
2615
|
responseSchema: responseSchema ?? void 0
|
|
2497
2616
|
});
|
|
2498
|
-
const
|
|
2617
|
+
const rawSlug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
|
|
2618
|
+
const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
|
|
2619
|
+
const { slug, stripped, matchedCode } = stripLocaleSuffixFromSlug(rawSlug, localeCodes);
|
|
2620
|
+
const slugAdjusted = stripped && matchedCode ? { from: rawSlug, to: slug, matchedCode } : void 0;
|
|
2499
2621
|
const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
|
|
2500
2622
|
if (!validated.ok) {
|
|
2501
2623
|
throw new Error(`Translation validation failed: ${validated.error}`);
|
|
2502
2624
|
}
|
|
2625
|
+
const { body: translatedBody, adjusted: mdxAdjusted } = sanitizeMdxJsxAttributeQuotes(
|
|
2626
|
+
result.parsed.body
|
|
2627
|
+
);
|
|
2503
2628
|
const writeDb = openStore(config, "readwrite");
|
|
2504
2629
|
const snapshotId = recordEnSnapshot(
|
|
2505
2630
|
config,
|
|
@@ -2518,7 +2643,7 @@ async function translatePage(config, item, options = {}) {
|
|
|
2518
2643
|
locale: item.locale,
|
|
2519
2644
|
slug,
|
|
2520
2645
|
frontmatter: validated.frontmatter,
|
|
2521
|
-
body:
|
|
2646
|
+
body: translatedBody,
|
|
2522
2647
|
enHash: currentEnHash,
|
|
2523
2648
|
translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2524
2649
|
model: result.model,
|
|
@@ -2536,7 +2661,9 @@ async function translatePage(config, item, options = {}) {
|
|
|
2536
2661
|
model: result.model,
|
|
2537
2662
|
usage: result.usage,
|
|
2538
2663
|
estimatedCostUsd,
|
|
2539
|
-
durationMs: Date.now() - startedAt
|
|
2664
|
+
durationMs: Date.now() - startedAt,
|
|
2665
|
+
slugAdjusted,
|
|
2666
|
+
mdxAdjusted: mdxAdjusted || void 0
|
|
2540
2667
|
};
|
|
2541
2668
|
} catch (error) {
|
|
2542
2669
|
return {
|
|
@@ -2671,6 +2798,6 @@ function writeStaticRawExports(project, options = {}) {
|
|
|
2671
2798
|
return { exports, written: exports.length };
|
|
2672
2799
|
}
|
|
2673
2800
|
|
|
2674
|
-
export { buildAllContentRedirects, buildStaticRawExports, buildWorklist, createProject, createScribe, defineConfig, defineContentType, exportDirSegment, field, findConfigPath, generateSitemap, getFieldKind, getRedirectSourceSlugs, getRelationTarget, getStaticExportRoots, isResolvedConfig, loadConfigSync, resolveConfig, resolveLocalesFromPreset, serializeMdx, translatePage, translateWorklist, unwrapSchema, validateProject, writeStaticRawExports };
|
|
2801
|
+
export { buildAllContentRedirects, buildStaticRawExports, buildWorklist, createProject, createScribe, defineConfig, defineContentType, exportDirSegment, field, findConfigPath, generateSitemap, getFieldKind, getRedirectSourceSlugs, getRelationTarget, getStaticExportRoots, isResolvedConfig, isRoutableType, loadConfigSync, resolveConfig, resolveLocalesFromPreset, serializeMdx, translatePage, translateWorklist, unwrapSchema, validateProject, writeStaticRawExports };
|
|
2675
2802
|
//# sourceMappingURL=index.js.map
|
|
2676
2803
|
//# sourceMappingURL=index.js.map
|