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
|
@@ -53,5 +53,13 @@ export declare function listTranslationsForEnSlug(db: Database.Database, content
|
|
|
53
53
|
export declare function listTranslationsForLocale(db: Database.Database, contentType: string, locale: string): TranslationRow[];
|
|
54
54
|
export declare function bulkLoadTranslations(db: Database.Database): TranslationRow[];
|
|
55
55
|
export declare function countTranslations(db: Database.Database): number;
|
|
56
|
+
export declare function countTranslationsByLocale(db: Database.Database): Array<{
|
|
57
|
+
locale: string;
|
|
58
|
+
count: number;
|
|
59
|
+
}>;
|
|
60
|
+
export declare function latestTranslationAtByLocale(db: Database.Database): Array<{
|
|
61
|
+
locale: string;
|
|
62
|
+
latest: string;
|
|
63
|
+
}>;
|
|
56
64
|
export declare function countStaleTranslations(db: Database.Database, contentType: string, enSlug: string, currentEnHash: string): number;
|
|
57
65
|
//# sourceMappingURL=translations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/storage/translations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAsB3F;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,UAAU,EAAE,MAAM,GACjB,aAAa,GAAG,SAAS,CAI3B;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,qBAAqB,EAAE,CAWzB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAyBtF;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,cAAc,GAAG,SAAS,CAM5B;AAED,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,WAAW,EAAE,MAAM,GAClB,cAAc,EAAE,CAIlB;AAED,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,cAAc,EAAE,CAMlB;AAED,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,cAAc,EAAE,CAIlB;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,cAAc,EAAE,CAE5E;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAG/D;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,MAAM,CAQR"}
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/storage/translations.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import type { ResolvedTranslateConfig } from "../resolve-translate-config.js";
|
|
|
2
2
|
import type { SlugStrategy } from "../../core/types.js";
|
|
3
3
|
export declare const LOCALE_NAMES: Record<string, string>;
|
|
4
4
|
export declare function defaultLocalizationPrompt(localeName: string, locale: string): string;
|
|
5
|
+
/** Type/project `translate.prompt` extras are appended before the locale directive. */
|
|
6
|
+
export declare function buildLocalizationPrompt(promptOverride: string | undefined, localeName: string, locale: string): string;
|
|
5
7
|
export declare function buildPageTranslationPrompt(input: {
|
|
6
8
|
resolved: ResolvedTranslateConfig;
|
|
7
9
|
targetLocale: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-prompt.d.ts","sourceRoot":"","sources":["../../../../src/translate/prompts/translation-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiB/C,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOpF;AA8CD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"translation-prompt.d.ts","sourceRoot":"","sources":["../../../../src/translate/prompts/translation-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiB/C,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOpF;AAED,uFAAuF;AACvF,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,MAAM,CAIR;AA8CD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,CAgDT"}
|
|
@@ -3,7 +3,7 @@ export interface TranslationWorkItem {
|
|
|
3
3
|
contentType: string;
|
|
4
4
|
enSlug: string;
|
|
5
5
|
locale: string;
|
|
6
|
-
reason: "missing" | "stale";
|
|
6
|
+
reason: "missing" | "stale" | "forced";
|
|
7
7
|
currentEnHash: string;
|
|
8
8
|
storedEnHash?: string;
|
|
9
9
|
/**
|
|
@@ -15,11 +15,14 @@ export interface TranslationWorkItem {
|
|
|
15
15
|
}
|
|
16
16
|
export type TranslationWorklistStrategy = "all" | "missing-only";
|
|
17
17
|
export interface WorklistOptions {
|
|
18
|
+
/** Single id or comma-separated ids (e.g. `vertical,platform`). */
|
|
18
19
|
contentType?: string;
|
|
19
20
|
locales?: string[];
|
|
20
21
|
enSlug?: string;
|
|
21
22
|
/** Which pages to include: all stale/missing (default) or missing only. */
|
|
22
23
|
strategy?: TranslationWorklistStrategy;
|
|
24
|
+
/** Include fresh translations so prepareTranslation re-runs despite matching hashes. */
|
|
25
|
+
force?: boolean;
|
|
23
26
|
}
|
|
24
27
|
/** List locale pages that are missing or stale vs current EN content. */
|
|
25
28
|
export declare function buildWorklist(config: ScribeConfig, options?: WorklistOptions): TranslationWorkItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worklist.d.ts","sourceRoot":"","sources":["../../../src/translate/worklist.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAOrD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"worklist.d.ts","sourceRoot":"","sources":["../../../src/translate/worklist.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAOrD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,2BAA2B,GAAG,KAAK,GAAG,cAAc,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,2BAA2B,CAAC;IACvC,wFAAwF;IACxF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAoBD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB,GAAG,mBAAmB,EAAE,CAoDxG;AAED,+DAA+D;AAC/D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,MAAM,EAAE,CAMV"}
|
package/dist/studio/server.cjs
CHANGED
|
@@ -515,6 +515,15 @@ function getTranslation(db, contentType, enSlug, locale) {
|
|
|
515
515
|
function listTranslationsForLocale(db, contentType, locale) {
|
|
516
516
|
return db.prepare(`SELECT * FROM translations WHERE content_type = ? AND locale = ? ORDER BY en_slug`).all(contentType, locale);
|
|
517
517
|
}
|
|
518
|
+
function countTranslations(db) {
|
|
519
|
+
const row = db.prepare(`SELECT COUNT(*) as count FROM translations`).get();
|
|
520
|
+
return row.count;
|
|
521
|
+
}
|
|
522
|
+
function latestTranslationAtByLocale(db) {
|
|
523
|
+
return db.prepare(
|
|
524
|
+
`SELECT locale, MAX(translated_at) as latest FROM translations GROUP BY locale`
|
|
525
|
+
).all();
|
|
526
|
+
}
|
|
518
527
|
unified.unified().use(remarkParse__default.default).use(remarkMdx__default.default);
|
|
519
528
|
|
|
520
529
|
// src/loader/normalize-en.ts
|
|
@@ -630,6 +639,11 @@ function getTranslatablePayload(doc, type) {
|
|
|
630
639
|
body: doc.content
|
|
631
640
|
};
|
|
632
641
|
}
|
|
642
|
+
function parseContentTypeFilter(contentType) {
|
|
643
|
+
if (!contentType) return void 0;
|
|
644
|
+
const ids = contentType.split(",").map((id) => id.trim()).filter(Boolean);
|
|
645
|
+
return ids.length > 0 ? new Set(ids) : void 0;
|
|
646
|
+
}
|
|
633
647
|
function listEnSlugs(rootDir, contentDir) {
|
|
634
648
|
const dir = path2__default.default.join(rootDir, contentDir);
|
|
635
649
|
if (!fs2__default.default.existsSync(dir)) return [];
|
|
@@ -639,8 +653,9 @@ function buildWorklist(config, options = {}) {
|
|
|
639
653
|
const db = openStore(config, "readonly");
|
|
640
654
|
const items = [];
|
|
641
655
|
const locales = options.locales ?? config.locales.filter((locale) => locale !== config.defaultLocale);
|
|
656
|
+
const contentTypes = parseContentTypeFilter(options.contentType);
|
|
642
657
|
for (const type of config.types) {
|
|
643
|
-
if (
|
|
658
|
+
if (contentTypes && !contentTypes.has(type.id)) continue;
|
|
644
659
|
const enSlugs = options.enSlug ? [options.enSlug] : listEnSlugs(config.rootDir, type.contentDir);
|
|
645
660
|
for (const enSlug of enSlugs) {
|
|
646
661
|
const enDoc = readEnDocument(config, type, enSlug);
|
|
@@ -660,12 +675,13 @@ function buildWorklist(config, options = {}) {
|
|
|
660
675
|
});
|
|
661
676
|
continue;
|
|
662
677
|
}
|
|
663
|
-
|
|
678
|
+
const stale = existing.en_hash !== currentEnHash;
|
|
679
|
+
if (stale || options.force) {
|
|
664
680
|
items.push({
|
|
665
681
|
contentType: type.id,
|
|
666
682
|
enSlug,
|
|
667
683
|
locale,
|
|
668
|
-
reason: "stale",
|
|
684
|
+
reason: stale ? "stale" : "forced",
|
|
669
685
|
currentEnHash,
|
|
670
686
|
storedEnHash: existing.en_hash
|
|
671
687
|
});
|
|
@@ -933,12 +949,21 @@ function renderLayout(title, body, project, options = {}) {
|
|
|
933
949
|
.tag { font-size: var(--fs-sm); color: var(--dim); margin-left: 6px; }
|
|
934
950
|
.tag-warn { color: var(--stale); }
|
|
935
951
|
.tag-err { color: var(--missing); }
|
|
952
|
+
|
|
953
|
+
/* dashboard */
|
|
954
|
+
.cards { display: flex; flex-wrap: wrap; gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }
|
|
955
|
+
.card { flex: 1 1 120px; background: var(--bg); padding: 12px; min-width: 120px; }
|
|
956
|
+
.card-value { font-size: 22px; font-weight: 300; line-height: 1.2; }
|
|
957
|
+
.card-label { font-size: var(--fs-sm); color: var(--dim); margin-top: 2px; }
|
|
958
|
+
.bar { display: inline-block; width: 120px; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; vertical-align: middle; margin-right: 8px; }
|
|
959
|
+
.bar-fill { display: block; height: 100%; }
|
|
936
960
|
</style>
|
|
937
961
|
</head>
|
|
938
962
|
<body>
|
|
939
963
|
<div class="app">
|
|
940
964
|
<nav class="actbar">
|
|
941
965
|
<a href="/" title="Overview">\u2302</a>
|
|
966
|
+
<a href="/dashboard" title="Dashboard">\u25A6</a>
|
|
942
967
|
<a href="/staleness" title="Staleness">\u26A0</a>
|
|
943
968
|
</nav>
|
|
944
969
|
<aside class="sidebar">
|
|
@@ -995,6 +1020,119 @@ async function startStudio(project, options = {}) {
|
|
|
995
1020
|
</table>`;
|
|
996
1021
|
return c.html(renderLayout("Overview", html, project));
|
|
997
1022
|
});
|
|
1023
|
+
app.get("/dashboard", (c) => {
|
|
1024
|
+
const db = openStore(config, "readonly");
|
|
1025
|
+
const types = project.listTypes();
|
|
1026
|
+
const targetLocales = config.locales.filter((l) => l !== config.defaultLocale);
|
|
1027
|
+
const docCountByType = /* @__PURE__ */ new Map();
|
|
1028
|
+
let totalEnDocs = 0;
|
|
1029
|
+
for (const type of types) {
|
|
1030
|
+
const n = type.list().length;
|
|
1031
|
+
docCountByType.set(type.id, n);
|
|
1032
|
+
totalEnDocs += n;
|
|
1033
|
+
}
|
|
1034
|
+
const worklist = buildWorklist(config);
|
|
1035
|
+
const missingByLocale = /* @__PURE__ */ new Map();
|
|
1036
|
+
const staleByLocale = /* @__PURE__ */ new Map();
|
|
1037
|
+
const missingByType = /* @__PURE__ */ new Map();
|
|
1038
|
+
const staleByType = /* @__PURE__ */ new Map();
|
|
1039
|
+
let missingTotal = 0;
|
|
1040
|
+
let staleTotal = 0;
|
|
1041
|
+
for (const item of worklist) {
|
|
1042
|
+
if (item.reason === "missing") {
|
|
1043
|
+
missingByLocale.set(item.locale, (missingByLocale.get(item.locale) ?? 0) + 1);
|
|
1044
|
+
missingByType.set(item.contentType, (missingByType.get(item.contentType) ?? 0) + 1);
|
|
1045
|
+
missingTotal++;
|
|
1046
|
+
} else if (item.reason === "stale") {
|
|
1047
|
+
staleByLocale.set(item.locale, (staleByLocale.get(item.locale) ?? 0) + 1);
|
|
1048
|
+
staleByType.set(item.contentType, (staleByType.get(item.contentType) ?? 0) + 1);
|
|
1049
|
+
staleTotal++;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const storedTranslations = countTranslations(db);
|
|
1053
|
+
const latestByLocale = /* @__PURE__ */ new Map();
|
|
1054
|
+
for (const row of latestTranslationAtByLocale(db)) {
|
|
1055
|
+
if (row.latest) latestByLocale.set(row.locale, row.latest);
|
|
1056
|
+
}
|
|
1057
|
+
db.close();
|
|
1058
|
+
const expectedTotal = totalEnDocs * targetLocales.length;
|
|
1059
|
+
const coverageTotal = expectedTotal > 0 ? (expectedTotal - missingTotal) / expectedTotal : 0;
|
|
1060
|
+
const coveragePct = expectedTotal > 0 ? Math.round(coverageTotal * 100) : 0;
|
|
1061
|
+
const pct = (fraction) => Math.round(fraction * 100);
|
|
1062
|
+
const num = (n, warnClass) => n > 0 ? `<span class="${warnClass}">${n}</span>` : `<span class="dim">0</span>`;
|
|
1063
|
+
const coverageColor = expectedTotal > 0 && coveragePct === 100 ? ` style="color:var(--ok)"` : "";
|
|
1064
|
+
const coverageValue = expectedTotal > 0 ? `${coveragePct}%` : `<span class="dim">\u2014</span>`;
|
|
1065
|
+
const staleColor = staleTotal > 0 ? ` style="color:var(--stale)"` : "";
|
|
1066
|
+
const missingColor = missingTotal > 0 ? ` style="color:var(--missing)"` : "";
|
|
1067
|
+
const cards = `<div class="cards">
|
|
1068
|
+
<div class="card"><div class="card-value">${totalEnDocs}</div><div class="card-label">Documents (EN)</div></div>
|
|
1069
|
+
<div class="card"><div class="card-value">${targetLocales.length}</div><div class="card-label">Target locales</div></div>
|
|
1070
|
+
<div class="card"><div class="card-value">${storedTranslations}</div><div class="card-label">Stored translations</div></div>
|
|
1071
|
+
<div class="card"><div class="card-value"${coverageColor}>${coverageValue}</div><div class="card-label">Coverage</div></div>
|
|
1072
|
+
<div class="card"><div class="card-value"${staleColor}>${staleTotal}</div><div class="card-label">Stale</div></div>
|
|
1073
|
+
<div class="card"><div class="card-value"${missingColor}>${missingTotal}</div><div class="card-label">Missing</div></div>
|
|
1074
|
+
</div>`;
|
|
1075
|
+
let localeRows;
|
|
1076
|
+
if (totalEnDocs === 0 || targetLocales.length === 0) {
|
|
1077
|
+
localeRows = `<tr><td colspan="7" class="dim">No target locales or documents</td></tr>`;
|
|
1078
|
+
} else {
|
|
1079
|
+
localeRows = targetLocales.map((locale) => {
|
|
1080
|
+
const expected = totalEnDocs;
|
|
1081
|
+
const missing = missingByLocale.get(locale) ?? 0;
|
|
1082
|
+
const stale = staleByLocale.get(locale) ?? 0;
|
|
1083
|
+
const translated = expected - missing;
|
|
1084
|
+
const upToDate = translated - stale;
|
|
1085
|
+
const coverage = expected > 0 ? translated / expected : 0;
|
|
1086
|
+
const covPct = pct(coverage);
|
|
1087
|
+
const fillColor = covPct === 100 ? "var(--ok)" : "var(--stale)";
|
|
1088
|
+
const fallbacks = config.localeFallbacks[locale]?.length ? escapeHtml(config.localeFallbacks[locale].join(" \u2192 ")) : `<span class="dim">\u2014</span>`;
|
|
1089
|
+
const latest = latestByLocale.get(locale);
|
|
1090
|
+
const last = latest ? escapeHtml(latest.slice(0, 10)) : `<span class="dim">\u2014</span>`;
|
|
1091
|
+
return `<tr>
|
|
1092
|
+
<td>${escapeHtml(locale)}</td>
|
|
1093
|
+
<td><span class="bar"><span class="bar-fill" style="width:${covPct}%;background:${fillColor}"></span></span>${covPct}%</td>
|
|
1094
|
+
<td>${upToDate}</td>
|
|
1095
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
1096
|
+
<td>${num(missing, "tag-err")}</td>
|
|
1097
|
+
<td>${fallbacks}</td>
|
|
1098
|
+
<td>${last}</td>
|
|
1099
|
+
</tr>`;
|
|
1100
|
+
}).join("");
|
|
1101
|
+
}
|
|
1102
|
+
let typeRows;
|
|
1103
|
+
if (types.length === 0 || targetLocales.length === 0) {
|
|
1104
|
+
typeRows = `<tr><td colspan="5" class="dim">No types or target locales</td></tr>`;
|
|
1105
|
+
} else {
|
|
1106
|
+
typeRows = types.map((type) => {
|
|
1107
|
+
const stale = staleByType.get(type.id) ?? 0;
|
|
1108
|
+
const missing = missingByType.get(type.id) ?? 0;
|
|
1109
|
+
return `<tr>
|
|
1110
|
+
<td><a href="/type/${encodePathSegment(type.id)}">${escapeHtml(type.config.label)}</a></td>
|
|
1111
|
+
<td class="dim">${escapeHtml(type.id)}</td>
|
|
1112
|
+
<td>${docCountByType.get(type.id) ?? 0}</td>
|
|
1113
|
+
<td>${num(stale, "tag-warn")}</td>
|
|
1114
|
+
<td>${num(missing, "tag-err")}</td>
|
|
1115
|
+
</tr>`;
|
|
1116
|
+
}).join("");
|
|
1117
|
+
}
|
|
1118
|
+
const html = `<div class="toolbar">Dashboard</div>
|
|
1119
|
+
${cards}
|
|
1120
|
+
<div class="section">
|
|
1121
|
+
<div class="section-head">Locales</div>
|
|
1122
|
+
<table class="data">
|
|
1123
|
+
<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>
|
|
1124
|
+
<tbody>${localeRows}</tbody>
|
|
1125
|
+
</table>
|
|
1126
|
+
</div>
|
|
1127
|
+
<div class="section">
|
|
1128
|
+
<div class="section-head">Types</div>
|
|
1129
|
+
<table class="data">
|
|
1130
|
+
<thead><tr><th>Type</th><th>ID</th><th>EN docs</th><th>Stale</th><th>Missing</th></tr></thead>
|
|
1131
|
+
<tbody>${typeRows}</tbody>
|
|
1132
|
+
</table>
|
|
1133
|
+
</div>`;
|
|
1134
|
+
return c.html(renderLayout("Dashboard", html, project));
|
|
1135
|
+
});
|
|
998
1136
|
app.get("/type/:id", (c) => {
|
|
999
1137
|
const typeId = c.req.param("id");
|
|
1000
1138
|
const type = project.getType(typeId);
|