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/cli/index.cjs +71 -29
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +70 -28
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/translate-progress.d.ts.map +1 -1
- package/dist/index.cjs +45 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +45 -16
- package/dist/index.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/translate/page-translator.d.ts +5 -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/validate/validate-slug-suffix.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -11,11 +11,11 @@ import { GoogleGenAI } from '@google/genai';
|
|
|
11
11
|
import dotenv from 'dotenv';
|
|
12
12
|
import { serve } from '@hono/node-server';
|
|
13
13
|
import { Hono } from 'hono';
|
|
14
|
+
import { CancelPromptError } from '@inquirer/core';
|
|
14
15
|
import { select, checkbox } from '@inquirer/prompts';
|
|
15
16
|
|
|
16
17
|
var __defProp = Object.defineProperty;
|
|
17
18
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
18
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
19
19
|
var __esm = (fn, res) => function __init() {
|
|
20
20
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
21
21
|
};
|
|
@@ -23,7 +23,6 @@ var __export = (target, all) => {
|
|
|
23
23
|
for (var name in all)
|
|
24
24
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
25
25
|
};
|
|
26
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
27
26
|
var init_esm_shims = __esm({
|
|
28
27
|
"../../node_modules/tsup/assets/esm_shims.js"() {
|
|
29
28
|
}
|
|
@@ -1627,22 +1626,45 @@ function validateDocumentAssets(config, input) {
|
|
|
1627
1626
|
|
|
1628
1627
|
// src/validate/validate-slug-suffix.ts
|
|
1629
1628
|
init_esm_shims();
|
|
1629
|
+
|
|
1630
|
+
// src/core/localized-slug.ts
|
|
1631
|
+
init_esm_shims();
|
|
1632
|
+
function findLocaleSuffixInSlug(slug, localeCodes) {
|
|
1633
|
+
const sorted = [...localeCodes].sort((a, b) => b.length - a.length);
|
|
1634
|
+
for (const code of sorted) {
|
|
1635
|
+
if (slug.endsWith(`-${code}`)) {
|
|
1636
|
+
return code;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return void 0;
|
|
1640
|
+
}
|
|
1641
|
+
function stripLocaleSuffixFromSlug(slug, localeCodes) {
|
|
1642
|
+
const matchedCode = findLocaleSuffixInSlug(slug, localeCodes);
|
|
1643
|
+
if (!matchedCode) {
|
|
1644
|
+
return { slug, stripped: false };
|
|
1645
|
+
}
|
|
1646
|
+
return {
|
|
1647
|
+
slug: slug.slice(0, -(matchedCode.length + 1)),
|
|
1648
|
+
stripped: true,
|
|
1649
|
+
matchedCode
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// src/validate/validate-slug-suffix.ts
|
|
1630
1654
|
function validateTranslationSlugSuffixes(config, db) {
|
|
1631
1655
|
const issues = [];
|
|
1632
1656
|
const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
|
|
1633
1657
|
for (const row of bulkLoadTranslations(db)) {
|
|
1634
1658
|
if (row.locale === config.defaultLocale) continue;
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
break;
|
|
1645
|
-
}
|
|
1659
|
+
const matchedCode = findLocaleSuffixInSlug(row.slug, localeCodes);
|
|
1660
|
+
if (matchedCode) {
|
|
1661
|
+
issues.push({
|
|
1662
|
+
contentTypeId: row.content_type,
|
|
1663
|
+
enSlug: row.en_slug,
|
|
1664
|
+
locale: row.locale,
|
|
1665
|
+
slug: row.slug,
|
|
1666
|
+
message: `Translation slug "${row.slug}" ends with locale code "-${matchedCode}"`
|
|
1667
|
+
});
|
|
1646
1668
|
}
|
|
1647
1669
|
}
|
|
1648
1670
|
return issues;
|
|
@@ -1790,7 +1812,7 @@ function validateProject(config) {
|
|
|
1790
1812
|
try {
|
|
1791
1813
|
for (const issue of validateTranslationSlugSuffixes(config, dbForSuffix)) {
|
|
1792
1814
|
issues.push({
|
|
1793
|
-
level: "
|
|
1815
|
+
level: "warning",
|
|
1794
1816
|
contentType: issue.contentTypeId,
|
|
1795
1817
|
enSlug: issue.enSlug,
|
|
1796
1818
|
locale: issue.locale,
|
|
@@ -2041,6 +2063,9 @@ function buildPageTranslationPrompt(input) {
|
|
|
2041
2063
|
...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
|
|
2042
2064
|
"## Rules",
|
|
2043
2065
|
...input.resolved.rules.map((rule) => `- ${rule}`),
|
|
2066
|
+
...input.slugStrategy === "localized" ? [
|
|
2067
|
+
`- 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.`
|
|
2068
|
+
] : [],
|
|
2044
2069
|
"",
|
|
2045
2070
|
"## Output format",
|
|
2046
2071
|
"Return ONLY valid JSON with keys:",
|
|
@@ -2128,9 +2153,11 @@ init_esm_shims();
|
|
|
2128
2153
|
function slugStrategyRules(slugStrategy, preserveTerms) {
|
|
2129
2154
|
if (slugStrategy === "localized") {
|
|
2130
2155
|
const rules = [
|
|
2131
|
-
"Provide a
|
|
2156
|
+
"Provide a URL slug in JSON field `slug` that is TRANSLATED into the target language.",
|
|
2157
|
+
"Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
|
|
2132
2158
|
"Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
|
|
2133
|
-
"For non-Latin
|
|
2159
|
+
"For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
|
|
2160
|
+
"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."
|
|
2134
2161
|
];
|
|
2135
2162
|
if (preserveTerms?.length) {
|
|
2136
2163
|
rules.push(
|
|
@@ -2289,7 +2316,10 @@ async function translatePage(config, item, options = {}) {
|
|
|
2289
2316
|
model,
|
|
2290
2317
|
responseSchema: responseSchema ?? void 0
|
|
2291
2318
|
});
|
|
2292
|
-
const
|
|
2319
|
+
const rawSlug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
|
|
2320
|
+
const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
|
|
2321
|
+
const { slug, stripped, matchedCode } = stripLocaleSuffixFromSlug(rawSlug, localeCodes);
|
|
2322
|
+
const slugAdjusted = stripped && matchedCode ? { from: rawSlug, to: slug, matchedCode } : void 0;
|
|
2293
2323
|
const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
|
|
2294
2324
|
if (!validated.ok) {
|
|
2295
2325
|
throw new Error(`Translation validation failed: ${validated.error}`);
|
|
@@ -2330,7 +2360,8 @@ async function translatePage(config, item, options = {}) {
|
|
|
2330
2360
|
model: result.model,
|
|
2331
2361
|
usage: result.usage,
|
|
2332
2362
|
estimatedCostUsd,
|
|
2333
|
-
durationMs: Date.now() - startedAt
|
|
2363
|
+
durationMs: Date.now() - startedAt,
|
|
2364
|
+
slugAdjusted
|
|
2334
2365
|
};
|
|
2335
2366
|
} catch (error) {
|
|
2336
2367
|
return {
|
|
@@ -2956,16 +2987,6 @@ async function startStudio(project, options = {}) {
|
|
|
2956
2987
|
|
|
2957
2988
|
// cli/prompt-translate.ts
|
|
2958
2989
|
init_esm_shims();
|
|
2959
|
-
|
|
2960
|
-
// ../../node_modules/@inquirer/core/dist/lib/errors.js
|
|
2961
|
-
init_esm_shims();
|
|
2962
|
-
var CancelPromptError = class extends Error {
|
|
2963
|
-
constructor() {
|
|
2964
|
-
super(...arguments);
|
|
2965
|
-
__publicField(this, "name", "CancelPromptError");
|
|
2966
|
-
__publicField(this, "message", "Prompt was canceled");
|
|
2967
|
-
}
|
|
2968
|
-
};
|
|
2969
2990
|
function isInteractive() {
|
|
2970
2991
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
2971
2992
|
}
|
|
@@ -3077,8 +3098,17 @@ function statusForResult(result, dryRun) {
|
|
|
3077
3098
|
if (dryRun) return yellow("would translate");
|
|
3078
3099
|
return green("translated");
|
|
3079
3100
|
}
|
|
3101
|
+
function slugAdjustedMessage(result) {
|
|
3102
|
+
if (!result.slugAdjusted) return void 0;
|
|
3103
|
+
const { from, to, matchedCode } = result.slugAdjusted;
|
|
3104
|
+
return yellow(
|
|
3105
|
+
`slug adjusted: "${from}" \u2192 "${to}" (stripped -${matchedCode})`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3080
3108
|
function detailForResult(result) {
|
|
3081
3109
|
const parts = [];
|
|
3110
|
+
const slugMsg = slugAdjustedMessage(result);
|
|
3111
|
+
if (slugMsg) parts.push(slugMsg);
|
|
3082
3112
|
if (result.durationMs !== void 0) parts.push(`${(result.durationMs / 1e3).toFixed(1)}s`);
|
|
3083
3113
|
if (result.usage) {
|
|
3084
3114
|
parts.push(`${formatTokenCount(result.usage.inputTokens)} in`);
|
|
@@ -3112,6 +3142,10 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
3112
3142
|
const status = statusForResult(event.result, dryRun);
|
|
3113
3143
|
const detail = detailForResult(event.result);
|
|
3114
3144
|
console.log(`${label}: ${status}${detail ? ` (${detail.replace(/\x1b\[[0-9;]*m/g, "")})` : ""}`);
|
|
3145
|
+
const slugMsg = slugAdjustedMessage(event.result);
|
|
3146
|
+
if (slugMsg) {
|
|
3147
|
+
console.log(`[warning] ${labelForResult(event.result)} ${slugMsg.replace(/\x1b\[[0-9;]*m/g, "")}`);
|
|
3148
|
+
}
|
|
3115
3149
|
if (event.result.failed && event.result.error) {
|
|
3116
3150
|
console.error(event.result.error);
|
|
3117
3151
|
}
|
|
@@ -3215,6 +3249,14 @@ function createTranslateProgressReporter(options = {}) {
|
|
|
3215
3249
|
for (const result of event.results.filter((entry) => entry.failed)) {
|
|
3216
3250
|
process.stdout.write("\x1B[2K" + red(`${labelForResult(result)}: ${result.error ?? "failed"}`) + "\n");
|
|
3217
3251
|
}
|
|
3252
|
+
for (const result of event.results.filter((entry) => entry.slugAdjusted)) {
|
|
3253
|
+
const slugMsg = slugAdjustedMessage(result);
|
|
3254
|
+
if (slugMsg) {
|
|
3255
|
+
process.stdout.write(
|
|
3256
|
+
"\x1B[2K" + yellow(`[warning] ${labelForResult(result)} ${slugMsg.replace(/\x1b\[[0-9;]*m/g, "")}`) + "\n"
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3218
3260
|
break;
|
|
3219
3261
|
}
|
|
3220
3262
|
},
|