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.
@@ -12,6 +12,7 @@ var genai = require('@google/genai');
12
12
  var dotenv = require('dotenv');
13
13
  var nodeServer = require('@hono/node-server');
14
14
  var hono = require('hono');
15
+ var core = require('@inquirer/core');
15
16
  var prompts = require('@inquirer/prompts');
16
17
  var url = require('url');
17
18
 
@@ -25,7 +26,6 @@ var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
25
26
 
26
27
  var __defProp = Object.defineProperty;
27
28
  var __getOwnPropNames = Object.getOwnPropertyNames;
28
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
29
29
  var __esm = (fn, res) => function __init() {
30
30
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
31
31
  };
@@ -33,7 +33,6 @@ var __export = (target, all) => {
33
33
  for (var name in all)
34
34
  __defProp(target, name, { get: all[name], enumerable: true });
35
35
  };
36
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
37
36
 
38
37
  // ../../node_modules/tsup/assets/cjs_shims.js
39
38
  var getImportMetaUrl, importMetaUrl;
@@ -1224,6 +1223,9 @@ function createProject(config) {
1224
1223
  getType: getRuntime,
1225
1224
  listTypes() {
1226
1225
  return Array.from(runtimes.values());
1226
+ },
1227
+ listRoutableTypes() {
1228
+ return Array.from(runtimes.values()).filter((runtime) => isRoutableType(runtime.config));
1227
1229
  }
1228
1230
  };
1229
1231
  }
