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/cli/index.cjs
CHANGED
|
@@ -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
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
},
|