scribe-cms 0.0.15 → 0.0.17
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 +156 -6
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +156 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +22 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/src/storage/translations.d.ts +8 -0
- package/dist/src/storage/translations.d.ts.map +1 -1
- package/dist/src/translate/prompts/translation-prompt.d.ts +2 -0
- package/dist/src/translate/prompts/translation-prompt.d.ts.map +1 -1
- package/dist/src/translate/worklist.d.ts +4 -1
- package/dist/src/translate/worklist.d.ts.map +1 -1
- package/dist/studio/server.cjs +141 -3
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.d.ts.map +1 -1
- package/dist/studio/server.js +141 -3
- package/dist/studio/server.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -756,6 +756,15 @@ function listTranslationsForLocale(db, contentType, locale) {
|
|
|
756
756
|
function bulkLoadTranslations(db) {
|
|
757
757
|
return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
|
|
758
758
|
}
|
|
759
|
+
function countTranslations(db) {
|
|
760
|
+
const row = db.prepare(`SELECT COUNT(*) as count FROM translations`).get();
|
|
761
|
+
return row.count;
|
|
762
|
+
}
|
|
763
|
+
function latestTranslationAtByLocale(db) {
|
|
764
|
+
return db.prepare(
|
|
765
|
+
`SELECT locale, MAX(translated_at) as latest FROM translations GROUP BY locale`
|
|
766
|
+
).all();
|
|
767
|
+
}
|
|
759
768
|
|
|
760
769
|
// src/translate/validate-mdx-body.ts
|
|
761
770
|
init_esm_shims();
|
|
@@ -2114,6 +2123,11 @@ function computePageEnHash(translatableFrontmatter, body) {
|
|
|
2114
2123
|
|
|
2115
2124
|
// src/translate/worklist.ts
|
|
2116
2125
|
init_sqlite();
|
|
2126
|
+
function parseContentTypeFilter(contentType) {
|
|
2127
|
+
if (!contentType) return void 0;
|
|
2128
|
+
const ids = contentType.split(",").map((id) => id.trim()).filter(Boolean);
|
|
2129
|
+
return ids.length > 0 ? new Set(ids) : void 0;
|
|
2130
|
+
}
|
|
2117
2131
|
function listEnSlugs4(rootDir, contentDir) {
|
|
2118
2132
|
const dir = path4.join(rootDir, contentDir);
|
|
2119
2133
|
if (!fs2.existsSync(dir)) return [];
|
|
@@ -2123,8 +2137,9 @@ function buildWorklist(config, options = {}) {
|
|
|
2123
2137
|
const db = openStore(config, "readonly");
|
|
2124
2138
|
const items = [];
|
|
2125
2139
|
const locales = options.locales ?? config.locales.filter((locale) => locale !== config.defaultLocale);
|
|
2140
|
+
const contentTypes = parseContentTypeFilter(options.contentType);
|
|
2126
2141
|
for (const type of config.types) {
|
|
2127
|
-
if (
|
|
2142
|
+
if (contentTypes && !contentTypes.has(type.id)) continue;
|
|
2128
2143
|
const enSlugs = options.enSlug ? [options.enSlug] : listEnSlugs4(config.rootDir, type.contentDir);
|
|
2129
2144
|
for (const enSlug of enSlugs) {
|
|
2130
2145
|
const enDoc = readEnDocument(config, type, enSlug);
|
|
@@ -2144,12 +2159,13 @@ function buildWorklist(config, options = {}) {
|
|
|
2144
2159
|
});
|
|
2145
2160
|
continue;
|
|
2146
2161
|
}
|
|
2147
|
-
|
|
2162
|
+
const stale = existing.en_hash !== currentEnHash;
|
|
2163
|
+
if (stale || options.force) {
|
|
2148
2164
|
items.push({
|
|
2149
2165
|
contentType: type.id,
|
|
2150
2166
|
enSlug,
|
|
2151
2167
|
locale,
|
|
2152
|
-
reason: "stale",
|
|
2168
|
+
reason: stale ? "stale" : "forced",
|
|
2153
2169
|
currentEnHash,
|
|
2154
2170
|
storedEnHash: existing.en_hash
|
|
2155
2171
|
});
|
|
@@ -2530,6 +2546,13 @@ function defaultLocalizationPrompt(localeName, locale) {
|
|
|
2530
2546
|
"Write as if a native speaker authored it for the target market."
|
|
2531
2547
|
].join(" ");
|
|
2532
2548
|
}
|
|
2549
|
+
function buildLocalizationPrompt(promptOverride, localeName, locale) {
|
|
2550
|
+
const localeDirective = defaultLocalizationPrompt(localeName, locale);
|
|
2551
|
+
if (!promptOverride) return localeDirective;
|
|
2552
|
+
return `${promptOverride}
|
|
2553
|
+
|
|
2554
|
+
${localeDirective}`;
|
|
2555
|
+
}
|
|
2533
2556
|
var TASK_FRAMING = "You are localizing the content below into a target language specified at the end of this prompt.";
|
|
2534
2557
|
function buildOutputFormatLine(hasFrontmatter, slugStrategy) {
|
|
2535
2558
|
if (!hasFrontmatter) {
|
|
@@ -2546,7 +2569,11 @@ function buildRetryContextSection(previousError) {
|
|
|
2546
2569
|
}
|
|
2547
2570
|
function buildPageTranslationPrompt(input) {
|
|
2548
2571
|
const localeName = LOCALE_NAMES[input.targetLocale] ?? input.targetLocale;
|
|
2549
|
-
const localizationPrompt =
|
|
2572
|
+
const localizationPrompt = buildLocalizationPrompt(
|
|
2573
|
+
input.resolved.promptOverride,
|
|
2574
|
+
localeName,
|
|
2575
|
+
input.targetLocale
|
|
2576
|
+
);
|
|
2550
2577
|
const prefix = [
|
|
2551
2578
|
TASK_FRAMING,
|
|
2552
2579
|
"",
|
|
@@ -3917,12 +3944,21 @@ function renderLayout(title, body, project, options = {}) {
|
|
|
3917
3944
|
.tag { font-size: var(--fs-sm); color: var(--dim); margin-left: 6px; }
|
|
3918
3945
|
.tag-warn { color: var(--stale); }
|
|
3919
3946
|
.tag-err { color: var(--missing); }
|
|
3947
|
+
|
|
3948
|
+
/* dashboard */
|
|
3949
|
+
.cards { display: flex; flex-wrap: wrap; gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }
|
|
3950
|
+
.card { flex: 1 1 120px; background: var(--bg); padding: 12px; min-width: 120px; }
|
|
3951
|
+
.card-value { font-size: 22px; font-weight: 300; line-height: 1.2; }
|
|
3952
|
+
.card-label { font-size: var(--fs-sm); color: var(--dim); margin-top: 2px; }
|
|
3953
|
+
.bar { display: inline-block; width: 120px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; vertical-align: middle; margin-right: 8px; }
|
|
3954
|
+
.bar-fill { display: block; height: 100%; }
|
|
3920
3955
|
</style>
|
|
3921
3956
|
</head>
|
|
3922
3957
|
<body>
|
|
3923
3958
|
<div class="app">
|
|
3924
3959
|
<nav class="actbar">
|
|
3925
3960
|
<a href="/" title="Overview">\u2302</a>
|
|
3961
|
+
<a href="/dashboard" title="Dashboard">\u25A6</a>
|
|
3926
3962
|
<a href="/staleness" title="Staleness">\u26A0</a>
|
|
3927
3963
|
</nav>
|
|
3928
3964
|
<aside class="sidebar">
|
|
@@ -3979,6 +4015,119 @@ async function startStudio(project, options = {}) {
|
|
|
3979
4015
|
</table>`;
|
|
3980
4016
|
return c.html(renderLayout("Overview", html, project));
|
|
3981
4017
|
});
|
|
4018
|
+
app.get("/dashboard", (c) => {
|
|
4019
|
+
const db = openStore(config, "readonly");
|
|
4020
|
+
const types = project.listTypes();
|
|
4021
|
+
const targetLocales = config.locales.filter((l) => l !== config.defaultLocale);
|
|
4022
|
+
const docCountByType = /* @__PURE__ */ new Map();
|
|
4023
|
+
let totalEnDocs = 0;
|
|
4024
|
+
for (const type of types) {
|
|
4025
|
+
const n = type.list().length;
|
|
4026
|
+
docCountByType.set(type.id, n);
|
|
4027
|
+
totalEnDocs += n;
|
|
4028
|
+
}
|
|
4029
|
+
const worklist = buildWorklist(config);
|
|
4030
|
+
const missingByLocale = /* @__PURE__ */ new Map();
|
|
4031
|
+
const staleByLocale = /* @__PURE__ */ new Map();
|
|
4032
|
+
const missingByType = /* @__PURE__ */ new Map();
|
|
4033
|
+
const staleByType = /* @__PURE__ */ new Map();
|
|
4034
|
+
let missingTotal = 0;
|
|
4035
|
+
let staleTotal = 0;
|
|
4036
|
+
for (const item of worklist) {
|
|
4037
|
+
if (item.reason === "missing") {
|
|
4038
|
+
missingByLocale.set(item.locale, (missingByLocale.get(item.locale) ?? 0) + 1);
|
|
4039
|
+
missingByType.set(item.contentType, (missingByType.get(item.contentType) ?? 0) + 1);
|
|
4040
|
+
missingTotal++;
|
|
4041
|
+
} else if (item.reason === "stale") {
|
|
4042
|
+
staleByLocale.set(item.locale, (staleByLocale.get(item.locale) ?? 0) + 1);
|
|
4043
|
+
staleByType.set(item.contentType, (staleByType.get(item.contentType) ?? 0) + 1);
|
|
4044
|
+
staleTotal++;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
const storedTranslations = countTranslations(db);
|
|
4048
|
+
const latestByLocale = /* @__PURE__ */ new Map();
|
|
4049
|
+
for (const row of latestTranslationAtByLocale(db)) {
|
|
4050
|
+
if (row.latest) latestByLocale.set(row.locale, row.latest);
|
|
4051
|
+
}
|
|
4052
|
+
db.close();
|
|
4053
|
+
const expectedTotal = totalEnDocs * targetLocales.length;
|
|
4054
|
+
const coverageTotal = expectedTotal > 0 ? (expectedTotal - missingTotal) / expectedTotal : 0;
|
|
4055
|
+
const coveragePct = expectedTotal > 0 ? Math.round(coverageTotal * 100) : 0;
|
|
4056
|
+
const pct = (fraction) => Math.round(fraction * 100);
|
|
4057
|
+
const num = (n, warnClass) => n > 0 ? `<span class="${warnClass}">${n}</span>` : `<span class="dim">0</span>`;
|
|
4058
|
+
const coverageColor = expectedTotal > 0 && coveragePct === 100 ? ` style="color:var(--ok)"` : "";
|
|
4059
|
+
const coverageValue = expectedTotal > 0 ? `${coveragePct}%` : `<span class="dim">\u2014</span>`;
|
|
4060
|
+
const staleColor = staleTotal > 0 ? ` style="color:var(--stale)"` : "";
|
|
4061
|
+
const missingColor = missingTotal > 0 ? ` style="color:var(--missing)"` : "";
|
|
4062
|
+
const cards = `<div class="cards">
|
|
4063
|
+
<div class="card"><div class="card-value">${totalEnDocs}</div><div class="card-label">Documents (EN)</div></div>
|
|
4064
|
+
<div class="card"><div class="card-value">${targetLocales.length}</div><div class="card-label">Target locales</div></div>
|
|
4065
|
+
<div class="card"><div class="card-value">${storedTranslations}</div><div class="card-label">Stored translations</div></div>
|
|
4066
|
+
<div class="card"><div class="card-value"${coverageColor}>${coverageValue}</div><div class="card-label">Coverage</div></div>
|
|
4067
|
+
<div class="card"><div class="card-value"${staleColor}>${staleTotal}</div><div class="card-label">Stale</div></div>
|
|
4068
|
+
<div class="card"><div class="card-value"${missingColor}>${missingTotal}</div><div class="card-label">Missing</div></div>
|
|
4069
|
+
</div>`;
|
|
4070
|
+
let localeRows;
|
|
4071
|
+
if (totalEnDocs === 0 || targetLocales.length === 0) {
|
|
4072
|
+
localeRows = `<tr><td colspan="7" class="dim">No target locales or documents</td></tr>`;
|
|
4073
|
+
} else {
|
|
4074
|
+
localeRows = targetLocales.map((locale) => {
|
|
4075
|
+
const expected = totalEnDocs;
|
|
4076
|
+
const missing = missingByLocale.get(locale) ?? 0;
|
|
4077
|
+
const stale = staleByLocale.get(locale) ?? 0;
|
|
4078
|
+
const translated = expected - missing;
|
|
4079
|
+
const upToDate = translated - stale;
|
|
4080
|
+
const coverage = expected > 0 ? translated / expected : 0;
|
|
4081
|
+
const covPct = pct(coverage);
|
|
4082
|
+
const fillColor = covPct === 100 ? "var(--ok)" : "var(--stale)";
|
|
4083
|
+
const fallbacks = config.localeFallbacks[locale]?.length ? escapeHtml(config.localeFallbacks[locale].join(" \u2192 ")) : `<span class="dim">\u2014</span>`;
|
|
4084
|
+
const latest = latestByLocale.get(locale);
|
|
4085
|
+
const last = latest ? escapeHtml(latest.slice(0, 10)) : `<span class="dim">\u2014</span>`;
|
|
4086
|
+
return `<tr>
|
|
4087
|
+
<td>${escapeHtml(locale)}</td>
|
|
4088
|
+
<td><span class="bar"><span class="bar-fill" style="width:${covPct}%;background:${fillColor}"></span></span>${covPct}%</td>
|
|
4089
|
+
<td>${upToDate}</td>
|
|
4090
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
4091
|
+
<td>${num(missing, "tag-err")}</td>
|
|
4092
|
+
<td>${fallbacks}</td>
|
|
4093
|
+
<td>${last}</td>
|
|
4094
|
+
</tr>`;
|
|
4095
|
+
}).join("");
|
|
4096
|
+
}
|
|
4097
|
+
let typeRows;
|
|
4098
|
+
if (types.length === 0 || targetLocales.length === 0) {
|
|
4099
|
+
typeRows = `<tr><td colspan="5" class="dim">No types or target locales</td></tr>`;
|
|
4100
|
+
} else {
|
|
4101
|
+
typeRows = types.map((type) => {
|
|
4102
|
+
const stale = staleByType.get(type.id) ?? 0;
|
|
4103
|
+
const missing = missingByType.get(type.id) ?? 0;
|
|
4104
|
+
return `<tr>
|
|
4105
|
+
<td><a href="/type/${encodePathSegment(type.id)}">${escapeHtml(type.config.label)}</a></td>
|
|
4106
|
+
<td class="dim">${escapeHtml(type.id)}</td>
|
|
4107
|
+
<td>${docCountByType.get(type.id) ?? 0}</td>
|
|
4108
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
4109
|
+
<td>${num(missing, "tag-err")}</td>
|
|
4110
|
+
</tr>`;
|
|
4111
|
+
}).join("");
|
|
4112
|
+
}
|
|
4113
|
+
const html = `<div class="toolbar">Dashboard</div>
|
|
4114
|
+
${cards}
|
|
4115
|
+
<div class="section">
|
|
4116
|
+
<div class="section-head">Locales</div>
|
|
4117
|
+
<table class="data">
|
|
4118
|
+
<thead><tr><th>Locale</th><th>Coverage</th><th>Up to date</th><th>Stale</th><th>Missing</th><th>Fallbacks</th><th>Last translated</th></tr></thead>
|
|
4119
|
+
<tbody>${localeRows}</tbody>
|
|
4120
|
+
</table>
|
|
4121
|
+
</div>
|
|
4122
|
+
<div class="section">
|
|
4123
|
+
<div class="section-head">Types</div>
|
|
4124
|
+
<table class="data">
|
|
4125
|
+
<thead><tr><th>Type</th><th>ID</th><th>EN docs</th><th>Stale</th><th>Missing</th></tr></thead>
|
|
4126
|
+
<tbody>${typeRows}</tbody>
|
|
4127
|
+
</table>
|
|
4128
|
+
</div>`;
|
|
4129
|
+
return c.html(renderLayout("Dashboard", html, project));
|
|
4130
|
+
});
|
|
3982
4131
|
app.get("/type/:id", (c) => {
|
|
3983
4132
|
const typeId = c.req.param("id");
|
|
3984
4133
|
const type = project.getType(typeId);
|
|
@@ -4746,7 +4895,7 @@ Export-static flags:
|
|
|
4746
4895
|
--locale <code>... Locales to export (default: all configured)
|
|
4747
4896
|
|
|
4748
4897
|
Translate flags:
|
|
4749
|
-
--type <id> Content type (interactive picker in a TTY when omitted)
|
|
4898
|
+
--type <id> Content type, or comma-separated ids (interactive picker in a TTY when omitted)
|
|
4750
4899
|
--preset <name> Locale preset from config (interactive picker in a TTY)
|
|
4751
4900
|
--locale <code>... Target locale(s); overrides --preset
|
|
4752
4901
|
--slug <en-slug> Single English document
|
|
@@ -4838,7 +4987,8 @@ mode to use.
|
|
|
4838
4987
|
contentType: selection.contentType,
|
|
4839
4988
|
locales,
|
|
4840
4989
|
enSlug: options.slug,
|
|
4841
|
-
strategy: selection.strategy ?? "all"
|
|
4990
|
+
strategy: selection.strategy ?? "all",
|
|
4991
|
+
force: options.force
|
|
4842
4992
|
});
|
|
4843
4993
|
if (worklist.length === 0) {
|
|
4844
4994
|
if (!options.dryRun) {
|