@@ -1642,22 +1644,45 @@ function validateDocumentAssets(config, input) {
1642
1644
 
1643
1645
  // src/validate/validate-slug-suffix.ts
1644
1646
  init_cjs_shims();
1647
+
1648
+ // src/core/localized-slug.ts
1649
+ init_cjs_shims();
1650
+ function findLocaleSuffixInSlug(slug, localeCodes) {
1651
+ const sorted = [...localeCodes].sort((a, b) => b.length - a.length);
1652
+ for (const code of sorted) {
1653
+ if (slug.endsWith(`-${code}`)) {
1654
+ return code;
1655
+ }
1656
+ }
1657
+ return void 0;
1658
+ }
1659
+ function stripLocaleSuffixFromSlug(slug, localeCodes) {
1660
+ const matchedCode = findLocaleSuffixInSlug(slug, localeCodes);
1661
+ if (!matchedCode) {
1662
+ return { slug, stripped: false };
1663
+ }
1664
+ return {
1665
+ slug: slug.slice(0, -(matchedCode.length + 1)),
1666
+ stripped: true,
1667
+ matchedCode
1668
+ };
1669
+ }
1670
+
1671
+ // src/validate/validate-slug-suffix.ts
1645
1672
  function validateTranslationSlugSuffixes(config, db) {
1646
1673
  const issues = [];
1647
1674
  const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
1648
1675
  for (const row of bulkLoadTranslations(db)) {
1649
1676
  if (row.locale === config.defaultLocale) continue;
1650
- for (const code of localeCodes) {
1651
- if (row.slug.endsWith(`-${code}`)) {
1652
- issues.push({
1653
- contentTypeId: row.content_type,
1654
- enSlug: row.en_slug,
1655
- locale: row.locale,
1656
- slug: row.slug,
1657
- message: `Translation slug "${row.slug}" ends with locale code "-${code}"`
1658
- });
1659
- break;
1660
- }
1677
+ const matchedCode = findLocaleSuffixInSlug(row.slug, localeCodes);
1678
+ if (matchedCode) {
1679
+ issues.push({
1680
+ contentTypeId: row.content_type,
1681
+ enSlug: row.en_slug,
1682
+ locale: row.locale,
1683
+ slug: row.slug,
1684
+ message: `Translation slug "${row.slug}" ends with locale code "-${matchedCode}"`
1685
+ });
1661
1686
  }
1662
1687
  }
1663
1688
  return issues;
@@ -1805,7 +1830,7 @@ function validateProject(config) {
1805
1830
  try {
1806
1831
  for (const issue of validateTranslationSlugSuffixes(config, dbForSuffix)) {
1807
1832
  issues.push({
1808
- level: "error",
1833
+ level: "warning",
1809
1834
  contentType: issue.contentTypeId,
1810
1835
  enSlug: issue.enSlug,
1811
1836
  locale: issue.locale,
@@ -2056,6 +2081,9 @@ function buildPageTranslationPrompt(input) {
2056
2081
  ...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
2057
2082
  "## Rules",
2058
2083
  ...input.resolved.rules.map((rule) => `- ${rule}`),
2084
+ ...input.slugStrategy === "localized" ? [
2085
+ `- 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.`
2086
+ ] : [],
2059
2087
  "",
2060
2088
  "## Output format",
2061
2089
  "Return ONLY valid JSON with keys:",
@@ -2143,9 +2171,12 @@ init_cjs_shims();
2143
2171
  function slugStrategyRules(slugStrategy, preserveTerms) {
2144
2172
  if (slugStrategy === "localized") {
2145
2173
  const rules = [
2146
- "Provide a localized URL slug in JSON field `slug`.",
2174
+ "Provide a URL slug in JSON field `slug` that is Localized into the target language.",
2175
+ "Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
2147
2176
  "Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
2148
- "For non-Latin locales, transliterate into Latin script."
2177
+ "For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
2178
+ "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.",
2179
+ '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 \\".'
2149
2180
  ];
2150
2181
  if (preserveTerms?.length) {
2151
2182
  rules.push(
@@ -2178,6 +2209,96 @@ function resolveTranslateConfig(config, type) {
2178
2209
  return mergeTranslateConfig(config.translate, type.translate, type.slugStrategy);
2179
2210
  }
2180
2211
 
2212
+ // src/translate/sanitize-mdx-jsx.ts
2213
+ init_cjs_shims();
2214
+ function sanitizeMdxJsxAttributeQuotes(body) {
2215
+ let adjusted = false;
2216
+ let out = "";
2217
+ let i = 0;
2218
+ while (i < body.length) {
2219
+ if (body[i] !== "<" || !/[\w.]/.test(body[i + 1] ?? "")) {
2220
+ out += body[i];
2221
+ i += 1;
2222
+ continue;
2223
+ }
2224
+ const tagStart = i;
2225
+ i += 1;
2226
+ while (i < body.length && /[\w.-]/.test(body[i] ?? "")) i += 1;
2227
+ const tagName = body.slice(tagStart + 1, i);
2228
+ let tagOut = `<${tagName}`;
2229
+ let tagAdjusted = false;
2230
+ while (i < body.length && body[i] !== ">" && body[i] !== "/") {
2231
+ while (i < body.length && /\s/.test(body[i] ?? "")) {
2232
+ tagOut += body[i];
2233
+ i += 1;
2234
+ }
2235
+ if (i >= body.length || body[i] === ">" || body[i] === "/") break;
2236
+ const attrStart = i;
2237
+ while (i < body.length && /[\w:.-]/.test(body[i] ?? "")) i += 1;
2238
+ body.slice(attrStart, i);
2239
+ while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
2240
+ if (body[i] !== "=") {
2241
+ tagOut += body.slice(attrStart, i);
2242
+ continue;
2243
+ }
2244
+ tagOut += body.slice(attrStart, i);
2245
+ tagOut += "=";
2246
+ i += 1;
2247
+ while (i < body.length && /\s/.test(body[i] ?? "")) i += 1;
2248
+ const quote = body[i];
2249
+ if (quote !== '"' && quote !== "'") {
2250
+ continue;
2251
+ }
2252
+ if (quote === "'") {
2253
+ const valStart2 = i + 1;
2254
+ i += 1;
2255
+ while (i < body.length) {
2256
+ if (body[i] === "\\") {
2257
+ i += 2;
2258
+ continue;
2259
+ }
2260
+ if (body[i] === "'") break;
2261
+ i += 1;
2262
+ }
2263
+ tagOut += body.slice(valStart2 - 1, i + 1);
2264
+ i += 1;
2265
+ continue;
2266
+ }
2267
+ const valStart = i + 1;
2268
+ i += 1;
2269
+ let closeIdx = -1;
2270
+ for (let scan = valStart; scan < body.length; scan += 1) {
2271
+ if (body[scan] !== '"') continue;
2272
+ let j = scan + 1;
2273
+ while (j < body.length && /\s/.test(body[j] ?? "")) j += 1;
2274
+ if (j < body.length && (body[j] === ">" || body[j] === "/" || /[a-zA-Z]/.test(body[j] ?? ""))) {
2275
+ closeIdx = scan;
2276
+ break;
2277
+ }
2278
+ }
2279
+ if (closeIdx === -1) {
2280
+ tagOut += body.slice(valStart - 1, i);
2281
+ break;
2282
+ }
2283
+ const value = body.slice(valStart, closeIdx);
2284
+ const hasInternalQuote = value.includes('"');
2285
+ if (hasInternalQuote) {
2286
+ const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2287
+ tagOut += `'${escaped}'`;
2288
+ tagAdjusted = true;
2289
+ } else {
2290
+ tagOut += `"${value}"`;
2291
+ }
2292
+ i = closeIdx + 1;
2293
+ }
2294
+ if (tagAdjusted) adjusted = true;
2295
+ tagOut += body[i] ?? "";
2296
+ out += tagOut;
2297
+ i += 1;
2298
+ }
2299
+ return { body: out, adjusted };
2300
+ }
2301
+
2181
2302
  // src/translate/validate-translation.ts
2182
2303
  init_cjs_shims();
2183
2304
  function formatZodIssues(error) {
@@ -2304,11 +2425,17 @@ async function translatePage(config, item, options = {}) {
2304
2425
  model,
2305
2426
  responseSchema: responseSchema ?? void 0
2306
2427
  });
2307
- const slug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2428
+ const rawSlug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2429
+ const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
2430
+ const { slug, stripped, matchedCode } = stripLocaleSuffixFromSlug(rawSlug, localeCodes);
2431
+ const slugAdjusted = stripped && matchedCode ? { from: rawSlug, to: slug, matchedCode } : void 0;
2308
2432
  const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
2309
2433
  if (!validated.ok) {
2310
2434
  throw new Error(`Translation validation failed: ${validated.error}`);
2311
2435
  }
2436
+ const { body: translatedBody, adjusted: mdxAdjusted } = sanitizeMdxJsxAttributeQuotes(
2437
+ result.parsed.body
2438
+ );
2312
2439
  const writeDb = openStore(config, "readwrite");
2313
2440
  const snapshotId = recordEnSnapshot(
2314
2441
  config,
@@ -2327,7 +2454,7 @@ async function translatePage(config, item, options = {}) {
2327
2454
  locale: item.locale,
2328
2455
  slug,
2329
2456
  frontmatter: validated.frontmatter,
2330
- body: result.parsed.body,
2457
+ body: translatedBody,
2331
2458
  enHash: currentEnHash,
2332
2459
  translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2333
2460
  model: result.model,
@@ -2345,7 +2472,9 @@ async function translatePage(config, item, options = {}) {
2345
2472
  model: result.model,
2346
2473
  usage: result.usage,
2347
2474
  estimatedCostUsd,
2348
- durationMs: Date.now() - startedAt
2475
+ durationMs: Date.now() - startedAt,
2476
+ slugAdjusted,
2477
+ mdxAdjusted: mdxAdjusted || void 0
2349
2478
  };
2350
2479
  } catch (error) {
2351
2480
  return {
@@ -2971,16 +3100,6 @@ async function startStudio(project, options = {}) {
2971
3100
 
2972
3101
  // cli/prompt-translate.ts
2973
3102
  init_cjs_shims();
2974
-
2975
- // ../../node_modules/@inquirer/core/dist/lib/errors.js
2976
- init_cjs_shims();
2977
- var CancelPromptError = class extends Error {
2978
- constructor() {
2979
- super(...arguments);
2980
- __publicField(this, "name", "CancelPromptError");
2981
- __publicField(this, "message", "Prompt was canceled");
2982
- }
2983
- };
2984
3103
  function isInteractive() {
2985
3104
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
2986
3105
  }
@@ -2991,7 +3110,7 @@ async function runPrompt(prompt) {
2991
3110
  try {
2992
3111
  return await prompt;
2993
3112
  } catch (error) {
2994
- if (error instanceof CancelPromptError) process.exit(0);
3113
+ if (error instanceof core.CancelPromptError) process.exit(0);
2995
3114
  throw error;
2996
3115
  }
2997
3116
  }
@@ -3092,8 +3211,17 @@ function statusForResult(result, dryRun) {
3092
3211
  if (dryRun) return yellow("would translate");
3093
3212
  return green("translated");
3094
3213
  }
3214
+ function slugAdjustedMessage(result) {
3215
+ if (!result.slugAdjusted) return void 0;
3216
+ const { from, to, matchedCode } = result.slugAdjusted;
3217
+ return yellow(
3218
+ `slug adjusted: "${from}" \u2192 "${to}" (stripped -${matchedCode})`
3219
+ );
3220
+ }
3095
3221
  function detailForResult(result) {
3096
3222
  const parts = [];
3223
+ const slugMsg = slugAdjustedMessage(result);
3224
+ if (slugMsg) parts.push(slugMsg);
3097
3225
  if (result.durationMs !== void 0) parts.push(`${(result.durationMs / 1e3).toFixed(1)}s`);
3098
3226
  if (result.usage) {
3099
3227
  parts.push(`${formatTokenCount(result.usage.inputTokens)} in`);
@@ -3127,6 +3255,10 @@ function createTranslateProgressReporter(options = {}) {
3127
3255
  const status = statusForResult(event.result, dryRun);
3128
3256
  const detail = detailForResult(event.result);
3129
3257
  console.log(`${label}: ${status}${detail ? ` (${detail.replace(/\x1b\[[0-9;]*m/g, "")})` : ""}`);
3258
+ const slugMsg = slugAdjustedMessage(event.result);
3259
+ if (slugMsg) {
3260
+ console.log(`[warning] ${labelForResult(event.result)} ${slugMsg.replace(/\x1b\[[0-9;]*m/g, "")}`);
3261
+ }
3130
3262
  if (event.result.failed && event.result.error) {
3131
3263
  console.error(event.result.error);
3132
3264
  }
@@ -3230,6 +3362,14 @@ function createTranslateProgressReporter(options = {}) {
3230
3362
  for (const result of event.results.filter((entry) => entry.failed)) {
3231
3363
  process.stdout.write("\x1B[2K" + red(`${labelForResult(result)}: ${result.error ?? "failed"}`) + "\n");
3232
3364
  }
3365
+ for (const result of event.results.filter((entry) => entry.slugAdjusted)) {
3366
+ const slugMsg = slugAdjustedMessage(result);
3367
+ if (slugMsg) {
3368
+ process.stdout.write(
3369
+ "\x1B[2K" + yellow(`[warning] ${labelForResult(result)} ${slugMsg.replace(/\x1b\[[0-9;]*m/g, "")}`) + "\n"
3370
+ );
3371
+ }
3372
+ }
3233
3373
  break;
3234
3374
  }
3235
3375
  },