prosebook 0.2.0 → 0.4.0
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/README.md +345 -29
- package/dist/bun.d.ts +34 -0
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +98 -0
- package/dist/bun.js.map +1 -0
- package/dist/bundle-CpUIWb9j.d.ts +6 -0
- package/dist/bundle-CpUIWb9j.d.ts.map +1 -0
- package/dist/bundle-DboItAre.js +111 -0
- package/dist/bundle-DboItAre.js.map +1 -0
- package/dist/cli.js +51 -56
- package/dist/cli.js.map +1 -1
- package/dist/compile-ClK9htKr.d.ts +16 -0
- package/dist/compile-ClK9htKr.d.ts.map +1 -0
- package/dist/{config-_4HfnJQu.d.ts → config-CzaJJQ60.d.ts} +75 -2
- package/dist/config-CzaJJQ60.d.ts.map +1 -0
- package/dist/{config-D--RITk2.js → config-nWZf9QmF.js} +42 -212
- package/dist/config-nWZf9QmF.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +1 -1
- package/dist/{copy-DXnzXsJa.d.ts → copy-B3V_QRkT.d.ts} +20 -7
- package/dist/copy-B3V_QRkT.d.ts.map +1 -0
- package/dist/copy-Bo_FIoLy.js +154 -0
- package/dist/copy-Bo_FIoLy.js.map +1 -0
- package/dist/endpoints-BgG_5Vpm.js +96 -0
- package/dist/endpoints-BgG_5Vpm.js.map +1 -0
- package/dist/generate-BW3N8EGb.js +5488 -0
- package/dist/generate-BW3N8EGb.js.map +1 -0
- package/dist/helpers-BWGcD0kS.js +96 -0
- package/dist/helpers-BWGcD0kS.js.map +1 -0
- package/dist/icu-Dej0mNsF.js +218 -0
- package/dist/icu-Dej0mNsF.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -188
- package/dist/inspector.d.ts +4 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +7 -2
- package/dist/inspector.js.map +1 -1
- package/dist/locale-DpQA1pqH.js +70 -0
- package/dist/locale-DpQA1pqH.js.map +1 -0
- package/dist/next-loader.d.ts +11 -0
- package/dist/next-loader.d.ts.map +1 -0
- package/dist/next-loader.js +17 -0
- package/dist/next-loader.js.map +1 -0
- package/dist/next.d.ts +30 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +160 -0
- package/dist/next.js.map +1 -0
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/server.d.ts +20 -0
- package/dist/react/server.d.ts.map +1 -0
- package/dist/react/server.js +34 -0
- package/dist/react/server.js.map +1 -0
- package/dist/{sarif-CI8HOokm.js → sarif-B5nrMqrT.js} +103 -87
- package/dist/sarif-B5nrMqrT.js.map +1 -0
- package/dist/scan-Bx0_yDSi.js +387 -0
- package/dist/scan-Bx0_yDSi.js.map +1 -0
- package/dist/tools.d.ts +83 -90
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +5 -4
- package/dist/vite.d.ts +3 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +29 -109
- package/dist/vite.js.map +1 -1
- package/package.json +37 -4
- package/src/bun.ts +132 -0
- package/src/cli.ts +60 -58
- package/src/index.ts +1 -1
- package/src/inspector.ts +8 -3
- package/src/next-loader.ts +16 -0
- package/src/next.ts +188 -0
- package/src/react/index.tsx +6 -6
- package/src/react/server.ts +37 -0
- package/src/runtime/copy.ts +22 -3
- package/src/runtime/helpers.ts +54 -18
- package/src/runtime/locale.ts +19 -0
- package/src/runtime/types.ts +21 -6
- package/src/tools/bundle.ts +131 -0
- package/src/tools/catalog.ts +2 -2
- package/src/tools/compile.ts +60 -23
- package/src/tools/config.ts +57 -0
- package/src/tools/endpoints.ts +121 -0
- package/src/tools/entry.ts +77 -0
- package/src/tools/generate.ts +2 -0
- package/src/tools/icu.ts +63 -7
- package/src/tools/index.ts +6 -3
- package/src/tools/lock.ts +7 -0
- package/src/tools/mf1.ts +21 -7
- package/src/tools/mf2.ts +249 -57
- package/src/tools/migrate.ts +3 -2
- package/src/tools/native/android.ts +201 -0
- package/src/tools/native/apple.ts +139 -0
- package/src/tools/native/emit.ts +87 -0
- package/src/tools/native/index.ts +48 -0
- package/src/tools/native/kotlin.ts +269 -0
- package/src/tools/native/model.ts +292 -0
- package/src/tools/native/number.ts +210 -0
- package/src/tools/native/plurals.ts +103 -0
- package/src/tools/native/printf.ts +95 -0
- package/src/tools/native/rust.ts +367 -0
- package/src/tools/native/swift.ts +326 -0
- package/src/tools/syntax.ts +3 -3
- package/src/tools/todo.ts +118 -0
- package/src/vite.ts +28 -114
- package/dist/config-D--RITk2.js.map +0 -1
- package/dist/config-_4HfnJQu.d.ts.map +0 -1
- package/dist/copy-DXnzXsJa.d.ts.map +0 -1
- package/dist/helpers-BPuPGjQP.js +0 -61
- package/dist/helpers-BPuPGjQP.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/sarif-CI8HOokm.js.map +0 -1
- package/dist/scan-Ch2BfIUD.js +0 -2561
- package/dist/scan-Ch2BfIUD.js.map +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { c as unseenReads, d as localeModule, i as keyRefs, l as compileLocale, r as isReferenced, s as sourceFiles, t as READERS } from "./scan-Bx0_yDSi.js";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { existsSync, readFileSync, watch } from "node:fs";
|
|
5
|
+
// src/tools/bundle.ts
|
|
6
|
+
/**
|
|
7
|
+
* What the bundler integrations share: which messages a build ships, the reads that would show a key
|
|
8
|
+
* once their message is pruned, and the code a dev server appends so an edit swaps messages in place.
|
|
9
|
+
*
|
|
10
|
+
* Vite sees every module before it writes a chunk, so it prunes after the fact (`vite.ts`). Next's
|
|
11
|
+
* loaders and Bun's `onLoad` hand over a locale module before the code that reads it has been seen,
|
|
12
|
+
* so they read the source under `scan.dirs` first (`scanSources`) and load the locale already pruned.
|
|
13
|
+
*/
|
|
14
|
+
/** Whether a build keeps a key: some module names it, or `keep` lists it, exactly or as `prefix.*`. */
|
|
15
|
+
function keeper(refs, keep = []) {
|
|
16
|
+
return (key) => isReferenced(key, refs) || keep.some((k) => k.endsWith(".*") ? key.startsWith(k.slice(0, -1)) : k === key);
|
|
17
|
+
}
|
|
18
|
+
/** The unseen reads that are a copy's: a name only counts once some module binds a reader to it. */
|
|
19
|
+
function unseenOf(reads) {
|
|
20
|
+
const all = [...reads];
|
|
21
|
+
const readers = /* @__PURE__ */ new Set([...READERS, ...all.flatMap((r) => [...r.aliases])]);
|
|
22
|
+
return all.flatMap((r) => r.unseen).filter((u) => u.name === "Text" || readers.has(u.name));
|
|
23
|
+
}
|
|
24
|
+
/** The error a strict build fails with, each read at its path from `root`. */
|
|
25
|
+
function unseenMessage(unseen, root) {
|
|
26
|
+
const at = (file) => relative(root, file).replaceAll("\\", "/");
|
|
27
|
+
return [
|
|
28
|
+
`${unseen.length} read(s) whose key the build can't see, so their messages would be pruned and the key shown instead:`,
|
|
29
|
+
...unseen.map((u) => ` ${at(u.file)}:${u.line}:${u.column} ${u.code}`),
|
|
30
|
+
"Write each key at the read, as a literal or `area.${id}`; or, where every key it reads is written elsewhere or listed in `keep`, mark the line `prosebook-ignore`. `prune: \"loose\"` turns this check off."
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Every key the app's source names and, with `strict`, its unseen reads. The generated modules are
|
|
35
|
+
* left out, since they name every key.
|
|
36
|
+
*/
|
|
37
|
+
function scanSources(config, strict) {
|
|
38
|
+
const refs = {
|
|
39
|
+
exact: /* @__PURE__ */ new Set(),
|
|
40
|
+
prefixes: /* @__PURE__ */ new Set()
|
|
41
|
+
};
|
|
42
|
+
const reads = [];
|
|
43
|
+
for (const file of sourceFiles(config.scan.dirs, config.scan.extensions, [config.out])) {
|
|
44
|
+
const code = readFileSync(file, "utf8");
|
|
45
|
+
keyRefs(code, refs);
|
|
46
|
+
if (strict) reads.push(unseenReads(file, code));
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
refs,
|
|
50
|
+
unseen: unseenOf(reads)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Calls `change` once a burst of edits to the catalog or its translations settles. Returns the stop. */
|
|
54
|
+
function watchCatalog(config, change) {
|
|
55
|
+
let timer;
|
|
56
|
+
const watchers = [config.catalog, config.locales].filter((dir) => existsSync(dir)).map((dir) => watch(dir, { recursive: true }, (_event, file) => {
|
|
57
|
+
if (file === null || !file.endsWith(".yaml")) return;
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
timer = setTimeout(change, 50);
|
|
60
|
+
}));
|
|
61
|
+
return () => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
for (const w of watchers) w.close();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** A locale's module holding only the keys `kept`, as JavaScript, for a bundler to load in its place. */
|
|
67
|
+
function prunedModule(entries, kept) {
|
|
68
|
+
return localeModule(compileLocale(entries, kept), "// Pruned by prosebook: only the messages the code names.", false);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Appended to a locale module in dev. Accepts its own update and hands the new messages to every
|
|
72
|
+
* copy (`HotHook`); a page loaded before the hooks existed reloads instead.
|
|
73
|
+
*/
|
|
74
|
+
const hotLocale = (locale) => `
|
|
75
|
+
if (import.meta.hot) {
|
|
76
|
+
import.meta.hot.accept((next) => {
|
|
77
|
+
const hot = globalThis.__PROSEBOOK_HOT__;
|
|
78
|
+
if (next === undefined || hot === undefined) return import.meta.hot.invalidate();
|
|
79
|
+
for (const update of hot) update(${JSON.stringify(locale)}, next.default);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
/** Appended to the index in dev: its doc comments change with every edit; only a new locale reloads. */
|
|
84
|
+
const HOT_INDEX = `
|
|
85
|
+
if (import.meta.hot) {
|
|
86
|
+
import.meta.hot.accept((next) => {
|
|
87
|
+
if (next === undefined || next.catalog.locales.join() !== catalog.locales.join()) import.meta.hot.invalidate();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
/**
|
|
92
|
+
* A locale module in dev under a bundler that runs it again when it changes, as Turbopack's Fast
|
|
93
|
+
* Refresh does, but has no `import.meta.hot`: each run hands its messages to every copy. The first
|
|
94
|
+
* run finds no copy holding the locale yet, so it changes nothing.
|
|
95
|
+
*/
|
|
96
|
+
const notifyingLocale = (code, locale) => `${code.replace(/^export default /m, "const messages = ")}
|
|
97
|
+
export default messages;
|
|
98
|
+
globalThis.__PROSEBOOK_HOT__?.forEach((update) => update(${JSON.stringify(locale)}, messages));
|
|
99
|
+
`;
|
|
100
|
+
/** Prepended to the index in dev, so the set every copy adds itself to exists before any copy does. */
|
|
101
|
+
const HOT_SET = "globalThis.__PROSEBOOK_HOT__ ??= new Set();\n";
|
|
102
|
+
/**
|
|
103
|
+
* A module of prosebook's own beside the one at `url`, which passes its `import.meta.url`: the source
|
|
104
|
+
* when run from source, the built file in dist.
|
|
105
|
+
*/
|
|
106
|
+
const beside = (url, name) => fileURLToPath(new URL(`./${name}${url.endsWith(".ts") ? ".ts" : ".js"}`, url)).replaceAll("\\", "/");
|
|
107
|
+
/** Code that starts the inspector (`inspector.ts`, imported from `path`) on the page, its endpoints under `base`. */
|
|
108
|
+
const installInspector = (path, base) => `import { install as installInspector } from ${JSON.stringify(path)};\ninstallInspector(${JSON.stringify({ base })});\n`;
|
|
109
|
+
export { installInspector as a, prunedModule as c, unseenOf as d, watchCatalog as f, hotLocale as i, scanSources as l, HOT_SET as n, keeper as o, beside as r, notifyingLocale as s, HOT_INDEX as t, unseenMessage as u };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=bundle-DboItAre.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-DboItAre.js","names":[],"sources":["../src/tools/bundle.ts"],"sourcesContent":["/**\n * What the bundler integrations share: which messages a build ships, the reads that would show a key\n * once their message is pruned, and the code a dev server appends so an edit swaps messages in place.\n *\n * Vite sees every module before it writes a chunk, so it prunes after the fact (`vite.ts`). Next's\n * loaders and Bun's `onLoad` hand over a locale module before the code that reads it has been seen,\n * so they read the source under `scan.dirs` first (`scanSources`) and load the locale already pruned.\n */\nimport { existsSync, readFileSync, watch } from 'node:fs';\nimport { relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { compileLocale, localeModule, type Entry } from './compile.ts';\nimport type { ResolvedConfig } from './config.ts';\nimport { isReferenced, keyRefs, READERS, sourceFiles, unseenReads, type KeyRefs, type Reads, type UnseenRead } from './scan.ts';\n\n/** How a build prunes: `strict` also fails on a read whose key it can't see. */\nexport type Prune = 'strict' | 'loose' | false;\n\n/** Whether a build keeps a key: some module names it, or `keep` lists it, exactly or as `prefix.*`. */\nexport function keeper(refs: KeyRefs, keep: string[] = []): (key: string) => boolean {\n return (key) => isReferenced(key, refs) || keep.some((k) => (k.endsWith('.*') ? key.startsWith(k.slice(0, -1)) : k === key));\n}\n\n/** The unseen reads that are a copy's: a name only counts once some module binds a reader to it. */\nexport function unseenOf(reads: Iterable<Reads>): UnseenRead[] {\n const all = [...reads];\n const readers = new Set([...READERS, ...all.flatMap((r) => [...r.aliases])]);\n return all.flatMap((r) => r.unseen).filter((u) => u.name === 'Text' || readers.has(u.name));\n}\n\n/** The error a strict build fails with, each read at its path from `root`. */\nexport function unseenMessage(unseen: UnseenRead[], root: string): string {\n const at = (file: string) => relative(root, file).replaceAll('\\\\', '/');\n return [\n `${unseen.length} read(s) whose key the build can't see, so their messages would be pruned and the key shown instead:`,\n ...unseen.map((u) => ` ${at(u.file)}:${u.line}:${u.column} ${u.code}`),\n 'Write each key at the read, as a literal or `area.${id}`; or, where every key it reads is written elsewhere or listed in `keep`, mark the line `prosebook-ignore`. `prune: \"loose\"` turns this check off.',\n ].join('\\n');\n}\n\nexport interface SourceScan {\n refs: KeyRefs;\n unseen: UnseenRead[];\n}\n\n/**\n * Every key the app's source names and, with `strict`, its unseen reads. The generated modules are\n * left out, since they name every key.\n */\nexport function scanSources(config: ResolvedConfig, strict: boolean): SourceScan {\n const refs: KeyRefs = { exact: new Set(), prefixes: new Set() };\n const reads: Reads[] = [];\n for (const file of sourceFiles(config.scan.dirs, config.scan.extensions, [config.out])) {\n const code = readFileSync(file, 'utf8');\n keyRefs(code, refs);\n if (strict) reads.push(unseenReads(file, code));\n }\n return { refs, unseen: unseenOf(reads) };\n}\n\n/** Calls `change` once a burst of edits to the catalog or its translations settles. Returns the stop. */\nexport function watchCatalog(config: ResolvedConfig, change: () => void): () => void {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const watchers = [config.catalog, config.locales]\n .filter((dir) => existsSync(dir))\n .map((dir) =>\n watch(dir, { recursive: true }, (_event, file) => {\n if (file === null || !file.endsWith('.yaml')) return;\n clearTimeout(timer);\n timer = setTimeout(change, 50);\n }),\n );\n return () => {\n clearTimeout(timer);\n for (const w of watchers) w.close();\n };\n}\n\n/** A locale's module holding only the keys `kept`, as JavaScript, for a bundler to load in its place. */\nexport function prunedModule(entries: Entry[], kept: (key: string) => boolean): string {\n return localeModule(compileLocale(entries, kept), '// Pruned by prosebook: only the messages the code names.', false);\n}\n\n/**\n * Appended to a locale module in dev. Accepts its own update and hands the new messages to every\n * copy (`HotHook`); a page loaded before the hooks existed reloads instead.\n */\nexport const hotLocale = (locale: string) => `\nif (import.meta.hot) {\n import.meta.hot.accept((next) => {\n const hot = globalThis.__PROSEBOOK_HOT__;\n if (next === undefined || hot === undefined) return import.meta.hot.invalidate();\n for (const update of hot) update(${JSON.stringify(locale)}, next.default);\n });\n}\n`;\n\n/** Appended to the index in dev: its doc comments change with every edit; only a new locale reloads. */\nexport const HOT_INDEX = `\nif (import.meta.hot) {\n import.meta.hot.accept((next) => {\n if (next === undefined || next.catalog.locales.join() !== catalog.locales.join()) import.meta.hot.invalidate();\n });\n}\n`;\n\n/**\n * A locale module in dev under a bundler that runs it again when it changes, as Turbopack's Fast\n * Refresh does, but has no `import.meta.hot`: each run hands its messages to every copy. The first\n * run finds no copy holding the locale yet, so it changes nothing.\n */\nexport const notifyingLocale = (code: string, locale: string) =>\n `${code.replace(/^export default /m, 'const messages = ')}\nexport default messages;\nglobalThis.__PROSEBOOK_HOT__?.forEach((update) => update(${JSON.stringify(locale)}, messages));\n`;\n\n/** Prepended to the index in dev, so the set every copy adds itself to exists before any copy does. */\nexport const HOT_SET = 'globalThis.__PROSEBOOK_HOT__ ??= new Set();\\n';\n\n/**\n * A module of prosebook's own beside the one at `url`, which passes its `import.meta.url`: the source\n * when run from source, the built file in dist.\n */\nexport const beside = (url: string, name: string) =>\n fileURLToPath(new URL(`./${name}${url.endsWith('.ts') ? '.ts' : '.js'}`, url)).replaceAll('\\\\', '/');\n\n/** Code that starts the inspector (`inspector.ts`, imported from `path`) on the page, its endpoints under `base`. */\nexport const installInspector = (path: string, base: string) =>\n `import { install as installInspector } from ${JSON.stringify(path)};\\ninstallInspector(${JSON.stringify({ base })});\\n`;\n"],"mappings":";;;;;;;;;;;;;;AAoBA,SAAgB,OAAO,MAAe,OAAiB,CAAC,GAA6B;CACnF,QAAQ,QAAQ,aAAa,KAAK,IAAI,KAAK,KAAK,MAAM,MAAO,EAAE,SAAS,IAAI,IAAI,IAAI,WAAW,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,MAAM,GAAI;AAC7H;;AAGA,SAAgB,SAAS,OAAsC;CAC7D,MAAM,MAAM,CAAC,GAAG,KAAK;CACrB,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;CAC3E,OAAO,IAAI,SAAS,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,MAAM,EAAE,SAAS,UAAU,QAAQ,IAAI,EAAE,IAAI,CAAC;AAC5F;;AAGA,SAAgB,cAAc,QAAsB,MAAsB;CACxE,MAAM,MAAM,SAAiB,SAAS,MAAM,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;CACtE,OAAO;EACL,GAAG,OAAO,OAAO;EACjB,GAAG,OAAO,KAAK,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,MAAM;EACvE;CACF,CAAC,CAAC,KAAK,IAAI;AACb;;;;;AAWA,SAAgB,YAAY,QAAwB,QAA6B;CAC/E,MAAM,OAAgB;EAAE,uBAAO,IAAI,IAAI;EAAG,0BAAU,IAAI,IAAI;CAAE;CAC9D,MAAM,QAAiB,CAAC;CACxB,KAAK,MAAM,QAAQ,YAAY,OAAO,KAAK,MAAM,OAAO,KAAK,YAAY,CAAC,OAAO,GAAG,CAAC,GAAG;EACtF,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,QAAQ,MAAM,IAAI;EAClB,IAAI,QAAQ,MAAM,KAAK,YAAY,MAAM,IAAI,CAAC;CAChD;CACA,OAAO;EAAE;EAAM,QAAQ,SAAS,KAAK;CAAE;AACzC;;AAGA,SAAgB,aAAa,QAAwB,QAAgC;CACnF,IAAI;CACJ,MAAM,WAAW,CAAC,OAAO,SAAS,OAAO,OAAO,CAAC,CAC9C,QAAQ,QAAQ,WAAW,GAAG,CAAC,CAAC,CAChC,KAAK,QACJ,MAAM,KAAK,EAAE,WAAW,KAAK,IAAI,QAAQ,SAAS;EAChD,IAAI,SAAS,QAAQ,CAAC,KAAK,SAAS,OAAO,GAAG;EAC9C,aAAa,KAAK;EAClB,QAAQ,WAAW,QAAQ,EAAE;CAC/B,CAAC,CACH;CACF,aAAa;EACX,aAAa,KAAK;EAClB,KAAK,MAAM,KAAK,UAAU,EAAE,MAAM;CACpC;AACF;;AAGA,SAAgB,aAAa,SAAkB,MAAwC;CACrF,OAAO,aAAa,cAAc,SAAS,IAAI,GAAG,6DAA6D,KAAK;AACtH;;;;;AAMA,MAAa,aAAa,WAAmB;;;;;uCAKN,KAAK,UAAU,MAAM,EAAE;;;;;AAM9D,MAAa,YAAY;;;;;;;;;;;;AAazB,MAAa,mBAAmB,MAAc,WAC5C,GAAG,KAAK,QAAQ,qBAAqB,mBAAmB,EAAE;;2DAED,KAAK,UAAU,MAAM,EAAE;;;AAIlF,MAAa,UAAU;;;;;AAMvB,MAAa,UAAU,KAAa,SAClC,cAAc,IAAI,IAAI,KAAK,OAAO,IAAI,SAAS,KAAK,IAAI,QAAQ,SAAS,GAAG,CAAC,CAAC,CAAC,WAAW,MAAM,GAAG;;AAGrG,MAAa,oBAAoB,MAAc,SAC7C,+CAA+C,KAAK,UAAU,IAAI,EAAE,sBAAsB,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE"}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { f as watchCatalog } from "./bundle-DboItAre.js";
|
|
3
|
+
import { D as whereOf, O as SYNTAX_NAME, _ as applyFixes, a as render, l as changeOf, n as generate, w as entryLocator, x as CatalogError, y as importTranslations } from "./generate-BW3N8EGb.js";
|
|
4
|
+
import { p as lint, r as loadConfig, t as CONFIG_FILES } from "./config-nWZf9QmF.js";
|
|
5
|
+
import { a as applyExtract, c as planExtract, i as planMigrate, n as todoOf, r as applyMigrate, t as SarifReport } from "./sarif-B5nrMqrT.js";
|
|
6
|
+
import { o as scan, r as isReferenced, s as sourceFiles } from "./scan-Bx0_yDSi.js";
|
|
5
7
|
import { join, relative, resolve } from "node:path";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
7
|
-
import { stringify } from "yaml";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { parseArgs } from "node:util";
|
|
9
10
|
// src/cli.ts
|
|
10
11
|
/**
|
|
@@ -23,6 +24,9 @@ Commands:
|
|
|
23
24
|
status Translation coverage and stale translations per locale (--strict, --verbose)
|
|
24
25
|
accept <locale> [keys] Mark stale translations as still right (--all for every stale one)
|
|
25
26
|
todo <locale> Print the missing and stale entries of a locale, ready to translate
|
|
27
|
+
(--prompt: as a prompt for a model, saying how to reply)
|
|
28
|
+
import <locale> [file] Write translations from todo's YAML filled in, or a model's reply to --prompt
|
|
29
|
+
(stdin without a file); each is checked first, and one that fails is left out
|
|
26
30
|
scan Find hardcoded text in JSX and catalog keys no code reads (--strict)
|
|
27
31
|
extract [paths] Move hardcoded JSX text into the catalog and rewrite the code to read it
|
|
28
32
|
(dry run unless --write; --area <name> puts every entry in one area)
|
|
@@ -68,6 +72,7 @@ async function main(argv) {
|
|
|
68
72
|
all: { type: "boolean" },
|
|
69
73
|
write: { type: "boolean" },
|
|
70
74
|
fix: { type: "boolean" },
|
|
75
|
+
prompt: { type: "boolean" },
|
|
71
76
|
area: { type: "string" },
|
|
72
77
|
format: { type: "string" },
|
|
73
78
|
help: {
|
|
@@ -110,7 +115,8 @@ async function main(argv) {
|
|
|
110
115
|
code = runStatus(config, values.strict === true, values.verbose === true, report);
|
|
111
116
|
break;
|
|
112
117
|
case "accept": return runAccept(config, rest, values.all === true);
|
|
113
|
-
case "todo": return runTodo(config, rest[0]);
|
|
118
|
+
case "todo": return runTodo(config, rest[0], values.prompt === true);
|
|
119
|
+
case "import": return runImport(config, rest[0], rest[1]);
|
|
114
120
|
case "scan":
|
|
115
121
|
code = runScan(config, values.strict === true, report);
|
|
116
122
|
break;
|
|
@@ -194,15 +200,7 @@ function watchMode(config) {
|
|
|
194
200
|
}
|
|
195
201
|
};
|
|
196
202
|
regenerate();
|
|
197
|
-
|
|
198
|
-
for (const dir of [config.catalog, config.locales]) {
|
|
199
|
-
if (!existsSync(dir)) continue;
|
|
200
|
-
watch(dir, { recursive: true }, (_event, file) => {
|
|
201
|
-
if (file === null || !file.endsWith(".yaml")) return;
|
|
202
|
-
clearTimeout(timer);
|
|
203
|
-
timer = setTimeout(regenerate, 50);
|
|
204
|
-
});
|
|
205
|
-
}
|
|
203
|
+
watchCatalog(config, regenerate);
|
|
206
204
|
out("prosebook: watching the catalog for changes");
|
|
207
205
|
return new Promise(() => {});
|
|
208
206
|
}
|
|
@@ -222,15 +220,7 @@ function runLint(config, report, fix = false) {
|
|
|
222
220
|
findings = lint(catalog.messages, config);
|
|
223
221
|
}
|
|
224
222
|
}
|
|
225
|
-
const
|
|
226
|
-
const placed = findings.map((f) => ({
|
|
227
|
-
...f,
|
|
228
|
-
...locate(f.file, f.key, f.at?.start, config.source)
|
|
229
|
-
}));
|
|
230
|
-
for (const f of placed) {
|
|
231
|
-
const severity = f.severity === "error" ? "error" : "warn ";
|
|
232
|
-
err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === void 0 ? "" : " (fixable)"}`);
|
|
233
|
-
}
|
|
223
|
+
const placed = printFindings(config, findings);
|
|
234
224
|
if (report !== void 0) {
|
|
235
225
|
for (const [name, { rule }] of Object.entries(config.lint.rules)) report.rule(name, rule.description);
|
|
236
226
|
report.rule("allow", RULES.allow);
|
|
@@ -252,6 +242,19 @@ function runLint(config, report, fix = false) {
|
|
|
252
242
|
}
|
|
253
243
|
return errors > 0 ? 1 : 0;
|
|
254
244
|
}
|
|
245
|
+
/** Each finding on its line, placed at its entry's words. */
|
|
246
|
+
function printFindings(config, findings) {
|
|
247
|
+
const locate = entryLocator(config.root);
|
|
248
|
+
const placed = findings.map((f) => ({
|
|
249
|
+
...f,
|
|
250
|
+
...locate(f.file, f.key, f.at?.start, config.source)
|
|
251
|
+
}));
|
|
252
|
+
for (const f of placed) {
|
|
253
|
+
const severity = f.severity === "error" ? "error" : "warn ";
|
|
254
|
+
err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === void 0 ? "" : " (fixable)"}`);
|
|
255
|
+
}
|
|
256
|
+
return placed;
|
|
257
|
+
}
|
|
255
258
|
function missingKeys(catalog, locale) {
|
|
256
259
|
return catalog.messages.filter((m) => !m.translations.has(locale)).map((m) => m.key);
|
|
257
260
|
}
|
|
@@ -324,44 +327,36 @@ function runAccept(config, args, all) {
|
|
|
324
327
|
out(`prosebook: accepted ${[...accepting].filter((k) => before.has(k)).length} translation(s) in ${locale}`);
|
|
325
328
|
return 0;
|
|
326
329
|
}
|
|
327
|
-
|
|
328
|
-
function changeOf(stale) {
|
|
329
|
-
if (stale.was === void 0) return "the earlier text isn't recorded (the lock predates prosebook 0.2)";
|
|
330
|
-
return formatDiff(wordDiff(stale.was, stale.now)).replaceAll("\n", " ");
|
|
331
|
-
}
|
|
332
|
-
function runTodo(config, locale) {
|
|
330
|
+
function runTodo(config, locale, prompt) {
|
|
333
331
|
if (locale === void 0) {
|
|
334
|
-
err("Usage: prosebook todo <locale>");
|
|
332
|
+
err("Usage: prosebook todo <locale> [--prompt]");
|
|
335
333
|
return 1;
|
|
336
334
|
}
|
|
337
335
|
const { catalog, stale } = render(config);
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (m.context) lines.push(`# ${m.context}`);
|
|
348
|
-
for (const [arg, said] of m.argDocs) lines.push(`# {${arg}}: ${said}`);
|
|
349
|
-
lines.push(`# ${config.source}: ${m.text.replaceAll("\n", " ")}`);
|
|
350
|
-
const change = staleKeys.get(m.key);
|
|
351
|
-
if (change !== void 0) lines.push(`# stale, the source changed: ${changeOf(change)}`);
|
|
352
|
-
lines.push(stringify({ [m.key]: current?.text ?? "" }, { lineWidth: 0 }).trimEnd());
|
|
353
|
-
byArea.set(m.area, lines);
|
|
354
|
-
}
|
|
355
|
-
if (count === 0) {
|
|
356
|
-
out(`# ${locale} is complete`);
|
|
357
|
-
return 0;
|
|
336
|
+
const todo = todoOf(config, catalog, stale.get(locale) ?? /* @__PURE__ */ new Map(), locale, prompt);
|
|
337
|
+
out(todo.text);
|
|
338
|
+
if (prompt && todo.count > 0) err(`\nHand this to a model, save its reply, and \`prosebook import ${locale} <reply>\`.`);
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
341
|
+
function runImport(config, locale, file) {
|
|
342
|
+
if (locale === void 0 || (file === void 0 || file === "-") && process.stdin.isTTY) {
|
|
343
|
+
err("Usage: prosebook import <locale> <file>, or the reply on stdin");
|
|
344
|
+
return 1;
|
|
358
345
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
346
|
+
const reply = readFileSync(file === void 0 || file === "-" ? 0 : resolve(file), "utf8");
|
|
347
|
+
const { written, unchanged, problems } = importTranslations(config, locale, reply);
|
|
348
|
+
reportProblems(config, problems);
|
|
349
|
+
if (written.length + unchanged.length > 0) {
|
|
350
|
+
const stale = render(config).stale.get(locale) ?? /* @__PURE__ */ new Map();
|
|
351
|
+
const kept = unchanged.filter((key) => stale.has(key));
|
|
352
|
+
const { catalog } = generate(config, { accept: /* @__PURE__ */ new Map([[locale, new Set(kept)]]) });
|
|
353
|
+
out(`prosebook: imported ${written.length} translation(s) into ${locale}${kept.length > 0 ? `, and accepted ${kept.length} stale one(s) as still right` : ""}`);
|
|
354
|
+
const imported = new Set(written);
|
|
355
|
+
printFindings(config, lint(catalog.messages.filter((m) => imported.has(m.key)), config).filter((f) => f.locale === locale));
|
|
363
356
|
}
|
|
364
|
-
return 0;
|
|
357
|
+
if (problems.length === 0) return 0;
|
|
358
|
+
err(`\n${problems.length} left out: fix them, or hand these lines back to the model, and import again.`);
|
|
359
|
+
return 1;
|
|
365
360
|
}
|
|
366
361
|
function runScan(config, strict, report) {
|
|
367
362
|
const { catalog } = render(config);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `prosebook` command. Each subcommand is a function of the resolved config; `check` runs the\n * ones CI needs in one go.\n */\nimport { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { stringify } from 'yaml';\n\nimport { CatalogError, entryLocator, whereOf, type Catalog, type Problem } from './tools/catalog.ts';\nimport { CONFIG_FILES, loadConfig, type ResolvedConfig } from './tools/config.ts';\nimport { formatDiff, wordDiff } from './tools/diff.ts';\nimport { applyFixes } from './tools/entry.ts';\nimport { applyExtract, planExtract } from './tools/extract.ts';\nimport { generate, render } from './tools/generate.ts';\nimport { lint } from './tools/lint.ts';\nimport type { StaleTranslation } from './tools/lock.ts';\nimport { applyMigrate, planMigrate } from './tools/migrate.ts';\nimport { SarifReport } from './tools/sarif.ts';\nimport { isReferenced, scan, sourceFiles } from './tools/scan.ts';\nimport { SYNTAX_NAME } from './tools/syntax.ts';\n\nconst HELP = `prosebook: every string your app shows, in one typed catalog\n\nUsage: prosebook <command> [options]\n\nCommands:\n init Create a config and a first catalog file\n generate Compile the catalog into typed modules (--check: fail if out of date, --watch)\n lint Check the messages and their translations against the writing rules\n (--fix: write the fixes the rules know, such as spellings and …)\n status Translation coverage and stale translations per locale (--strict, --verbose)\n accept <locale> [keys] Mark stale translations as still right (--all for every stale one)\n todo <locale> Print the missing and stale entries of a locale, ready to translate\n scan Find hardcoded text in JSX and catalog keys no code reads (--strict)\n extract [paths] Move hardcoded JSX text into the catalog and rewrite the code to read it\n (dry run unless --write; --area <name> puts every entry in one area)\n migrate <1|2> Rewrite the catalog and translations in ICU MessageFormat (1) or MessageFormat 2\n (dry run unless --write)\n check generate --check, lint and status --strict together, for CI\n\nOptions:\n --root <dir> Project root (default: the current directory)\n --format <text|sarif> With lint, scan, status and check: sarif writes a SARIF 2.1.0 log to stdout\n for code scanning, and the usual text to stderr\n -h, --help Show this help\n`;\n\nconst SARIF_COMMANDS = ['lint', 'scan', 'status', 'check'];\n\n/** What the checks that aren't lint rules hold the project to, for the rules in a SARIF log. */\nconst RULES = {\n catalog: 'The catalog and its translations parse, and each translation keeps the placeholders of its source.',\n 'out-of-date': 'The generated modules match the catalog: run `prosebook generate`.',\n 'missing-translation': 'Every locale translates every entry.',\n 'stale-translation': 'A translation was made from the source text as it is now.',\n 'hardcoded-text': 'Text a person reads comes from the catalog, not from a literal in JSX.',\n 'unused-key': 'Code reads every catalog key.',\n allow: \"An entry's exemption names a rule the entry breaks, and says why in a sentence.\",\n};\n\n/** Text for people. Under `--format sarif` stdout holds only the log, so this goes to stderr. */\nlet out = (s = '') => process.stdout.write(`${s}\\n`);\nconst err = (s = '') => process.stderr.write(`${s}\\n`);\n\nasync function main(argv: string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n root: { type: 'string' },\n check: { type: 'boolean' },\n watch: { type: 'boolean' },\n strict: { type: 'boolean' },\n verbose: { type: 'boolean' },\n all: { type: 'boolean' },\n write: { type: 'boolean' },\n fix: { type: 'boolean' },\n area: { type: 'string' },\n format: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...rest] = positionals;\n if (values.help || command === undefined) {\n out(HELP);\n return 0;\n }\n if (values.format !== undefined && values.format !== 'text' && values.format !== 'sarif') {\n err(`prosebook: --format is text or sarif, not \"${values.format}\"`);\n return 1;\n }\n const report = values.format === 'sarif' ? new SarifReport() : undefined;\n if (report !== undefined) {\n if (!SARIF_COMMANDS.includes(command)) {\n err(`prosebook: --format sarif works with ${SARIF_COMMANDS.join(', ')}`);\n return 1;\n }\n out = err;\n }\n const root = values.root ?? process.cwd();\n if (command === 'init') return init(root);\n const config = await loadConfig(root);\n let code: number;\n try {\n switch (command) {\n case 'generate':\n case 'gen':\n if (values.watch) return watchMode(config);\n return runGenerate(config, values.check === true);\n case 'lint':\n code = runLint(config, report, values.fix === true);\n break;\n case 'status':\n code = runStatus(config, values.strict === true, values.verbose === true, report);\n break;\n case 'accept':\n return runAccept(config, rest, values.all === true);\n case 'todo':\n return runTodo(config, rest[0]);\n case 'scan':\n code = runScan(config, values.strict === true, report);\n break;\n case 'extract':\n return runExtract(config, rest, values.write === true, values.area);\n case 'migrate':\n return runMigrate(config, rest[0], values.write === true);\n case 'check': {\n const results = [runGenerate(config, true, report), runLint(config, report), runStatus(config, true, false, report)];\n code = results.some((r) => r !== 0) ? 1 : 0;\n break;\n }\n default:\n err(`prosebook: no command \"${command}\"\\n`);\n out(HELP);\n return 1;\n }\n } catch (e) {\n if (!(e instanceof CatalogError)) throw e;\n reportProblems(config, e.problems, report);\n err(`\\n${e.problems.filter((p) => p.severity === 'error').length} error(s) in the catalog.`);\n code = 1;\n }\n if (report !== undefined) process.stdout.write(`${JSON.stringify(report.toJSON(config.root, tool()), null, 2)}\\n`);\n return code;\n}\n\nfunction tool() {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string; homepage: string };\n return { name: 'prosebook', version: pkg.version, informationUri: pkg.homepage };\n}\n\nfunction reportProblems(config: ResolvedConfig, problems: Problem[], report?: SarifReport) {\n for (const p of problems) err(`${p.severity === 'error' ? 'error' : 'warn '} ${whereOf(p)}: ${p.message}`);\n if (report === undefined) return;\n const locate = entryLocator(config.root);\n report.rule('catalog', RULES.catalog);\n for (const p of problems) {\n report.add({\n rule: 'catalog',\n level: p.severity === 'error' ? 'error' : 'warning',\n message: p.message,\n file: p.file,\n ...(p.file !== undefined && p.key !== undefined ? locate(p.file, p.key) : {}),\n });\n }\n}\n\nfunction rel(config: ResolvedConfig, path: string) {\n return relative(config.root, path).replaceAll('\\\\', '/');\n}\n\nfunction runGenerate(config: ResolvedConfig, check: boolean, report?: SarifReport): number {\n const { changed, catalog, stale } = generate(config, { check });\n reportProblems(config, catalog.problems, report);\n const staleCount = [...stale.values()].reduce((n, s) => n + s.size, 0);\n const summary = `${catalog.messages.length} messages, ${catalog.locales.length + 1} locale(s)${staleCount > 0 ? `, ${staleCount} stale translation(s)` : ''}`;\n if (check) {\n if (changed.length === 0) {\n out(`prosebook: generated files are up to date (${summary})`);\n return 0;\n }\n err(`prosebook: out of date, run \\`prosebook generate\\`:\\n ${changed.map((p) => rel(config, p)).join('\\n ')}`);\n report?.rule('out-of-date', RULES['out-of-date']);\n for (const p of changed)\n report?.add({ rule: 'out-of-date', level: 'error', message: 'out of date: run `prosebook generate`', file: rel(config, p) });\n return 1;\n }\n out(`prosebook: ${changed.length === 0 ? 'nothing changed' : `wrote ${changed.length} file(s)`} (${summary})`);\n return 0;\n}\n\nfunction watchMode(config: ResolvedConfig): Promise<number> {\n const regenerate = () => {\n try {\n runGenerate(config, false);\n } catch (e) {\n if (e instanceof CatalogError) reportProblems(config, e.problems);\n else err(String(e));\n }\n };\n regenerate();\n let timer: ReturnType<typeof setTimeout> | undefined;\n for (const dir of [config.catalog, config.locales]) {\n if (!existsSync(dir)) continue;\n watch(dir, { recursive: true }, (_event, file) => {\n if (file === null || !file.endsWith('.yaml')) return;\n clearTimeout(timer);\n timer = setTimeout(regenerate, 50);\n });\n }\n out('prosebook: watching the catalog for changes');\n return new Promise(() => {});\n}\n\nfunction runLint(config: ResolvedConfig, report?: SarifReport, fix = false): number {\n let { catalog } = render(config);\n let findings = lint(catalog.messages, config);\n if (fix) {\n const { fixed, problems } = applyFixes(config, catalog, findings);\n if (problems.length > 0) {\n reportProblems(config, problems, report);\n err('The fixes would break the catalog, so nothing was written.');\n return 1;\n }\n if (fixed > 0) {\n out(`prosebook: fixed ${fixed} finding(s); run prosebook generate to compile them`);\n ({ catalog } = render(config));\n findings = lint(catalog.messages, config);\n }\n }\n const locate = entryLocator(config.root);\n const placed = findings.map((f) => ({ ...f, ...locate(f.file, f.key, f.at?.start, config.source) }));\n for (const f of placed) {\n const severity = f.severity === 'error' ? 'error' : 'warn ';\n err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === undefined ? '' : ' (fixable)'}`);\n }\n if (report !== undefined) {\n for (const [name, { rule }] of Object.entries(config.lint.rules)) report.rule(name, rule.description);\n report.rule('allow', RULES.allow);\n for (const f of placed) {\n report.add({\n rule: f.rule,\n level: f.severity === 'error' ? 'error' : 'warning',\n message: `${f.key}: ${f.message}`,\n file: f.file,\n line: f.line,\n column: f.column,\n });\n }\n }\n const errors = findings.filter((f) => f.severity === 'error').length;\n const fixable = findings.filter((f) => f.fix !== undefined).length;\n if (findings.length === 0) {\n out(`prosebook: ${catalog.messages.length} messages pass the writing rules`);\n } else {\n err(\n `\\n${errors} error(s), ${findings.length - errors} warning(s). Fix the string, or add \\`allow: { <rule>: <why> }\\` where the rule is wrong about it.`,\n );\n if (fixable > 0) err(`${fixable} of them can be fixed with prosebook lint --fix.`);\n }\n return errors > 0 ? 1 : 0;\n}\n\nfunction missingKeys(catalog: Catalog, locale: string): string[] {\n return catalog.messages.filter((m) => !m.translations.has(locale)).map((m) => m.key);\n}\n\nfunction runStatus(config: ResolvedConfig, strict: boolean, verbose: boolean, report?: SarifReport): number {\n const { catalog, stale } = render(config);\n if (report !== undefined) reportStatus(config, catalog, stale, strict ? 'error' : 'warning', report);\n const total = catalog.messages.length;\n if (catalog.locales.length === 0) {\n out(`prosebook: ${total} messages in ${config.source}, no translations yet`);\n return 0;\n }\n let incomplete = false;\n out(`${'locale'.padEnd(10)} ${'translated'.padStart(12)} ${'missing'.padStart(8)} ${'stale'.padStart(6)}`);\n for (const locale of catalog.locales) {\n const missing = missingKeys(catalog, locale);\n const staleHere = stale.get(locale) ?? new Map<string, StaleTranslation>();\n const staleKeys = [...staleHere.keys()];\n const done = total - missing.length;\n if (missing.length > 0 || staleKeys.length > 0) incomplete = true;\n const pct = total === 0 ? 100 : Math.floor((done / total) * 100);\n out(\n `${locale.padEnd(10)} ${`${done}/${total} ${String(pct).padStart(3)}%`.padStart(12)} ${String(missing.length).padStart(8)} ${String(staleKeys.length).padStart(6)}`,\n );\n if (verbose) {\n for (const key of missing) out(` missing ${key}`);\n for (const [key, s] of staleHere) out(` stale ${key}: ${changeOf(s)}`);\n }\n }\n if (incomplete && !verbose)\n out('\\n`prosebook status --verbose` lists the keys; `prosebook todo <locale>` prints them ready to translate.');\n return strict && incomplete ? 1 : 0;\n}\n\nfunction reportStatus(\n config: ResolvedConfig,\n catalog: Catalog,\n stale: Map<string, Map<string, StaleTranslation>>,\n level: 'error' | 'warning',\n report: SarifReport,\n) {\n const locate = entryLocator(config.root);\n report.rule('missing-translation', RULES['missing-translation']);\n report.rule('stale-translation', RULES['stale-translation']);\n for (const locale of catalog.locales) {\n for (const m of catalog.messages) {\n if (m.translations.has(locale)) continue;\n const message = `${m.key} has no ${locale} translation: \\`prosebook todo ${locale}\\` prints it ready to translate`;\n report.add({ rule: 'missing-translation', level, message, file: m.file, ...locate(m.file, m.key) });\n }\n for (const [key, s] of stale.get(locale) ?? []) {\n const file = catalog.messages.find((m) => m.key === key)?.translations.get(locale)?.file;\n if (file === undefined) continue;\n const message = `the ${locale} translation of ${key} was made from an earlier source (${changeOf(s)}): update it, or \\`prosebook accept ${locale} ${key}\\` if it is still right`;\n report.add({ rule: 'stale-translation', level, message, file, ...locate(file, key) });\n }\n }\n}\n\nfunction runAccept(config: ResolvedConfig, args: string[], all: boolean): number {\n const [locale, ...keys] = args;\n if (locale === undefined || (keys.length === 0 && !all)) {\n err('Usage: prosebook accept <locale> <key>... | prosebook accept <locale> --all');\n return 1;\n }\n const before = render(config).stale.get(locale) ?? new Map<string, StaleTranslation>();\n const accepting = all ? new Set(before.keys()) : new Set(keys);\n for (const key of accepting) if (!before.has(key)) err(`warn ${locale} ${key} is not stale`);\n generate(config, { accept: new Map([[locale, all ? true : new Set(keys)]]) });\n out(`prosebook: accepted ${[...accepting].filter((k) => before.has(k)).length} translation(s) in ${locale}`);\n return 0;\n}\n\n/** What changed in the source of a stale translation, as a word diff, or why that isn't known. */\nfunction changeOf(stale: StaleTranslation): string {\n if (stale.was === undefined) return \"the earlier text isn't recorded (the lock predates prosebook 0.2)\";\n return formatDiff(wordDiff(stale.was, stale.now)).replaceAll('\\n', ' ');\n}\n\nfunction runTodo(config: ResolvedConfig, locale: string | undefined): number {\n if (locale === undefined) {\n err('Usage: prosebook todo <locale>');\n return 1;\n }\n const { catalog, stale } = render(config);\n const staleKeys = stale.get(locale) ?? new Map<string, StaleTranslation>();\n const byArea = new Map<string, string[]>();\n let count = 0;\n for (const m of catalog.messages) {\n const current = m.translations.get(locale);\n if (current !== undefined && !staleKeys.has(m.key)) continue;\n count += 1;\n const lines = byArea.get(m.area) ?? [];\n lines.push('');\n if (m.context) lines.push(`# ${m.context}`);\n for (const [arg, said] of m.argDocs) lines.push(`# {${arg}}: ${said}`);\n lines.push(`# ${config.source}: ${m.text.replaceAll('\\n', ' ')}`);\n const change = staleKeys.get(m.key);\n if (change !== undefined) lines.push(`# stale, the source changed: ${changeOf(change)}`);\n lines.push(stringify({ [m.key]: current?.text ?? '' }, { lineWidth: 0 }).trimEnd());\n byArea.set(m.area, lines);\n }\n if (count === 0) {\n out(`# ${locale} is complete`);\n return 0;\n }\n out(\n `# prosebook todo: ${count} entr${count === 1 ? 'y' : 'ies'} for ${locale}. Fill them in and paste each block into ${rel(config, config.locales)}/${locale}/<area>.yaml (or ${locale}.yaml).`,\n );\n for (const [area, lines] of byArea) {\n out(`\\n# ── ${area}.yaml ──`);\n out(lines.join('\\n'));\n }\n return 0;\n}\n\nfunction runScan(config: ResolvedConfig, strict: boolean, report?: SarifReport): number {\n const { catalog } = render(config);\n const result = scan(config.scan.dirs, config.scan.extensions, config.scan.attributes, config.root, [config.out]);\n for (const h of result.hardcoded)\n err(`${h.file}:${h.line}:${h.column} hardcoded ${h.where === 'text' ? 'text' : `${h.where}=`} \"${h.text}\"`);\n const unused = catalog.messages.filter((m) => !isReferenced(m.key, result.refs));\n for (const m of unused) err(`${m.file} ${m.key}: no code reads this key`);\n if (report !== undefined) {\n const level = strict ? 'error' : 'warning';\n const locate = entryLocator(config.root);\n report.rule('hardcoded-text', RULES['hardcoded-text']);\n report.rule('unused-key', RULES['unused-key']);\n for (const h of result.hardcoded) {\n const what = `${h.where === 'text' ? '' : `${h.where}=`}\"${h.text}\"`;\n const message = `${what} is hardcoded: move it into the catalog with \\`prosebook extract\\`, or mark the line \\`prosebook-ignore\\``;\n report.add({ rule: 'hardcoded-text', level, message, file: h.file, line: h.line, column: h.column });\n }\n for (const m of unused)\n report.add({ rule: 'unused-key', level, message: `no code reads ${m.key}: remove it`, file: m.file, ...locate(m.file, m.key) });\n }\n out(\n `prosebook: scanned ${result.files} file(s): ${result.hardcoded.length} hardcoded string(s), ${unused.length} unused key(s)` +\n (result.hardcoded.length > 0\n ? '. `prosebook extract` moves the text into the catalog; mark a deliberate line `prosebook-ignore`.'\n : ''),\n );\n return strict && result.hardcoded.length + unused.length > 0 ? 1 : 0;\n}\n\nasync function runExtract(config: ResolvedConfig, paths: string[], write: boolean, area: string | undefined): Promise<number> {\n const targets = paths.length > 0 ? paths.map((p) => resolve(config.root, p)) : config.scan.dirs;\n const files = sourceFiles(targets, ['.tsx', '.jsx'], [config.out]).concat(targets.filter((t) => /\\.[jt]sx$/.test(t) && existsSync(t)));\n const plan = await planExtract(config, [...new Set(files)], { area });\n const extractions = plan.files.flatMap((f) => f.extractions);\n for (const x of extractions) {\n out(`${x.file}:${x.line}:${x.column} ${x.where === 'text' ? '' : `${x.where}=`}\"${x.message}\"`);\n out(` → ${x.key}${x.reused ? ' (already in the catalog)' : ''}: ${x.replacement.trim()}`);\n }\n for (const f of plan.files) for (const w of f.warnings) err(`warn ${w}`);\n if (extractions.length === 0) {\n out('prosebook: no hardcoded text to extract');\n return 0;\n }\n const changedFiles = plan.files.filter((f) => f.edits.length > 0).length;\n const areas = [...new Set(plan.entries.map((e) => e.area))].map((a) => `${a}.yaml`);\n const summary = `${extractions.length} string(s) in ${changedFiles} file(s), ${plan.entries.length} new entr${plan.entries.length === 1 ? 'y' : 'ies'}${areas.length > 0 ? ` in ${areas.join(', ')}` : ''}`;\n out(`\nPlaceholder types: ${plan.types === 'syntax' ? 'read from the code alone (no TypeScript 7 project found)' : `from ${plan.types}`}.`);\n if (!write) {\n out(`prosebook: would extract ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyExtract(config, plan);\n out(`\nprosebook: extracted ${summary}.`);\n out('The new entries are marked `TODO(prosebook extract)`: check each key, kind, context and argument, then run `prosebook lint`.');\n return runGenerate(config, false);\n}\n\nfunction runMigrate(config: ResolvedConfig, target: string | undefined, write: boolean): number {\n const to = target === '1' ? 1 : target === '2' ? 2 : undefined;\n if (to === undefined) {\n err('Usage: prosebook migrate <1|2> [--write]');\n return 1;\n }\n if (config.messageFormat === to) {\n out(`prosebook: the catalog is already in ${SYNTAX_NAME[to]}`);\n return 0;\n }\n const plan = planMigrate(config, to);\n const where = (x: { file: string; key: string; locale: string }) =>\n `${x.file} ${x.key}${x.locale === config.source ? '' : ` (${x.locale})`}`;\n for (const r of plan.rewrites) out(`${where(r)}\\n ${r.after.replaceAll('\\n', '\\n ')}`);\n for (const c of plan.changed) err(`warn ${where(c)}: renders differently for ${c.values}: \"${c.before}\" becomes \"${c.after}\"`);\n for (const b of plan.blocked) err(`error ${where(b)}: ${b.reason}`);\n if (plan.blocked.length > 0) {\n err(`\nprosebook: ${plan.blocked.length} message(s) have no ${SYNTAX_NAME[to]} equivalent. Change them, then migrate again; nothing was written.`);\n return 1;\n }\n const files = new Set(plan.rewrites.map((r) => r.file)).size;\n const summary = `${plan.rewrites.length} message(s) in ${files} file(s) in ${SYNTAX_NAME[to]}`;\n if (!write) {\n out(`\nprosebook: would rewrite ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyMigrate(config, plan);\n out(`\nprosebook: rewrote ${summary}.`);\n out(`Now set \\`messageFormat: ${to}\\` in the config, then run \\`prosebook generate\\`.`);\n return 0;\n}\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'prosebook/config';\n\nexport default defineConfig({\n catalog: 'copy/catalog',\n locales: 'copy/locales',\n out: 'src/copy',\n source: 'en',\n // Write messages in Unicode MessageFormat 2 rather than ICU MessageFormat:\n // messageFormat: 2,\n});\n`;\n\nconst SAMPLE_CATALOG = `# yaml-language-server: $schema=./catalog.schema.json\napp.title:\n kind: name\n surface: title\n context: The app's name, in the window title and the header.\n en: My app\n\napp.greeting:\n kind: state\n surface: status\n context: Greets the person signed in, at the top of the home page.\n args:\n name: the person's first name\n en: Welcome back, {name}.\n`;\n\nfunction init(root: string): number {\n const configured = CONFIG_FILES.find((f) => existsSync(join(root, f)));\n if (configured === undefined) {\n writeFileSync(join(root, 'prosebook.config.ts'), SAMPLE_CONFIG);\n out('created prosebook.config.ts');\n }\n const catalogDir = join(root, 'copy', 'catalog');\n if (!existsSync(catalogDir)) {\n mkdirSync(catalogDir, { recursive: true });\n mkdirSync(join(root, 'copy', 'locales'), { recursive: true });\n writeFileSync(join(catalogDir, 'app.yaml'), SAMPLE_CATALOG);\n out('created copy/catalog/app.yaml');\n }\n out('\\nNext: `prosebook generate`, then `createCopy(catalog)` from the generated src/copy.');\n return 0;\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (e: unknown) => {\n err(e instanceof Error ? (e.stack ?? e.message) : String(e));\n process.exit(1);\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The `prosebook` command. Each subcommand is a function of the resolved config; `check` runs the\n * ones CI needs in one go.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { watchCatalog } from './tools/bundle.ts';\nimport { CatalogError, entryLocator, whereOf, type Catalog, type Problem } from './tools/catalog.ts';\nimport { CONFIG_FILES, loadConfig, type ResolvedConfig } from './tools/config.ts';\nimport { applyFixes, importTranslations } from './tools/entry.ts';\nimport { applyExtract, planExtract } from './tools/extract.ts';\nimport { generate, render } from './tools/generate.ts';\nimport { lint, type Finding } from './tools/lint.ts';\nimport { changeOf, type StaleTranslation } from './tools/lock.ts';\nimport { applyMigrate, planMigrate } from './tools/migrate.ts';\nimport { SarifReport } from './tools/sarif.ts';\nimport { isReferenced, scan, sourceFiles } from './tools/scan.ts';\nimport { SYNTAX_NAME } from './tools/syntax.ts';\nimport { todoOf } from './tools/todo.ts';\n\nconst HELP = `prosebook: every string your app shows, in one typed catalog\n\nUsage: prosebook <command> [options]\n\nCommands:\n init Create a config and a first catalog file\n generate Compile the catalog into typed modules (--check: fail if out of date, --watch)\n lint Check the messages and their translations against the writing rules\n (--fix: write the fixes the rules know, such as spellings and …)\n status Translation coverage and stale translations per locale (--strict, --verbose)\n accept <locale> [keys] Mark stale translations as still right (--all for every stale one)\n todo <locale> Print the missing and stale entries of a locale, ready to translate\n (--prompt: as a prompt for a model, saying how to reply)\n import <locale> [file] Write translations from todo's YAML filled in, or a model's reply to --prompt\n (stdin without a file); each is checked first, and one that fails is left out\n scan Find hardcoded text in JSX and catalog keys no code reads (--strict)\n extract [paths] Move hardcoded JSX text into the catalog and rewrite the code to read it\n (dry run unless --write; --area <name> puts every entry in one area)\n migrate <1|2> Rewrite the catalog and translations in ICU MessageFormat (1) or MessageFormat 2\n (dry run unless --write)\n check generate --check, lint and status --strict together, for CI\n\nOptions:\n --root <dir> Project root (default: the current directory)\n --format <text|sarif> With lint, scan, status and check: sarif writes a SARIF 2.1.0 log to stdout\n for code scanning, and the usual text to stderr\n -h, --help Show this help\n`;\n\nconst SARIF_COMMANDS = ['lint', 'scan', 'status', 'check'];\n\n/** What the checks that aren't lint rules hold the project to, for the rules in a SARIF log. */\nconst RULES = {\n catalog: 'The catalog and its translations parse, and each translation keeps the placeholders of its source.',\n 'out-of-date': 'The generated modules match the catalog: run `prosebook generate`.',\n 'missing-translation': 'Every locale translates every entry.',\n 'stale-translation': 'A translation was made from the source text as it is now.',\n 'hardcoded-text': 'Text a person reads comes from the catalog, not from a literal in JSX.',\n 'unused-key': 'Code reads every catalog key.',\n allow: \"An entry's exemption names a rule the entry breaks, and says why in a sentence.\",\n};\n\n/** Text for people. Under `--format sarif` stdout holds only the log, so this goes to stderr. */\nlet out = (s = '') => process.stdout.write(`${s}\\n`);\nconst err = (s = '') => process.stderr.write(`${s}\\n`);\n\nasync function main(argv: string[]): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n root: { type: 'string' },\n check: { type: 'boolean' },\n watch: { type: 'boolean' },\n strict: { type: 'boolean' },\n verbose: { type: 'boolean' },\n all: { type: 'boolean' },\n write: { type: 'boolean' },\n fix: { type: 'boolean' },\n prompt: { type: 'boolean' },\n area: { type: 'string' },\n format: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [command, ...rest] = positionals;\n if (values.help || command === undefined) {\n out(HELP);\n return 0;\n }\n if (values.format !== undefined && values.format !== 'text' && values.format !== 'sarif') {\n err(`prosebook: --format is text or sarif, not \"${values.format}\"`);\n return 1;\n }\n const report = values.format === 'sarif' ? new SarifReport() : undefined;\n if (report !== undefined) {\n if (!SARIF_COMMANDS.includes(command)) {\n err(`prosebook: --format sarif works with ${SARIF_COMMANDS.join(', ')}`);\n return 1;\n }\n out = err;\n }\n const root = values.root ?? process.cwd();\n if (command === 'init') return init(root);\n const config = await loadConfig(root);\n let code: number;\n try {\n switch (command) {\n case 'generate':\n case 'gen':\n if (values.watch) return watchMode(config);\n return runGenerate(config, values.check === true);\n case 'lint':\n code = runLint(config, report, values.fix === true);\n break;\n case 'status':\n code = runStatus(config, values.strict === true, values.verbose === true, report);\n break;\n case 'accept':\n return runAccept(config, rest, values.all === true);\n case 'todo':\n return runTodo(config, rest[0], values.prompt === true);\n case 'import':\n return runImport(config, rest[0], rest[1]);\n case 'scan':\n code = runScan(config, values.strict === true, report);\n break;\n case 'extract':\n return runExtract(config, rest, values.write === true, values.area);\n case 'migrate':\n return runMigrate(config, rest[0], values.write === true);\n case 'check': {\n const results = [runGenerate(config, true, report), runLint(config, report), runStatus(config, true, false, report)];\n code = results.some((r) => r !== 0) ? 1 : 0;\n break;\n }\n default:\n err(`prosebook: no command \"${command}\"\\n`);\n out(HELP);\n return 1;\n }\n } catch (e) {\n if (!(e instanceof CatalogError)) throw e;\n reportProblems(config, e.problems, report);\n err(`\\n${e.problems.filter((p) => p.severity === 'error').length} error(s) in the catalog.`);\n code = 1;\n }\n if (report !== undefined) process.stdout.write(`${JSON.stringify(report.toJSON(config.root, tool()), null, 2)}\\n`);\n return code;\n}\n\nfunction tool() {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string; homepage: string };\n return { name: 'prosebook', version: pkg.version, informationUri: pkg.homepage };\n}\n\nfunction reportProblems(config: ResolvedConfig, problems: Problem[], report?: SarifReport) {\n for (const p of problems) err(`${p.severity === 'error' ? 'error' : 'warn '} ${whereOf(p)}: ${p.message}`);\n if (report === undefined) return;\n const locate = entryLocator(config.root);\n report.rule('catalog', RULES.catalog);\n for (const p of problems) {\n report.add({\n rule: 'catalog',\n level: p.severity === 'error' ? 'error' : 'warning',\n message: p.message,\n file: p.file,\n ...(p.file !== undefined && p.key !== undefined ? locate(p.file, p.key) : {}),\n });\n }\n}\n\nfunction rel(config: ResolvedConfig, path: string) {\n return relative(config.root, path).replaceAll('\\\\', '/');\n}\n\nfunction runGenerate(config: ResolvedConfig, check: boolean, report?: SarifReport): number {\n const { changed, catalog, stale } = generate(config, { check });\n reportProblems(config, catalog.problems, report);\n const staleCount = [...stale.values()].reduce((n, s) => n + s.size, 0);\n const summary = `${catalog.messages.length} messages, ${catalog.locales.length + 1} locale(s)${staleCount > 0 ? `, ${staleCount} stale translation(s)` : ''}`;\n if (check) {\n if (changed.length === 0) {\n out(`prosebook: generated files are up to date (${summary})`);\n return 0;\n }\n err(`prosebook: out of date, run \\`prosebook generate\\`:\\n ${changed.map((p) => rel(config, p)).join('\\n ')}`);\n report?.rule('out-of-date', RULES['out-of-date']);\n for (const p of changed)\n report?.add({ rule: 'out-of-date', level: 'error', message: 'out of date: run `prosebook generate`', file: rel(config, p) });\n return 1;\n }\n out(`prosebook: ${changed.length === 0 ? 'nothing changed' : `wrote ${changed.length} file(s)`} (${summary})`);\n return 0;\n}\n\nfunction watchMode(config: ResolvedConfig): Promise<number> {\n const regenerate = () => {\n try {\n runGenerate(config, false);\n } catch (e) {\n if (e instanceof CatalogError) reportProblems(config, e.problems);\n else err(String(e));\n }\n };\n regenerate();\n watchCatalog(config, regenerate);\n out('prosebook: watching the catalog for changes');\n return new Promise(() => {});\n}\n\nfunction runLint(config: ResolvedConfig, report?: SarifReport, fix = false): number {\n let { catalog } = render(config);\n let findings = lint(catalog.messages, config);\n if (fix) {\n const { fixed, problems } = applyFixes(config, catalog, findings);\n if (problems.length > 0) {\n reportProblems(config, problems, report);\n err('The fixes would break the catalog, so nothing was written.');\n return 1;\n }\n if (fixed > 0) {\n out(`prosebook: fixed ${fixed} finding(s); run prosebook generate to compile them`);\n ({ catalog } = render(config));\n findings = lint(catalog.messages, config);\n }\n }\n const placed = printFindings(config, findings);\n if (report !== undefined) {\n for (const [name, { rule }] of Object.entries(config.lint.rules)) report.rule(name, rule.description);\n report.rule('allow', RULES.allow);\n for (const f of placed) {\n report.add({\n rule: f.rule,\n level: f.severity === 'error' ? 'error' : 'warning',\n message: `${f.key}: ${f.message}`,\n file: f.file,\n line: f.line,\n column: f.column,\n });\n }\n }\n const errors = findings.filter((f) => f.severity === 'error').length;\n const fixable = findings.filter((f) => f.fix !== undefined).length;\n if (findings.length === 0) {\n out(`prosebook: ${catalog.messages.length} messages pass the writing rules`);\n } else {\n err(\n `\\n${errors} error(s), ${findings.length - errors} warning(s). Fix the string, or add \\`allow: { <rule>: <why> }\\` where the rule is wrong about it.`,\n );\n if (fixable > 0) err(`${fixable} of them can be fixed with prosebook lint --fix.`);\n }\n return errors > 0 ? 1 : 0;\n}\n\n/** Each finding on its line, placed at its entry's words. */\nfunction printFindings(config: ResolvedConfig, findings: Finding[]) {\n const locate = entryLocator(config.root);\n const placed = findings.map((f) => ({ ...f, ...locate(f.file, f.key, f.at?.start, config.source) }));\n for (const f of placed) {\n const severity = f.severity === 'error' ? 'error' : 'warn ';\n err(`${severity} ${f.file}:${f.line}:${f.column} ${f.key}: [${f.rule}] ${f.message}${f.fix === undefined ? '' : ' (fixable)'}`);\n }\n return placed;\n}\n\nfunction missingKeys(catalog: Catalog, locale: string): string[] {\n return catalog.messages.filter((m) => !m.translations.has(locale)).map((m) => m.key);\n}\n\nfunction runStatus(config: ResolvedConfig, strict: boolean, verbose: boolean, report?: SarifReport): number {\n const { catalog, stale } = render(config);\n if (report !== undefined) reportStatus(config, catalog, stale, strict ? 'error' : 'warning', report);\n const total = catalog.messages.length;\n if (catalog.locales.length === 0) {\n out(`prosebook: ${total} messages in ${config.source}, no translations yet`);\n return 0;\n }\n let incomplete = false;\n out(`${'locale'.padEnd(10)} ${'translated'.padStart(12)} ${'missing'.padStart(8)} ${'stale'.padStart(6)}`);\n for (const locale of catalog.locales) {\n const missing = missingKeys(catalog, locale);\n const staleHere = stale.get(locale) ?? new Map<string, StaleTranslation>();\n const staleKeys = [...staleHere.keys()];\n const done = total - missing.length;\n if (missing.length > 0 || staleKeys.length > 0) incomplete = true;\n const pct = total === 0 ? 100 : Math.floor((done / total) * 100);\n out(\n `${locale.padEnd(10)} ${`${done}/${total} ${String(pct).padStart(3)}%`.padStart(12)} ${String(missing.length).padStart(8)} ${String(staleKeys.length).padStart(6)}`,\n );\n if (verbose) {\n for (const key of missing) out(` missing ${key}`);\n for (const [key, s] of staleHere) out(` stale ${key}: ${changeOf(s)}`);\n }\n }\n if (incomplete && !verbose)\n out('\\n`prosebook status --verbose` lists the keys; `prosebook todo <locale>` prints them ready to translate.');\n return strict && incomplete ? 1 : 0;\n}\n\nfunction reportStatus(\n config: ResolvedConfig,\n catalog: Catalog,\n stale: Map<string, Map<string, StaleTranslation>>,\n level: 'error' | 'warning',\n report: SarifReport,\n) {\n const locate = entryLocator(config.root);\n report.rule('missing-translation', RULES['missing-translation']);\n report.rule('stale-translation', RULES['stale-translation']);\n for (const locale of catalog.locales) {\n for (const m of catalog.messages) {\n if (m.translations.has(locale)) continue;\n const message = `${m.key} has no ${locale} translation: \\`prosebook todo ${locale}\\` prints it ready to translate`;\n report.add({ rule: 'missing-translation', level, message, file: m.file, ...locate(m.file, m.key) });\n }\n for (const [key, s] of stale.get(locale) ?? []) {\n const file = catalog.messages.find((m) => m.key === key)?.translations.get(locale)?.file;\n if (file === undefined) continue;\n const message = `the ${locale} translation of ${key} was made from an earlier source (${changeOf(s)}): update it, or \\`prosebook accept ${locale} ${key}\\` if it is still right`;\n report.add({ rule: 'stale-translation', level, message, file, ...locate(file, key) });\n }\n }\n}\n\nfunction runAccept(config: ResolvedConfig, args: string[], all: boolean): number {\n const [locale, ...keys] = args;\n if (locale === undefined || (keys.length === 0 && !all)) {\n err('Usage: prosebook accept <locale> <key>... | prosebook accept <locale> --all');\n return 1;\n }\n const before = render(config).stale.get(locale) ?? new Map<string, StaleTranslation>();\n const accepting = all ? new Set(before.keys()) : new Set(keys);\n for (const key of accepting) if (!before.has(key)) err(`warn ${locale} ${key} is not stale`);\n generate(config, { accept: new Map([[locale, all ? true : new Set(keys)]]) });\n out(`prosebook: accepted ${[...accepting].filter((k) => before.has(k)).length} translation(s) in ${locale}`);\n return 0;\n}\n\nfunction runTodo(config: ResolvedConfig, locale: string | undefined, prompt: boolean): number {\n if (locale === undefined) {\n err('Usage: prosebook todo <locale> [--prompt]');\n return 1;\n }\n const { catalog, stale } = render(config);\n const todo = todoOf(config, catalog, stale.get(locale) ?? new Map(), locale, prompt);\n out(todo.text);\n if (prompt && todo.count > 0) err(`\\nHand this to a model, save its reply, and \\`prosebook import ${locale} <reply>\\`.`);\n return 0;\n}\n\nfunction runImport(config: ResolvedConfig, locale: string | undefined, file: string | undefined): number {\n if (locale === undefined || ((file === undefined || file === '-') && process.stdin.isTTY)) {\n err('Usage: prosebook import <locale> <file>, or the reply on stdin');\n return 1;\n }\n const reply = readFileSync(file === undefined || file === '-' ? 0 : resolve(file), 'utf8');\n const { written, unchanged, problems } = importTranslations(config, locale, reply);\n reportProblems(config, problems);\n if (written.length + unchanged.length > 0) {\n // A stale translation given back unchanged was looked at and still holds.\n const stale = render(config).stale.get(locale) ?? new Map<string, StaleTranslation>();\n const kept = unchanged.filter((key) => stale.has(key));\n const { catalog } = generate(config, { accept: new Map([[locale, new Set(kept)]]) });\n out(\n `prosebook: imported ${written.length} translation(s) into ${locale}${kept.length > 0 ? `, and accepted ${kept.length} stale one(s) as still right` : ''}`,\n );\n const imported = new Set(written);\n printFindings(\n config,\n lint(\n catalog.messages.filter((m) => imported.has(m.key)),\n config,\n ).filter((f) => f.locale === locale),\n );\n }\n if (problems.length === 0) return 0;\n err(`\\n${problems.length} left out: fix them, or hand these lines back to the model, and import again.`);\n return 1;\n}\n\nfunction runScan(config: ResolvedConfig, strict: boolean, report?: SarifReport): number {\n const { catalog } = render(config);\n const result = scan(config.scan.dirs, config.scan.extensions, config.scan.attributes, config.root, [config.out]);\n for (const h of result.hardcoded)\n err(`${h.file}:${h.line}:${h.column} hardcoded ${h.where === 'text' ? 'text' : `${h.where}=`} \"${h.text}\"`);\n const unused = catalog.messages.filter((m) => !isReferenced(m.key, result.refs));\n for (const m of unused) err(`${m.file} ${m.key}: no code reads this key`);\n if (report !== undefined) {\n const level = strict ? 'error' : 'warning';\n const locate = entryLocator(config.root);\n report.rule('hardcoded-text', RULES['hardcoded-text']);\n report.rule('unused-key', RULES['unused-key']);\n for (const h of result.hardcoded) {\n const what = `${h.where === 'text' ? '' : `${h.where}=`}\"${h.text}\"`;\n const message = `${what} is hardcoded: move it into the catalog with \\`prosebook extract\\`, or mark the line \\`prosebook-ignore\\``;\n report.add({ rule: 'hardcoded-text', level, message, file: h.file, line: h.line, column: h.column });\n }\n for (const m of unused)\n report.add({ rule: 'unused-key', level, message: `no code reads ${m.key}: remove it`, file: m.file, ...locate(m.file, m.key) });\n }\n out(\n `prosebook: scanned ${result.files} file(s): ${result.hardcoded.length} hardcoded string(s), ${unused.length} unused key(s)` +\n (result.hardcoded.length > 0\n ? '. `prosebook extract` moves the text into the catalog; mark a deliberate line `prosebook-ignore`.'\n : ''),\n );\n return strict && result.hardcoded.length + unused.length > 0 ? 1 : 0;\n}\n\nasync function runExtract(config: ResolvedConfig, paths: string[], write: boolean, area: string | undefined): Promise<number> {\n const targets = paths.length > 0 ? paths.map((p) => resolve(config.root, p)) : config.scan.dirs;\n const files = sourceFiles(targets, ['.tsx', '.jsx'], [config.out]).concat(targets.filter((t) => /\\.[jt]sx$/.test(t) && existsSync(t)));\n const plan = await planExtract(config, [...new Set(files)], { area });\n const extractions = plan.files.flatMap((f) => f.extractions);\n for (const x of extractions) {\n out(`${x.file}:${x.line}:${x.column} ${x.where === 'text' ? '' : `${x.where}=`}\"${x.message}\"`);\n out(` → ${x.key}${x.reused ? ' (already in the catalog)' : ''}: ${x.replacement.trim()}`);\n }\n for (const f of plan.files) for (const w of f.warnings) err(`warn ${w}`);\n if (extractions.length === 0) {\n out('prosebook: no hardcoded text to extract');\n return 0;\n }\n const changedFiles = plan.files.filter((f) => f.edits.length > 0).length;\n const areas = [...new Set(plan.entries.map((e) => e.area))].map((a) => `${a}.yaml`);\n const summary = `${extractions.length} string(s) in ${changedFiles} file(s), ${plan.entries.length} new entr${plan.entries.length === 1 ? 'y' : 'ies'}${areas.length > 0 ? ` in ${areas.join(', ')}` : ''}`;\n out(`\nPlaceholder types: ${plan.types === 'syntax' ? 'read from the code alone (no TypeScript 7 project found)' : `from ${plan.types}`}.`);\n if (!write) {\n out(`prosebook: would extract ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyExtract(config, plan);\n out(`\nprosebook: extracted ${summary}.`);\n out('The new entries are marked `TODO(prosebook extract)`: check each key, kind, context and argument, then run `prosebook lint`.');\n return runGenerate(config, false);\n}\n\nfunction runMigrate(config: ResolvedConfig, target: string | undefined, write: boolean): number {\n const to = target === '1' ? 1 : target === '2' ? 2 : undefined;\n if (to === undefined) {\n err('Usage: prosebook migrate <1|2> [--write]');\n return 1;\n }\n if (config.messageFormat === to) {\n out(`prosebook: the catalog is already in ${SYNTAX_NAME[to]}`);\n return 0;\n }\n const plan = planMigrate(config, to);\n const where = (x: { file: string; key: string; locale: string }) =>\n `${x.file} ${x.key}${x.locale === config.source ? '' : ` (${x.locale})`}`;\n for (const r of plan.rewrites) out(`${where(r)}\\n ${r.after.replaceAll('\\n', '\\n ')}`);\n for (const c of plan.changed) err(`warn ${where(c)}: renders differently for ${c.values}: \"${c.before}\" becomes \"${c.after}\"`);\n for (const b of plan.blocked) err(`error ${where(b)}: ${b.reason}`);\n if (plan.blocked.length > 0) {\n err(`\nprosebook: ${plan.blocked.length} message(s) have no ${SYNTAX_NAME[to]} equivalent. Change them, then migrate again; nothing was written.`);\n return 1;\n }\n const files = new Set(plan.rewrites.map((r) => r.file)).size;\n const summary = `${plan.rewrites.length} message(s) in ${files} file(s) in ${SYNTAX_NAME[to]}`;\n if (!write) {\n out(`\nprosebook: would rewrite ${summary}. Run again with --write to apply.`);\n return 0;\n }\n applyMigrate(config, plan);\n out(`\nprosebook: rewrote ${summary}.`);\n out(`Now set \\`messageFormat: ${to}\\` in the config, then run \\`prosebook generate\\`.`);\n return 0;\n}\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'prosebook/config';\n\nexport default defineConfig({\n catalog: 'copy/catalog',\n locales: 'copy/locales',\n out: 'src/copy',\n source: 'en',\n // Write messages in Unicode MessageFormat 2 rather than ICU MessageFormat:\n // messageFormat: 2,\n});\n`;\n\nconst SAMPLE_CATALOG = `# yaml-language-server: $schema=./catalog.schema.json\napp.title:\n kind: name\n surface: title\n context: The app's name, in the window title and the header.\n en: My app\n\napp.greeting:\n kind: state\n surface: status\n context: Greets the person signed in, at the top of the home page.\n args:\n name: the person's first name\n en: Welcome back, {name}.\n`;\n\nfunction init(root: string): number {\n const configured = CONFIG_FILES.find((f) => existsSync(join(root, f)));\n if (configured === undefined) {\n writeFileSync(join(root, 'prosebook.config.ts'), SAMPLE_CONFIG);\n out('created prosebook.config.ts');\n }\n const catalogDir = join(root, 'copy', 'catalog');\n if (!existsSync(catalogDir)) {\n mkdirSync(catalogDir, { recursive: true });\n mkdirSync(join(root, 'copy', 'locales'), { recursive: true });\n writeFileSync(join(catalogDir, 'app.yaml'), SAMPLE_CATALOG);\n out('created copy/catalog/app.yaml');\n }\n out('\\nNext: `prosebook generate`, then `createCopy(catalog)` from the generated src/copy.');\n return 0;\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (e: unknown) => {\n err(e instanceof Error ? (e.stack ?? e.message) : String(e));\n process.exit(1);\n },\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { nt as MessageFormatElement } from "./config-CzaJJQ60.js";
|
|
2
|
+
type Entry = readonly [key: string, ast: MessageFormatElement[]];
|
|
3
|
+
interface CompiledLocale {
|
|
4
|
+
/** `const oN = {…};` lines the messages refer to. */
|
|
5
|
+
declarations: string[];
|
|
6
|
+
/** `"key": message,` lines. */
|
|
7
|
+
rows: string[];
|
|
8
|
+
}
|
|
9
|
+
/** One locale's messages as code, keeping only `keep` when given. */
|
|
10
|
+
declare function compileLocale(entries: Iterable<Entry>, keep?: (key: string) => boolean): CompiledLocale;
|
|
11
|
+
/** The locale as an ES module whose default export is its messages. */
|
|
12
|
+
declare function localeModule(compiled: CompiledLocale, banner: string, ts: boolean): string;
|
|
13
|
+
/** The locale as one expression, for a bundler to inline in place of the module. */
|
|
14
|
+
declare function localeExpression(compiled: CompiledLocale): string;
|
|
15
|
+
export { localeModule as a, localeExpression as i, Entry as n, compileLocale as r, CompiledLocale as t };
|
|
16
|
+
//# sourceMappingURL=compile-ClK9htKr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-ClK9htKr.d.ts","names":[],"sources":["../src/tools/compile.ts"],"mappings":";KA0BY,kBAAkB,aAAa,KAAK;UAoG/B;;EAEf;;EAEA;;;iBAIc,cAAc,SAAS,SAAS,QAAQ,QAAQ,0BAA0B;;iBAW1E,aAAa,UAAU,gBAAgB,gBAAgB;;iBAUvD,iBAAiB,UAAU"}
|
|
@@ -412,6 +412,34 @@ declare function compileRule(name: string, rule: Rule): CheckRule;
|
|
|
412
412
|
/** `text` with the edits made, leaving out any that overlaps one before it. */
|
|
413
413
|
declare function applyEdits(text: string, edits: Edit[]): string;
|
|
414
414
|
declare function lint(messages: Message[], config: ResolvedConfig): Finding[];
|
|
415
|
+
type Element = MessageFormatElement;
|
|
416
|
+
/** A function as written: `:number minimumFractionDigits=2`. Kept on the styles it produces, to write back. */
|
|
417
|
+
interface Mf2Function {
|
|
418
|
+
name: string;
|
|
419
|
+
options: Record<string, string>;
|
|
420
|
+
/** Options given by an argument, as `currency=$code`: the option's name to the argument's. */
|
|
421
|
+
args?: Record<string, string>;
|
|
422
|
+
/** `u:dir`: the direction the value is written in, which picks the mark isolating it. */
|
|
423
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
424
|
+
}
|
|
425
|
+
/** An MF2 message as the element tree. Throws, with the fix, where it can't be read or compiled. */
|
|
426
|
+
declare function parseMf2(text: string, locale: string, isolate?: Isolate): Element[];
|
|
427
|
+
/**
|
|
428
|
+
* When a placeholder is isolated from the text around it, so a value written the other way can't
|
|
429
|
+
* reorder it: `'rtl'` in a locale not known to run left to right, or where `u:dir` asks; `'always'`
|
|
430
|
+
* also for every value of unknown direction (a string), as the spec's default strategy has it.
|
|
431
|
+
*/
|
|
432
|
+
type Isolate = 'rtl' | 'always' | false;
|
|
433
|
+
/** The `Intl.NumberFormat` options a numeric function formats with. */
|
|
434
|
+
declare function intlNumber(fn: Mf2Function): Intl.NumberFormatOptions;
|
|
435
|
+
/**
|
|
436
|
+
* The `Intl.DateTimeFormat` options a date or time function formats with. MF2's date and time
|
|
437
|
+
* functions are still a draft; this follows the spec's semantic options as the `messageformat`
|
|
438
|
+
* package does, so a message reads the same in both.
|
|
439
|
+
*/
|
|
440
|
+
declare function intlDate(fn: Mf2Function): Intl.DateTimeFormatOptions;
|
|
441
|
+
/** A message tree as MF2 text. Throws, saying what has no MF2 equivalent, where it can't be written. */
|
|
442
|
+
declare function toMf2(ast: Element[], locale: string): string;
|
|
415
443
|
interface WordLists {
|
|
416
444
|
/** Words a string never uses, in groups that say why. */
|
|
417
445
|
banned?: {
|
|
@@ -437,6 +465,38 @@ interface LintConfig extends WordLists {
|
|
|
437
465
|
/** Severities for one area's entries (a key's first segment) over `rules`: `{ native: { 'sentence-case': 'off' } }`. */
|
|
438
466
|
areas?: Record<string, RuleSeverities>;
|
|
439
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Native code generated from the catalog, for the parts of an app outside the browser: a tray, a
|
|
470
|
+
* menu bar, a CLI. One file, holding every locale, with a typed function per message; for Android and
|
|
471
|
+
* Apple, string resources, a file per locale.
|
|
472
|
+
*/
|
|
473
|
+
interface NativeTarget {
|
|
474
|
+
language: 'rust' | 'swift' | 'kotlin' | 'android' | 'apple';
|
|
475
|
+
/**
|
|
476
|
+
* The file written; for Android, the `res` folder its `values*` folders go in, and for Apple, the
|
|
477
|
+
* folder its `.lproj` folders go in.
|
|
478
|
+
*/
|
|
479
|
+
out: string;
|
|
480
|
+
/**
|
|
481
|
+
* Key prefixes the file holds: `['native']` takes every `native.*` key, which it names without the
|
|
482
|
+
* prefix (`native.tray.quit` is `tray_quit`). Every key, named in full, by default.
|
|
483
|
+
*/
|
|
484
|
+
keys?: string[];
|
|
485
|
+
/**
|
|
486
|
+
* The type holding the locales and messages: `Locale` in Rust, `Copy` in Swift and Kotlin. For
|
|
487
|
+
* Android, the resource file's name: `strings`, for `values/strings.xml`. For Apple, the table's:
|
|
488
|
+
* `Localizable`, or `InfoPlist` with `plist`.
|
|
489
|
+
*/
|
|
490
|
+
name?: string;
|
|
491
|
+
/** Kotlin's package. */
|
|
492
|
+
package?: string;
|
|
493
|
+
/**
|
|
494
|
+
* For Apple, the Info.plist the table translates: each key names one of its strings, spelled in
|
|
495
|
+
* words (`ns-camera-usage-description` is `NSCameraUsageDescription`), and the source must say
|
|
496
|
+
* what the plist says.
|
|
497
|
+
*/
|
|
498
|
+
plist?: string;
|
|
499
|
+
}
|
|
440
500
|
interface ProsebookConfig {
|
|
441
501
|
/** The source-language catalog: one `<area>.yaml` per area. */
|
|
442
502
|
catalog?: string;
|
|
@@ -453,6 +513,13 @@ interface ProsebookConfig {
|
|
|
453
513
|
* translations alike; `prosebook migrate` rewrites a catalog from one to the other.
|
|
454
514
|
*/
|
|
455
515
|
messageFormat?: 1 | 2;
|
|
516
|
+
/**
|
|
517
|
+
* In MF2, when a placeholder is wrapped in Unicode isolation marks, so a value written the other
|
|
518
|
+
* way (a Latin name in Hebrew) can't reorder the text around it. `'rtl'`, the default: in locales
|
|
519
|
+
* written right to left, and wherever `u:dir` asks. `'always'`: also every string in any locale,
|
|
520
|
+
* as the spec's default has it. `false`: never.
|
|
521
|
+
*/
|
|
522
|
+
isolate?: Isolate;
|
|
456
523
|
/** Locales that ship in the main bundle beside the source, rather than loading when first used. */
|
|
457
524
|
bundled?: string[];
|
|
458
525
|
/** A pseudo-locale generated from the source (accented and lengthened) to find layout bugs and hardcoded text. */
|
|
@@ -470,6 +537,8 @@ interface ProsebookConfig {
|
|
|
470
537
|
/** Where a string may sit, with what each surface means. */
|
|
471
538
|
surfaces?: Record<string, string>;
|
|
472
539
|
lint?: LintConfig;
|
|
540
|
+
/** Rust, Swift or Kotlin files `generate` writes beside the TypeScript. */
|
|
541
|
+
native?: NativeTarget[];
|
|
473
542
|
/** Where `prosebook scan` looks for hardcoded text and catalog keys. */
|
|
474
543
|
scan?: {
|
|
475
544
|
dirs?: string[];
|
|
@@ -486,6 +555,7 @@ interface ResolvedConfig {
|
|
|
486
555
|
lock: string;
|
|
487
556
|
source: string;
|
|
488
557
|
messageFormat: 1 | 2;
|
|
558
|
+
isolate: Isolate;
|
|
489
559
|
bundled: string[];
|
|
490
560
|
pseudo: string | false;
|
|
491
561
|
stale: 'use' | 'fallback';
|
|
@@ -501,6 +571,9 @@ interface ResolvedConfig {
|
|
|
501
571
|
}>;
|
|
502
572
|
areas: Record<string, Record<string, Severity>>;
|
|
503
573
|
} & Required<WordLists>;
|
|
574
|
+
native: (NativeTarget & {
|
|
575
|
+
name: string;
|
|
576
|
+
})[];
|
|
504
577
|
scan: {
|
|
505
578
|
dirs: string[];
|
|
506
579
|
extensions: string[];
|
|
@@ -513,5 +586,5 @@ declare const CONFIG_FILES: string[];
|
|
|
513
586
|
/** Finds and reads the config at `root`, or the defaults when there is none. */
|
|
514
587
|
declare function loadConfig(root: string, inline?: ProsebookConfig): Promise<ResolvedConfig>;
|
|
515
588
|
declare function resolveConfig(root: string, config: ProsebookConfig): ResolvedConfig;
|
|
516
|
-
export {
|
|
517
|
-
//# sourceMappingURL=config-
|
|
589
|
+
export { loadCatalog as $, RuleContext as A, DEFAULT_KINDS as B, Finding as C, Readings as D, Plugin as E, lint as F, Catalog as G, KindRule as H, phrase as I, KEY_PATTERN as J, CatalogError as K, sentences as L, applyEdits as M, compileRule as N, Report as O, definePlugin as P, entryLocation as Q, singularOf as R, ExistenceRule as S, OccurrenceRule as T, STRICT_LISTS as U, DEFAULT_SURFACES as V, Severity as W, Problem as X, Message as Y, Translation as Z, CapitalizationRule as _, ResolvedConfig as a, CoreRule as b, loadConfig as c, Mf2Function as d, previewOf as et, intlDate as f, CORE_RULES as g, toMf2 as h, ProsebookConfig as i, SubstitutionRule as j, Rule as k, resolveConfig as l, parseMf2 as m, LintConfig as n, MessageFormatElement as nt, WordLists as o, intlNumber as p, EntryLocation as q, NativeTarget as r, defineConfig as s, CONFIG_FILES as t, whereOf as tt, Isolate as u, CheckRule as v, HOLE as w, Edit as x, ConditionalRule as y, words as z };
|
|
590
|
+
//# sourceMappingURL=config-CzaJJQ60.d.ts.map
|