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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../studio/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoC,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../studio/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAoC,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA4W5F,mFAAmF;AACnF,wBAAsB,WAAW,CAC/B,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,OAAO,CAAC,IAAI,CAAC,CA4Wf"}
|
package/dist/studio/server.js
CHANGED
|
@@ -504,6 +504,15 @@ function getTranslation(db, contentType, enSlug, locale) {
|
|
|
504
504
|
function listTranslationsForLocale(db, contentType, locale) {
|
|
505
505
|
return db.prepare(`SELECT * FROM translations WHERE content_type = ? AND locale = ? ORDER BY en_slug`).all(contentType, locale);
|
|
506
506
|
}
|
|
507
|
+
function countTranslations(db) {
|
|
508
|
+
const row = db.prepare(`SELECT COUNT(*) as count FROM translations`).get();
|
|
509
|
+
return row.count;
|
|
510
|
+
}
|
|
511
|
+
function latestTranslationAtByLocale(db) {
|
|
512
|
+
return db.prepare(
|
|
513
|
+
`SELECT locale, MAX(translated_at) as latest FROM translations GROUP BY locale`
|
|
514
|
+
).all();
|
|
515
|
+
}
|
|
507
516
|
unified().use(remarkParse).use(remarkMdx);
|
|
508
517
|
|
|
509
518
|
// src/loader/normalize-en.ts
|
|
@@ -619,6 +628,11 @@ function getTranslatablePayload(doc, type) {
|
|
|
619
628
|
body: doc.content
|
|
620
629
|
};
|
|
621
630
|
}
|
|
631
|
+
function parseContentTypeFilter(contentType) {
|
|
632
|
+
if (!contentType) return void 0;
|
|
633
|
+
const ids = contentType.split(",").map((id) => id.trim()).filter(Boolean);
|
|
634
|
+
return ids.length > 0 ? new Set(ids) : void 0;
|
|
635
|
+
}
|
|
622
636
|
function listEnSlugs(rootDir, contentDir) {
|
|
623
637
|
const dir = path2.join(rootDir, contentDir);
|
|
624
638
|
if (!fs2.existsSync(dir)) return [];
|
|
@@ -628,8 +642,9 @@ function buildWorklist(config, options = {}) {
|
|
|
628
642
|
const db = openStore(config, "readonly");
|
|
629
643
|
const items = [];
|
|
630
644
|
const locales = options.locales ?? config.locales.filter((locale) => locale !== config.defaultLocale);
|
|
645
|
+
const contentTypes = parseContentTypeFilter(options.contentType);
|
|
631
646
|
for (const type of config.types) {
|
|
632
|
-
if (
|
|
647
|
+
if (contentTypes && !contentTypes.has(type.id)) continue;
|
|
633
648
|
const enSlugs = options.enSlug ? [options.enSlug] : listEnSlugs(config.rootDir, type.contentDir);
|
|
634
649
|
for (const enSlug of enSlugs) {
|
|
635
650
|
const enDoc = readEnDocument(config, type, enSlug);
|
|
@@ -649,12 +664,13 @@ function buildWorklist(config, options = {}) {
|
|
|
649
664
|
});
|
|
650
665
|
continue;
|
|
651
666
|
}
|
|
652
|
-
|
|
667
|
+
const stale = existing.en_hash !== currentEnHash;
|
|
668
|
+
if (stale || options.force) {
|
|
653
669
|
items.push({
|
|
654
670
|
contentType: type.id,
|
|
655
671
|
enSlug,
|
|
656
672
|
locale,
|
|
657
|
-
reason: "stale",
|
|
673
|
+
reason: stale ? "stale" : "forced",
|
|
658
674
|
currentEnHash,
|
|
659
675
|
storedEnHash: existing.en_hash
|
|
660
676
|
});
|
|
@@ -922,12 +938,21 @@ function renderLayout(title, body, project, options = {}) {
|
|
|
922
938
|
.tag { font-size: var(--fs-sm); color: var(--dim); margin-left: 6px; }
|
|
923
939
|
.tag-warn { color: var(--stale); }
|
|
924
940
|
.tag-err { color: var(--missing); }
|
|
941
|
+
|
|
942
|
+
/* dashboard */
|
|
943
|
+
.cards { display: flex; flex-wrap: wrap; gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }
|
|
944
|
+
.card { flex: 1 1 120px; background: var(--bg); padding: 12px; min-width: 120px; }
|
|
945
|
+
.card-value { font-size: 22px; font-weight: 300; line-height: 1.2; }
|
|
946
|
+
.card-label { font-size: var(--fs-sm); color: var(--dim); margin-top: 2px; }
|
|
947
|
+
.bar { display: inline-block; width: 120px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; vertical-align: middle; margin-right: 8px; }
|
|
948
|
+
.bar-fill { display: block; height: 100%; }
|
|
925
949
|
</style>
|
|
926
950
|
</head>
|
|
927
951
|
<body>
|
|
928
952
|
<div class="app">
|
|
929
953
|
<nav class="actbar">
|
|
930
954
|
<a href="/" title="Overview">\u2302</a>
|
|
955
|
+
<a href="/dashboard" title="Dashboard">\u25A6</a>
|
|
931
956
|
<a href="/staleness" title="Staleness">\u26A0</a>
|
|
932
957
|
</nav>
|
|
933
958
|
<aside class="sidebar">
|
|
@@ -984,6 +1009,119 @@ async function startStudio(project, options = {}) {
|
|
|
984
1009
|
</table>`;
|
|
985
1010
|
return c.html(renderLayout("Overview", html, project));
|
|
986
1011
|
});
|
|
1012
|
+
app.get("/dashboard", (c) => {
|
|
1013
|
+
const db = openStore(config, "readonly");
|
|
1014
|
+
const types = project.listTypes();
|
|
1015
|
+
const targetLocales = config.locales.filter((l) => l !== config.defaultLocale);
|
|
1016
|
+
const docCountByType = /* @__PURE__ */ new Map();
|
|
1017
|
+
let totalEnDocs = 0;
|
|
1018
|
+
for (const type of types) {
|
|
1019
|
+
const n = type.list().length;
|
|
1020
|
+
docCountByType.set(type.id, n);
|
|
1021
|
+
totalEnDocs += n;
|
|
1022
|
+
}
|
|
1023
|
+
const worklist = buildWorklist(config);
|
|
1024
|
+
const missingByLocale = /* @__PURE__ */ new Map();
|
|
1025
|
+
const staleByLocale = /* @__PURE__ */ new Map();
|
|
1026
|
+
const missingByType = /* @__PURE__ */ new Map();
|
|
1027
|
+
const staleByType = /* @__PURE__ */ new Map();
|
|
1028
|
+
let missingTotal = 0;
|
|
1029
|
+
let staleTotal = 0;
|
|
1030
|
+
for (const item of worklist) {
|
|
1031
|
+
if (item.reason === "missing") {
|
|
1032
|
+
missingByLocale.set(item.locale, (missingByLocale.get(item.locale) ?? 0) + 1);
|
|
1033
|
+
missingByType.set(item.contentType, (missingByType.get(item.contentType) ?? 0) + 1);
|
|
1034
|
+
missingTotal++;
|
|
1035
|
+
} else if (item.reason === "stale") {
|
|
1036
|
+
staleByLocale.set(item.locale, (staleByLocale.get(item.locale) ?? 0) + 1);
|
|
1037
|
+
staleByType.set(item.contentType, (staleByType.get(item.contentType) ?? 0) + 1);
|
|
1038
|
+
staleTotal++;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
const storedTranslations = countTranslations(db);
|
|
1042
|
+
const latestByLocale = /* @__PURE__ */ new Map();
|
|
1043
|
+
for (const row of latestTranslationAtByLocale(db)) {
|
|
1044
|
+
if (row.latest) latestByLocale.set(row.locale, row.latest);
|
|
1045
|
+
}
|
|
1046
|
+
db.close();
|
|
1047
|
+
const expectedTotal = totalEnDocs * targetLocales.length;
|
|
1048
|
+
const coverageTotal = expectedTotal > 0 ? (expectedTotal - missingTotal) / expectedTotal : 0;
|
|
1049
|
+
const coveragePct = expectedTotal > 0 ? Math.round(coverageTotal * 100) : 0;
|
|
1050
|
+
const pct = (fraction) => Math.round(fraction * 100);
|
|
1051
|
+
const num = (n, warnClass) => n > 0 ? `<span class="${warnClass}">${n}</span>` : `<span class="dim">0</span>`;
|
|
1052
|
+
const coverageColor = expectedTotal > 0 && coveragePct === 100 ? ` style="color:var(--ok)"` : "";
|
|
1053
|
+
const coverageValue = expectedTotal > 0 ? `${coveragePct}%` : `<span class="dim">\u2014</span>`;
|
|
1054
|
+
const staleColor = staleTotal > 0 ? ` style="color:var(--stale)"` : "";
|
|
1055
|
+
const missingColor = missingTotal > 0 ? ` style="color:var(--missing)"` : "";
|
|
1056
|
+
const cards = `<div class="cards">
|
|
1057
|
+
<div class="card"><div class="card-value">${totalEnDocs}</div><div class="card-label">Documents (EN)</div></div>
|
|
1058
|
+
<div class="card"><div class="card-value">${targetLocales.length}</div><div class="card-label">Target locales</div></div>
|
|
1059
|
+
<div class="card"><div class="card-value">${storedTranslations}</div><div class="card-label">Stored translations</div></div>
|
|
1060
|
+
<div class="card"><div class="card-value"${coverageColor}>${coverageValue}</div><div class="card-label">Coverage</div></div>
|
|
1061
|
+
<div class="card"><div class="card-value"${staleColor}>${staleTotal}</div><div class="card-label">Stale</div></div>
|
|
1062
|
+
<div class="card"><div class="card-value"${missingColor}>${missingTotal}</div><div class="card-label">Missing</div></div>
|
|
1063
|
+
</div>`;
|
|
1064
|
+
let localeRows;
|
|
1065
|
+
if (totalEnDocs === 0 || targetLocales.length === 0) {
|
|
1066
|
+
localeRows = `<tr><td colspan="7" class="dim">No target locales or documents</td></tr>`;
|
|
1067
|
+
} else {
|
|
1068
|
+
localeRows = targetLocales.map((locale) => {
|
|
1069
|
+
const expected = totalEnDocs;
|
|
1070
|
+
const missing = missingByLocale.get(locale) ?? 0;
|
|
1071
|
+
const stale = staleByLocale.get(locale) ?? 0;
|
|
1072
|
+
const translated = expected - missing;
|
|
1073
|
+
const upToDate = translated - stale;
|
|
1074
|
+
const coverage = expected > 0 ? translated / expected : 0;
|
|
1075
|
+
const covPct = pct(coverage);
|
|
1076
|
+
const fillColor = covPct === 100 ? "var(--ok)" : "var(--stale)";
|
|
1077
|
+
const fallbacks = config.localeFallbacks[locale]?.length ? escapeHtml(config.localeFallbacks[locale].join(" \u2192 ")) : `<span class="dim">\u2014</span>`;
|
|
1078
|
+
const latest = latestByLocale.get(locale);
|
|
1079
|
+
const last = latest ? escapeHtml(latest.slice(0, 10)) : `<span class="dim">\u2014</span>`;
|
|
1080
|
+
return `<tr>
|
|
1081
|
+
<td>${escapeHtml(locale)}</td>
|
|
1082
|
+
<td><span class="bar"><span class="bar-fill" style="width:${covPct}%;background:${fillColor}"></span></span>${covPct}%</td>
|
|
1083
|
+
<td>${upToDate}</td>
|
|
1084
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
1085
|
+
<td>${num(missing, "tag-err")}</td>
|
|
1086
|
+
<td>${fallbacks}</td>
|
|
1087
|
+
<td>${last}</td>
|
|
1088
|
+
</tr>`;
|
|
1089
|
+
}).join("");
|
|
1090
|
+
}
|
|
1091
|
+
let typeRows;
|
|
1092
|
+
if (types.length === 0 || targetLocales.length === 0) {
|
|
1093
|
+
typeRows = `<tr><td colspan="5" class="dim">No types or target locales</td></tr>`;
|
|
1094
|
+
} else {
|
|
1095
|
+
typeRows = types.map((type) => {
|
|
1096
|
+
const stale = staleByType.get(type.id) ?? 0;
|
|
1097
|
+
const missing = missingByType.get(type.id) ?? 0;
|
|
1098
|
+
return `<tr>
|
|
1099
|
+
<td><a href="/type/${encodePathSegment(type.id)}">${escapeHtml(type.config.label)}</a></td>
|
|
1100
|
+
<td class="dim">${escapeHtml(type.id)}</td>
|
|
1101
|
+
<td>${docCountByType.get(type.id) ?? 0}</td>
|
|
1102
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
1103
|
+
<td>${num(missing, "tag-err")}</td>
|
|
1104
|
+
</tr>`;
|
|
1105
|
+
}).join("");
|
|
1106
|
+
}
|
|
1107
|
+
const html = `<div class="toolbar">Dashboard</div>
|
|
1108
|
+
${cards}
|
|
1109
|
+
<div class="section">
|
|
1110
|
+
<div class="section-head">Locales</div>
|
|
1111
|
+
<table class="data">
|
|
1112
|
+
<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>
|
|
1113
|
+
<tbody>${localeRows}</tbody>
|
|
1114
|
+
</table>
|
|
1115
|
+
</div>
|
|
1116
|
+
<div class="section">
|
|
1117
|
+
<div class="section-head">Types</div>
|
|
1118
|
+
<table class="data">
|
|
1119
|
+
<thead><tr><th>Type</th><th>ID</th><th>EN docs</th><th>Stale</th><th>Missing</th></tr></thead>
|
|
1120
|
+
<tbody>${typeRows}</tbody>
|
|
1121
|
+
</table>
|
|
1122
|
+
</div>`;
|
|
1123
|
+
return c.html(renderLayout("Dashboard", html, project));
|
|
1124
|
+
});
|
|
987
1125
|
app.get("/type/:id", (c) => {
|
|
988
1126
|
const typeId = c.req.param("id");
|
|
989
1127
|
const type = project.getType(typeId);
|