mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
116 lines
3.9 KiB
JavaScript
116 lines
3.9 KiB
JavaScript
|
|
/**
|
|||
|
|
* 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 ` |