scribe-cms 0.0.16 → 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 +144 -5
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +144 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +10 -3
- 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/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
|
});
|
|
@@ -3928,12 +3944,21 @@ function renderLayout(title, body, project, options = {}) {
|
|
|
3928
3944
|
.tag { font-size: var(--fs-sm); color: var(--dim); margin-left: 6px; }
|
|
3929
3945
|
.tag-warn { color: var(--stale); }
|
|
3930
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%; }
|
|
3931
3955
|
</style>
|
|
3932
3956
|
</head>
|
|
3933
3957
|
<body>
|
|
3934
3958
|
<div class="app">
|
|
3935
3959
|
<nav class="actbar">
|
|
3936
3960
|
<a href="/" title="Overview">\u2302</a>
|
|
3961
|
+
<a href="/dashboard" title="Dashboard">\u25A6</a>
|
|
3937
3962
|
<a href="/staleness" title="Staleness">\u26A0</a>
|
|
3938
3963
|
</nav>
|
|
3939
3964
|
<aside class="sidebar">
|
|
@@ -3990,6 +4015,119 @@ async function startStudio(project, options = {}) {
|
|
|
3990
4015
|
</table>`;
|
|
3991
4016
|
return c.html(renderLayout("Overview", html, project));
|
|
3992
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
|
+
});
|
|
3993
4131
|
app.get("/type/:id", (c) => {
|
|
3994
4132
|
const typeId = c.req.param("id");
|
|
3995
4133
|
const type = project.getType(typeId);
|
|
@@ -4757,7 +4895,7 @@ Export-static flags:
|
|
|
4757
4895
|
--locale <code>... Locales to export (default: all configured)
|
|
4758
4896
|
|
|
4759
4897
|
Translate flags:
|
|
4760
|
-
--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)
|
|
4761
4899
|
--preset <name> Locale preset from config (interactive picker in a TTY)
|
|
4762
4900
|
--locale <code>... Target locale(s); overrides --preset
|
|
4763
4901
|
--slug <en-slug> Single English document
|
|
@@ -4849,7 +4987,8 @@ mode to use.
|
|
|
4849
4987
|
contentType: selection.contentType,
|
|
4850
4988
|
locales,
|
|
4851
4989
|
enSlug: options.slug,
|
|
4852
|
-
strategy: selection.strategy ?? "all"
|
|
4990
|
+
strategy: selection.strategy ?? "all",
|
|
4991
|
+
force: options.force
|
|
4853
4992
|
});
|
|
4854
4993
|
if (worklist.length === 0) {
|
|
4855
4994
|
if (!options.dryRun) {
|