scribe-cms 0.0.7 → 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/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
  }
@@ -2369,11 +2373,12 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
2369
2373
  function slugStrategyRules(slugStrategy, preserveTerms) {
2370
2374
  if (slugStrategy === "localized") {
2371
2375
  const rules = [
2372
- "Provide a URL slug in JSON field `slug` that is TRANSLATED into the target language.",
2376
+ "Provide a URL slug in JSON field `slug` that is Localized into the target language.",
2373
2377
  "Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
2374
2378
  "Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
2375
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).",
2376
- "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."
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 \\".'
2377
2382
  ];
2378
2383
  if (preserveTerms?.length) {
2379
2384
  rules.push(
@@ -2406,6 +2411,95 @@ function resolveTranslateConfig(config, type) {
2406
2411
  return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
2407
2412
  }
2408
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
+
2409
2503
  // src/translate/validate-translation.ts
2410
2504
  function formatZodIssues(error) {
2411
2505
  return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
@@ -2539,6 +2633,9 @@ async function translatePage(config, item, options = {}) {
2539
2633
  if (!validated.ok) {
2540
2634
  throw new Error(`Translation validation failed: ${validated.error}`);
2541
2635
  }
2636
+ const { body: translatedBody, adjusted: mdxAdjusted } = sanitizeMdxJsxAttributeQuotes(
2637
+ result.parsed.body
2638
+ );
2542
2639
  const writeDb = openStore(config, "readwrite");
2543
2640
  const snapshotId = recordEnSnapshot(
2544
2641
  config,
@@ -2557,7 +2654,7 @@ async function translatePage(config, item, options = {}) {
2557
2654
  locale: item.locale,
2558
2655
  slug,
2559
2656
  frontmatter: validated.frontmatter,
2560
- body: result.parsed.body,
2657
+ body: translatedBody,
2561
2658
  enHash: currentEnHash,
2562
2659
  translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2563
2660
  model: result.model,
@@ -2576,7 +2673,8 @@ async function translatePage(config, item, options = {}) {
2576
2673
  usage: result.usage,
2577
2674
  estimatedCostUsd,
2578
2675
  durationMs: Date.now() - startedAt,
2579
- slugAdjusted
2676
+ slugAdjusted,
2677
+ mdxAdjusted: mdxAdjusted || void 0
2580
2678
  };
2581
2679
  } catch (error) {
2582
2680
  return {
@@ -2727,6 +2825,7 @@ exports.getRedirectSourceSlugs = getRedirectSourceSlugs;
2727
2825
  exports.getRelationTarget = getRelationTarget;
2728
2826
  exports.getStaticExportRoots = getStaticExportRoots;
2729
2827
  exports.isResolvedConfig = isResolvedConfig;
2828
+ exports.isRoutableType = isRoutableType;
2730
2829
  exports.loadConfigSync = loadConfigSync;
2731
2830
  exports.resolveConfig = resolveConfig;
2732
2831
  exports.resolveLocalesFromPreset = resolveLocalesFromPreset;