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.cjs
CHANGED
|
@@ -773,6 +773,15 @@ function listTranslationsForLocale(db, contentType, locale) {
|
|
|
773
773
|
function bulkLoadTranslations(db) {
|
|
774
774
|
return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
|
|
775
775
|
}
|
|
776
|
+
function countTranslations(db) {
|
|
777
|
+
const row = db.prepare(`SELECT COUNT(*) as count FROM translations`).get();
|
|
778
|
+
return row.count;
|
|
779
|
+
}
|
|
780
|
+
function latestTranslationAtByLocale(db) {
|
|
781
|
+
return db.prepare(
|
|
782
|
+
`SELECT locale, MAX(translated_at) as latest FROM translations GROUP BY locale`
|
|
783
|
+
).all();
|
|
784
|
+
}
|
|
776
785
|
|
|
777
786
|
// src/translate/validate-mdx-body.ts
|
|
778
787
|
init_cjs_shims();
|
|
@@ -2131,6 +2140,11 @@ function computePageEnHash(translatableFrontmatter, body) {
|
|
|
2131
2140
|
|
|
2132
2141
|
// src/translate/worklist.ts
|
|
2133
2142
|
init_sqlite();
|
|
2143
|
+
function parseContentTypeFilter(contentType) {
|
|
2144
|
+
if (!contentType) return void 0;
|
|
2145
|
+
const ids = contentType.split(",").map((id) => id.trim()).filter(Boolean);
|
|
2146
|
+
return ids.length > 0 ? new Set(ids) : void 0;
|
|
2147
|
+
}
|
|
2134
2148
|
function listEnSlugs4(rootDir, contentDir) {
|
|
2135
2149
|
const dir = path3__default.default.join(rootDir, contentDir);
|
|
2136
2150
|
if (!fs2__default.default.existsSync(dir)) return [];
|
|
@@ -2140,8 +2154,9 @@ function buildWorklist(config, options = {}) {
|
|
|
2140
2154
|
const db = openStore(config, "readonly");
|
|
2141
2155
|
const items = [];
|
|
2142
2156
|
const locales = options.locales ?? config.locales.filter((locale) => locale !== config.defaultLocale);
|
|
2157
|
+
const contentTypes = parseContentTypeFilter(options.contentType);
|
|
2143
2158
|
for (const type of config.types) {
|
|
2144
|
-
if (
|
|
2159
|
+
if (contentTypes && !contentTypes.has(type.id)) continue;
|
|
2145
2160
|
const enSlugs = options.enSlug ? [options.enSlug] : listEnSlugs4(config.rootDir, type.contentDir);
|
|
2146
2161
|
for (const enSlug of enSlugs) {
|
|
2147
2162
|
const enDoc = readEnDocument(config, type, enSlug);
|
|
@@ -2161,12 +2176,13 @@ function buildWorklist(config, options = {}) {
|
|
|
2161
2176
|
});
|
|
2162
2177
|
continue;
|
|
2163
2178
|
}
|
|
2164
|
-
|
|
2179
|
+
const stale = existing.en_hash !== currentEnHash;
|
|
2180
|
+
if (stale || options.force) {
|
|
2165
2181
|
items.push({
|
|
2166
2182
|
contentType: type.id,
|
|
2167
2183
|
enSlug,
|
|
2168
2184
|
locale,
|
|
2169
|
-
reason: "stale",
|
|
2185
|
+
reason: stale ? "stale" : "forced",
|
|
2170
2186
|
currentEnHash,
|
|
2171
2187
|
storedEnHash: existing.en_hash
|
|
2172
2188
|
});
|
|
@@ -3945,12 +3961,21 @@ function renderLayout(title, body, project, options = {}) {
|
|
|
3945
3961
|
.tag { font-size: var(--fs-sm); color: var(--dim); margin-left: 6px; }
|
|
3946
3962
|
.tag-warn { color: var(--stale); }
|
|
3947
3963
|
.tag-err { color: var(--missing); }
|
|
3964
|
+
|
|
3965
|
+
/* dashboard */
|
|
3966
|
+
.cards { display: flex; flex-wrap: wrap; gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }
|
|
3967
|
+
.card { flex: 1 1 120px; background: var(--bg); padding: 12px; min-width: 120px; }
|
|
3968
|
+
.card-value { font-size: 22px; font-weight: 300; line-height: 1.2; }
|
|
3969
|
+
.card-label { font-size: var(--fs-sm); color: var(--dim); margin-top: 2px; }
|
|
3970
|
+
.bar { display: inline-block; width: 120px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; vertical-align: middle; margin-right: 8px; }
|
|
3971
|
+
.bar-fill { display: block; height: 100%; }
|
|
3948
3972
|
</style>
|
|
3949
3973
|
</head>
|
|
3950
3974
|
<body>
|
|
3951
3975
|
<div class="app">
|
|
3952
3976
|
<nav class="actbar">
|
|
3953
3977
|
<a href="/" title="Overview">\u2302</a>
|
|
3978
|
+
<a href="/dashboard" title="Dashboard">\u25A6</a>
|
|
3954
3979
|
<a href="/staleness" title="Staleness">\u26A0</a>
|
|
3955
3980
|
</nav>
|
|
3956
3981
|
<aside class="sidebar">
|
|
@@ -4007,6 +4032,119 @@ async function startStudio(project, options = {}) {
|
|
|
4007
4032
|
</table>`;
|
|
4008
4033
|
return c.html(renderLayout("Overview", html, project));
|
|
4009
4034
|
});
|
|
4035
|
+
app.get("/dashboard", (c) => {
|
|
4036
|
+
const db = openStore(config, "readonly");
|
|
4037
|
+
const types = project.listTypes();
|
|
4038
|
+
const targetLocales = config.locales.filter((l) => l !== config.defaultLocale);
|
|
4039
|
+
const docCountByType = /* @__PURE__ */ new Map();
|
|
4040
|
+
let totalEnDocs = 0;
|
|
4041
|
+
for (const type of types) {
|
|
4042
|
+
const n = type.list().length;
|
|
4043
|
+
docCountByType.set(type.id, n);
|
|
4044
|
+
totalEnDocs += n;
|
|
4045
|
+
}
|
|
4046
|
+
const worklist = buildWorklist(config);
|
|
4047
|
+
const missingByLocale = /* @__PURE__ */ new Map();
|
|
4048
|
+
const staleByLocale = /* @__PURE__ */ new Map();
|
|
4049
|
+
const missingByType = /* @__PURE__ */ new Map();
|
|
4050
|
+
const staleByType = /* @__PURE__ */ new Map();
|
|
4051
|
+
let missingTotal = 0;
|
|
4052
|
+
let staleTotal = 0;
|
|
4053
|
+
for (const item of worklist) {
|
|
4054
|
+
if (item.reason === "missing") {
|
|
4055
|
+
missingByLocale.set(item.locale, (missingByLocale.get(item.locale) ?? 0) + 1);
|
|
4056
|
+
missingByType.set(item.contentType, (missingByType.get(item.contentType) ?? 0) + 1);
|
|
4057
|
+
missingTotal++;
|
|
4058
|
+
} else if (item.reason === "stale") {
|
|
4059
|
+
staleByLocale.set(item.locale, (staleByLocale.get(item.locale) ?? 0) + 1);
|
|
4060
|
+
staleByType.set(item.contentType, (staleByType.get(item.contentType) ?? 0) + 1);
|
|
4061
|
+
staleTotal++;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
const storedTranslations = countTranslations(db);
|
|
4065
|
+
const latestByLocale = /* @__PURE__ */ new Map();
|
|
4066
|
+
for (const row of latestTranslationAtByLocale(db)) {
|
|
4067
|
+
if (row.latest) latestByLocale.set(row.locale, row.latest);
|
|
4068
|
+
}
|
|
4069
|
+
db.close();
|
|
4070
|
+
const expectedTotal = totalEnDocs * targetLocales.length;
|
|
4071
|
+
const coverageTotal = expectedTotal > 0 ? (expectedTotal - missingTotal) / expectedTotal : 0;
|
|
4072
|
+
const coveragePct = expectedTotal > 0 ? Math.round(coverageTotal * 100) : 0;
|
|
4073
|
+
const pct = (fraction) => Math.round(fraction * 100);
|
|
4074
|
+
const num = (n, warnClass) => n > 0 ? `<span class="${warnClass}">${n}</span>` : `<span class="dim">0</span>`;
|
|
4075
|
+
const coverageColor = expectedTotal > 0 && coveragePct === 100 ? ` style="color:var(--ok)"` : "";
|
|
4076
|
+
const coverageValue = expectedTotal > 0 ? `${coveragePct}%` : `<span class="dim">\u2014</span>`;
|
|
4077
|
+
const staleColor = staleTotal > 0 ? ` style="color:var(--stale)"` : "";
|
|
4078
|
+
const missingColor = missingTotal > 0 ? ` style="color:var(--missing)"` : "";
|
|
4079
|
+
const cards = `<div class="cards">
|
|
4080
|
+
<div class="card"><div class="card-value">${totalEnDocs}</div><div class="card-label">Documents (EN)</div></div>
|
|
4081
|
+
<div class="card"><div class="card-value">${targetLocales.length}</div><div class="card-label">Target locales</div></div>
|
|
4082
|
+
<div class="card"><div class="card-value">${storedTranslations}</div><div class="card-label">Stored translations</div></div>
|
|
4083
|
+
<div class="card"><div class="card-value"${coverageColor}>${coverageValue}</div><div class="card-label">Coverage</div></div>
|
|
4084
|
+
<div class="card"><div class="card-value"${staleColor}>${staleTotal}</div><div class="card-label">Stale</div></div>
|
|
4085
|
+
<div class="card"><div class="card-value"${missingColor}>${missingTotal}</div><div class="card-label">Missing</div></div>
|
|
4086
|
+
</div>`;
|
|
4087
|
+
let localeRows;
|
|
4088
|
+
if (totalEnDocs === 0 || targetLocales.length === 0) {
|
|
4089
|
+
localeRows = `<tr><td colspan="7" class="dim">No target locales or documents</td></tr>`;
|
|
4090
|
+
} else {
|
|
4091
|
+
localeRows = targetLocales.map((locale) => {
|
|
4092
|
+
const expected = totalEnDocs;
|
|
4093
|
+
const missing = missingByLocale.get(locale) ?? 0;
|
|
4094
|
+
const stale = staleByLocale.get(locale) ?? 0;
|
|
4095
|
+
const translated = expected - missing;
|
|
4096
|
+
const upToDate = translated - stale;
|
|
4097
|
+
const coverage = expected > 0 ? translated / expected : 0;
|
|
4098
|
+
const covPct = pct(coverage);
|
|
4099
|
+
const fillColor = covPct === 100 ? "var(--ok)" : "var(--stale)";
|
|
4100
|
+
const fallbacks = config.localeFallbacks[locale]?.length ? escapeHtml(config.localeFallbacks[locale].join(" \u2192 ")) : `<span class="dim">\u2014</span>`;
|
|
4101
|
+
const latest = latestByLocale.get(locale);
|
|
4102
|
+
const last = latest ? escapeHtml(latest.slice(0, 10)) : `<span class="dim">\u2014</span>`;
|
|
4103
|
+
return `<tr>
|
|
4104
|
+
<td>${escapeHtml(locale)}</td>
|
|
4105
|
+
<td><span class="bar"><span class="bar-fill" style="width:${covPct}%;background:${fillColor}"></span></span>${covPct}%</td>
|
|
4106
|
+
<td>${upToDate}</td>
|
|
4107
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
4108
|
+
<td>${num(missing, "tag-err")}</td>
|
|
4109
|
+
<td>${fallbacks}</td>
|
|
4110
|
+
<td>${last}</td>
|
|
4111
|
+
</tr>`;
|
|
4112
|
+
}).join("");
|
|
4113
|
+
}
|
|
4114
|
+
let typeRows;
|
|
4115
|
+
if (types.length === 0 || targetLocales.length === 0) {
|
|
4116
|
+
typeRows = `<tr><td colspan="5" class="dim">No types or target locales</td></tr>`;
|
|
4117
|
+
} else {
|
|
4118
|
+
typeRows = types.map((type) => {
|
|
4119
|
+
const stale = staleByType.get(type.id) ?? 0;
|
|
4120
|
+
const missing = missingByType.get(type.id) ?? 0;
|
|
4121
|
+
return `<tr>
|
|
4122
|
+
<td><a href="/type/${encodePathSegment(type.id)}">${escapeHtml(type.config.label)}</a></td>
|
|
4123
|
+
<td class="dim">${escapeHtml(type.id)}</td>
|
|
4124
|
+
<td>${docCountByType.get(type.id) ?? 0}</td>
|
|
4125
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
4126
|
+
<td>${num(missing, "tag-err")}</td>
|
|
4127
|
+
</tr>`;
|
|
4128
|
+
}).join("");
|
|
4129
|
+
}
|
|
4130
|
+
const html = `<div class="toolbar">Dashboard</div>
|
|
4131
|
+
${cards}
|
|
4132
|
+
<div class="section">
|
|
4133
|
+
<div class="section-head">Locales</div>
|
|
4134
|
+
<table class="data">
|
|
4135
|
+
<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>
|
|
4136
|
+
<tbody>${localeRows}</tbody>
|
|
4137
|
+
</table>
|
|
4138
|
+
</div>
|
|
4139
|
+
<div class="section">
|
|
4140
|
+
<div class="section-head">Types</div>
|
|
4141
|
+
<table class="data">
|
|
4142
|
+
<thead><tr><th>Type</th><th>ID</th><th>EN docs</th><th>Stale</th><th>Missing</th></tr></thead>
|
|
4143
|
+
<tbody>${typeRows}</tbody>
|
|
4144
|
+
</table>
|
|
4145
|
+
</div>`;
|
|
4146
|
+
return c.html(renderLayout("Dashboard", html, project));
|
|
4147
|
+
});
|
|
4010
4148
|
app.get("/type/:id", (c) => {
|
|
4011
4149
|
const typeId = c.req.param("id");
|
|
4012
4150
|
const type = project.getType(typeId);
|
|
@@ -4774,7 +4912,7 @@ Export-static flags:
|
|
|
4774
4912
|
--locale <code>... Locales to export (default: all configured)
|
|
4775
4913
|
|
|
4776
4914
|
Translate flags:
|
|
4777
|
-
--type <id> Content type (interactive picker in a TTY when omitted)
|
|
4915
|
+
--type <id> Content type, or comma-separated ids (interactive picker in a TTY when omitted)
|
|
4778
4916
|
--preset <name> Locale preset from config (interactive picker in a TTY)
|
|
4779
4917
|
--locale <code>... Target locale(s); overrides --preset
|
|
4780
4918
|
--slug <en-slug> Single English document
|
|
@@ -4866,7 +5004,8 @@ mode to use.
|
|
|
4866
5004
|
contentType: selection.contentType,
|
|
4867
5005
|
locales,
|
|
4868
5006
|
enSlug: options.slug,
|
|
4869
|
-
strategy: selection.strategy ?? "all"
|
|
5007
|
+
strategy: selection.strategy ?? "all",
|
|
5008
|
+
force: options.force
|
|
4870
5009
|
});
|
|
4871
5010
|
if (worklist.length === 0) {
|
|
4872
5011
|
if (!options.dryRun) {
|