Files
notesnook/docs/help/scripts/check-strings.mjs

116 lines
3.9 KiB
JavaScript
Raw Permalink Normal View History

docs(help): rebuild the help site with VitePress (#10169) * docs: new help built with vitepress. * docs: improve docs home ui Added a Go to Docs button on homepage Added a searchbar for directly searching what you are looking for in the docs * docs(help): wire up with build system & setup proper ci * Clean up new documentation (#10174) * Docs: Cleanup pass 1 Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> * docs: cleanup pass 2 Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> * docs: Fix vscode's manglement that I missed. Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> * Update docs/help/contents/plans-and-limits.md Co-authored-by: Abdullah Atta <thecodrr@protonmail.com> Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> * Update docs/help/contents/rich-text-editor/outline-lists.md Co-authored-by: Abdullah Atta <thecodrr@protonmail.com> Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> * docs: Remove self-hosting guide from sidebar, and comments for reviewer. Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> --------- Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> Co-authored-by: Abdullah Atta <thecodrr@protonmail.com> * docs: some fixes * ci: do not publish help on push * docs(help): improve regional pricing & free trials * docs: improve sidebar * docs: some more fixes after re-review * docs: improve search and nav * docs: a few more fixes * docs: fix tabs formatting compat with prettier * docs: fix tabs formatting in various places * docs: show correct plus button image for mobile * docs: disable notesnook self hosting docs * docs: update help docs with fixes --------- Signed-off-by: Chloe Oletto <NeedsChloesure@riseup.net> Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co> Co-authored-by: Chloe Oletto <NeedsChloesure@riseup.net> Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2026-08-14 08:57:51 +05:00
/**
* Keeps quoted UI labels in the docs tied to the app's string catalogue.
*
* npm run strings report only
* npm run strings -- --fix rewrite unambiguous labels into `{{key}}`
*
* A label written as `Archive` is a snapshot that goes stale silently. Written
* as `{{archive}}` it is resolved from packages/intl at build time, so renaming
* the string in the app updates the docs.
*
* The script never invents strings: it only matches text that an existing
* zero-argument key already produces.
*/
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { pathToFileURL } from "node:url";
const HELP = new URL("..", import.meta.url).pathname;
const CONTENTS = join(HELP, "contents");
const FIX = process.argv.includes("--fix");
const { buildReverseIndex, resolveString } = await import(
pathToFileURL(join(HELP, ".vitepress/strings.mts")).href
).catch(async () => {
// .mts needs a TS-aware loader; fall back to the transpiled copy VitePress uses.
return await import(pathToFileURL(join(HELP, ".vitepress/strings.mjs")).href);
});
const byValue = buildReverseIndex();
function walk(dir, out = []) {
for (const entry of readdirSync(dir)) {
if (["public", "_versions"].includes(entry) || /^v\d/.test(entry)) continue;
const p = join(dir, entry);
if (statSync(p).isDirectory()) walk(p, out);
else if (p.endsWith(".md")) out.push(p);
}
return out;
}
/** Split frontmatter off; we only touch the body. */
function splitFrontmatter(text) {
if (!text.startsWith("---\n")) return ["", text];
const end = text.indexOf("\n---\n", 3);
return end === -1 ? ["", text] : [text.slice(0, end + 5), text.slice(end + 5)];
}
const stats = {
files: 0,
alreadyKeys: 0,
converted: 0,
ambiguous: new Map(),
unmatched: new Map()
};
for (const file of walk(CONTENTS)) {
const original = readFileSync(file, "utf8");
const [frontmatter, body] = splitFrontmatter(original);
const rel = relative(CONTENTS, file);
stats.alreadyKeys += (body.match(/\{\{[A-Za-z][A-Za-z0-9_]*\}\}/g) || []).length;
let changed = false;
const fences = [];
// Protect fenced code blocks from rewriting.
const masked = body.replace(/```[\s\S]*?```/g, (m) => {
fences.push(m);
return `FENCE${fences.length - 1}`;
});
const next = masked.replace(/`([^`\n]{2,60})`/g, (match, label) => {
if (label.startsWith("{{")) return match;
const keys = byValue.get(label);
if (!keys) {
if (/^[A-Z]/.test(label) && label.split(" ").length <= 4)
stats.unmatched.set(label, (stats.unmatched.get(label) || new Set()).add(rel));
return match;
}
if (keys.length > 1) {
stats.ambiguous.set(label, keys);
return match;
}
changed = true;
stats.converted++;
return `\`{{${keys[0]}}}\``;
});
if (changed && FIX) {
const restored = next.replace(/FENCE(\d+)/g, (_, i) => fences[Number(i)]);
writeFileSync(file, frontmatter + restored);
stats.files++;
}
}
console.log(
FIX
? `Rewrote ${stats.converted} labels into string keys across ${stats.files} files.`
: `${stats.converted} labels could be rewritten as string keys (run with --fix).`
);
console.log(`${stats.alreadyKeys} labels are already keys.`);
if (stats.ambiguous.size) {
console.log(`\n${stats.ambiguous.size} labels map to more than one key — left alone:`);
for (const [label, keys] of [...stats.ambiguous].slice(0, 15))
console.log(` "${label}" -> ${keys.join(", ")}`);
}
if (stats.unmatched.size) {
const sorted = [...stats.unmatched].sort((a, b) => b[1].size - a[1].size).slice(0, 20);
console.log(
`\n${stats.unmatched.size} capitalised labels have no matching string (they may be third-party UI, or genuinely absent from the catalogue):`
);
for (const [label, files] of sorted)
console.log(` "${label}" — ${[...files].slice(0, 2).join(", ")}`);
}