scribe-cms 0.0.6 → 0.0.7

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.js CHANGED
@@ -1869,23 +1869,43 @@ function validateDocumentAssets(config, input) {
1869
1869
  return issues;
1870
1870
  }
1871
1871
 
1872
+ // src/core/localized-slug.ts
1873
+ function findLocaleSuffixInSlug(slug, localeCodes) {
1874
+ const sorted = [...localeCodes].sort((a, b) => b.length - a.length);
1875
+ for (const code of sorted) {
1876
+ if (slug.endsWith(`-${code}`)) {
1877
+ return code;
1878
+ }
1879
+ }
1880
+ return void 0;
1881
+ }
1882
+ function stripLocaleSuffixFromSlug(slug, localeCodes) {
1883
+ const matchedCode = findLocaleSuffixInSlug(slug, localeCodes);
1884
+ if (!matchedCode) {
1885
+ return { slug, stripped: false };
1886
+ }
1887
+ return {
1888
+ slug: slug.slice(0, -(matchedCode.length + 1)),
1889
+ stripped: true,
1890
+ matchedCode
1891
+ };
1892
+ }
1893
+
1872
1894
  // src/validate/validate-slug-suffix.ts
1873
1895
  function validateTranslationSlugSuffixes(config, db) {
1874
1896
  const issues = [];
1875
1897
  const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
1876
1898
  for (const row of bulkLoadTranslations(db)) {
1877
1899
  if (row.locale === config.defaultLocale) continue;
1878
- for (const code of localeCodes) {
1879
- if (row.slug.endsWith(`-${code}`)) {
1880
- issues.push({
1881
- contentTypeId: row.content_type,
1882
- enSlug: row.en_slug,
1883
- locale: row.locale,
1884
- slug: row.slug,
1885
- message: `Translation slug "${row.slug}" ends with locale code "-${code}"`
1886
- });
1887
- break;
1888
- }
1900
+ const matchedCode = findLocaleSuffixInSlug(row.slug, localeCodes);
1901
+ if (matchedCode) {
1902
+ issues.push({
1903
+ contentTypeId: row.content_type,
1904
+ enSlug: row.en_slug,
1905
+ locale: row.locale,
1906
+ slug: row.slug,
1907
+ message: `Translation slug "${row.slug}" ends with locale code "-${matchedCode}"`
1908
+ });
1889
1909
  }
1890
1910
  }
1891
1911
  return issues;
@@ -2033,7 +2053,7 @@ function validateProject(config) {
2033
2053
  try {
2034
2054
  for (const issue of validateTranslationSlugSuffixes(config, dbForSuffix)) {
2035
2055
  issues.push({
2036
- level: "error",
2056
+ level: "warning",
2037
2057
  contentType: issue.contentTypeId,
2038
2058
  enSlug: issue.enSlug,
2039
2059
  locale: issue.locale,
@@ -2252,6 +2272,9 @@ function buildPageTranslationPrompt(input) {
2252
2272
  ...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
2253
2273
  "## Rules",
2254
2274
  ...input.resolved.rules.map((rule) => `- ${rule}`),
2275
+ ...input.slugStrategy === "localized" ? [
2276
+ `- 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.`
2277
+ ] : [],
2255
2278
  "",
2256
2279
  "## Output format",
2257
2280
  "Return ONLY valid JSON with keys:",
@@ -2335,9 +2358,11 @@ function buildGeminiResponseSchema(schema, slugStrategy) {
2335
2358
  function slugStrategyRules(slugStrategy, preserveTerms) {
2336
2359
  if (slugStrategy === "localized") {
2337
2360
  const rules = [
2338
- "Provide a localized URL slug in JSON field `slug`.",
2361
+ "Provide a URL slug in JSON field `slug` that is TRANSLATED into the target language.",
2362
+ "Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
2339
2363
  "Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
2340
- "For non-Latin locales, transliterate into Latin script."
2364
+ "For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
2365
+ "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."
2341
2366
  ];
2342
2367
  if (preserveTerms?.length) {
2343
2368
  rules.push(
@@ -2495,7 +2520,10 @@ async function translatePage(config, item, options = {}) {
2495
2520
  model,
2496
2521
  responseSchema: responseSchema ?? void 0
2497
2522
  });
2498
- const slug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2523
+ const rawSlug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2524
+ const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
2525
+ const { slug, stripped, matchedCode } = stripLocaleSuffixFromSlug(rawSlug, localeCodes);
2526
+ const slugAdjusted = stripped && matchedCode ? { from: rawSlug, to: slug, matchedCode } : void 0;
2499
2527
  const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
2500
2528
  if (!validated.ok) {
2501
2529
  throw new Error(`Translation validation failed: ${validated.error}`);
@@ -2536,7 +2564,8 @@ async function translatePage(config, item, options = {}) {
2536
2564
  model: result.model,
2537
2565
  usage: result.usage,
2538
2566
  estimatedCostUsd,
2539
- durationMs: Date.now() - startedAt
2567
+ durationMs: Date.now() - startedAt,
2568
+ slugAdjusted
2540
2569
  };
2541
2570
  } catch (error) {
2542
2571
  return {