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.
Files changed (115) hide show
  1. package/README.md +345 -29
  2. package/dist/bun.d.ts +34 -0
  3. package/dist/bun.d.ts.map +1 -0
  4. package/dist/bun.js +98 -0
  5. package/dist/bun.js.map +1 -0
  6. package/dist/bundle-CpUIWb9j.d.ts +6 -0
  7. package/dist/bundle-CpUIWb9j.d.ts.map +1 -0
  8. package/dist/bundle-DboItAre.js +111 -0
  9. package/dist/bundle-DboItAre.js.map +1 -0
  10. package/dist/cli.js +51 -56
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compile-ClK9htKr.d.ts +16 -0
  13. package/dist/compile-ClK9htKr.d.ts.map +1 -0
  14. package/dist/{config-_4HfnJQu.d.ts → config-CzaJJQ60.d.ts} +75 -2
  15. package/dist/config-CzaJJQ60.d.ts.map +1 -0
  16. package/dist/{config-D--RITk2.js → config-nWZf9QmF.js} +42 -212
  17. package/dist/config-nWZf9QmF.js.map +1 -0
  18. package/dist/config.d.ts +2 -2
  19. package/dist/config.js +1 -1
  20. package/dist/{copy-DXnzXsJa.d.ts → copy-B3V_QRkT.d.ts} +20 -7
  21. package/dist/copy-B3V_QRkT.d.ts.map +1 -0
  22. package/dist/copy-Bo_FIoLy.js +154 -0
  23. package/dist/copy-Bo_FIoLy.js.map +1 -0
  24. package/dist/endpoints-BgG_5Vpm.js +96 -0
  25. package/dist/endpoints-BgG_5Vpm.js.map +1 -0
  26. package/dist/generate-BW3N8EGb.js +5488 -0
  27. package/dist/generate-BW3N8EGb.js.map +1 -0
  28. package/dist/helpers-BWGcD0kS.js +96 -0
  29. package/dist/helpers-BWGcD0kS.js.map +1 -0
  30. package/dist/icu-Dej0mNsF.js +218 -0
  31. package/dist/icu-Dej0mNsF.js.map +1 -0
  32. package/dist/index.d.ts +12 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -188
  35. package/dist/inspector.d.ts +4 -1
  36. package/dist/inspector.d.ts.map +1 -1
  37. package/dist/inspector.js +7 -2
  38. package/dist/inspector.js.map +1 -1
  39. package/dist/locale-DpQA1pqH.js +70 -0
  40. package/dist/locale-DpQA1pqH.js.map +1 -0
  41. package/dist/next-loader.d.ts +11 -0
  42. package/dist/next-loader.d.ts.map +1 -0
  43. package/dist/next-loader.js +17 -0
  44. package/dist/next-loader.js.map +1 -0
  45. package/dist/next.d.ts +30 -0
  46. package/dist/next.d.ts.map +1 -0
  47. package/dist/next.js +160 -0
  48. package/dist/next.js.map +1 -0
  49. package/dist/react/index.d.ts +3 -3
  50. package/dist/react/index.d.ts.map +1 -1
  51. package/dist/react/index.js.map +1 -1
  52. package/dist/react/server.d.ts +20 -0
  53. package/dist/react/server.d.ts.map +1 -0
  54. package/dist/react/server.js +34 -0
  55. package/dist/react/server.js.map +1 -0
  56. package/dist/{sarif-CI8HOokm.js → sarif-B5nrMqrT.js} +103 -87
  57. package/dist/sarif-B5nrMqrT.js.map +1 -0
  58. package/dist/scan-Bx0_yDSi.js +387 -0
  59. package/dist/scan-Bx0_yDSi.js.map +1 -0
  60. package/dist/tools.d.ts +83 -90
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +5 -4
  63. package/dist/vite.d.ts +3 -2
  64. package/dist/vite.d.ts.map +1 -1
  65. package/dist/vite.js +29 -109
  66. package/dist/vite.js.map +1 -1
  67. package/package.json +37 -4
  68. package/src/bun.ts +132 -0
  69. package/src/cli.ts +60 -58
  70. package/src/index.ts +1 -1
  71. package/src/inspector.ts +8 -3
  72. package/src/next-loader.ts +16 -0
  73. package/src/next.ts +188 -0
  74. package/src/react/index.tsx +6 -6
  75. package/src/react/server.ts +37 -0
  76. package/src/runtime/copy.ts +22 -3
  77. package/src/runtime/helpers.ts +54 -18
  78. package/src/runtime/locale.ts +19 -0
  79. package/src/runtime/types.ts +21 -6
  80. package/src/tools/bundle.ts +131 -0
  81. package/src/tools/catalog.ts +2 -2
  82. package/src/tools/compile.ts +60 -23
  83. package/src/tools/config.ts +57 -0
  84. package/src/tools/endpoints.ts +121 -0
  85. package/src/tools/entry.ts +77 -0
  86. package/src/tools/generate.ts +2 -0
  87. package/src/tools/icu.ts +63 -7
  88. package/src/tools/index.ts +6 -3
  89. package/src/tools/lock.ts +7 -0
  90. package/src/tools/mf1.ts +21 -7
  91. package/src/tools/mf2.ts +249 -57
  92. package/src/tools/migrate.ts +3 -2
  93. package/src/tools/native/android.ts +201 -0
  94. package/src/tools/native/apple.ts +139 -0
  95. package/src/tools/native/emit.ts +87 -0
  96. package/src/tools/native/index.ts +48 -0
  97. package/src/tools/native/kotlin.ts +269 -0
  98. package/src/tools/native/model.ts +292 -0
  99. package/src/tools/native/number.ts +210 -0
  100. package/src/tools/native/plurals.ts +103 -0
  101. package/src/tools/native/printf.ts +95 -0
  102. package/src/tools/native/rust.ts +367 -0
  103. package/src/tools/native/swift.ts +326 -0
  104. package/src/tools/syntax.ts +3 -3
  105. package/src/tools/todo.ts +118 -0
  106. package/src/vite.ts +28 -114
  107. package/dist/config-D--RITk2.js.map +0 -1
  108. package/dist/config-_4HfnJQu.d.ts.map +0 -1
  109. package/dist/copy-DXnzXsJa.d.ts.map +0 -1
  110. package/dist/helpers-BPuPGjQP.js +0 -61
  111. package/dist/helpers-BPuPGjQP.js.map +0 -1
  112. package/dist/index.js.map +0 -1
  113. package/dist/sarif-CI8HOokm.js.map +0 -1
  114. package/dist/scan-Ch2BfIUD.js +0 -2561
  115. 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 { F as SYNTAX_NAME, O as CatalogError, P as whereOf, S as applyFixes, j as entryLocator, o as scan, p as render, r as isReferenced, s as sourceFiles, u as generate } from "./scan-Ch2BfIUD.js";
3
- import { p as lint, r as loadConfig, t as CONFIG_FILES } from "./config-D--RITk2.js";
4
- import { a as wordDiff, i as formatDiff, l as planExtract, n as applyMigrate, o as applyExtract, r as planMigrate, t as SarifReport } from "./sarif-CI8HOokm.js";
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, watch, writeFileSync } from "node:fs";
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
- let timer;
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 locate = entryLocator(config.root);
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
- /** What changed in the source of a stale translation, as a word diff, or why that isn't known. */
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 staleKeys = stale.get(locale) ?? /* @__PURE__ */ new Map();
339
- const byArea = /* @__PURE__ */ new Map();
340
- let count = 0;
341
- for (const m of catalog.messages) {
342
- const current = m.translations.get(locale);
343
- if (current !== void 0 && !staleKeys.has(m.key)) continue;
344
- count += 1;
345
- const lines = byArea.get(m.area) ?? [];
346
- lines.push("");
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
- out(`# 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).`);
360
- for (const [area, lines] of byArea) {
361
- out(`\n# ── ${area}.yaml ──`);
362
- out(lines.join("\n"));
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 { sentences as A, EntryLocation as B, RuleContext as C, definePlugin as D, compileRule as E, KindRule as F, entryLocation as G, Message as H, STRICT_LISTS as I, whereOf as J, loadCatalog as K, Severity as L, words as M, DEFAULT_KINDS as N, lint as O, DEFAULT_SURFACES as P, Catalog as R, Rule as S, applyEdits as T, Problem as U, KEY_PATTERN as V, Translation as W, MessageFormatElement as Y, HOLE as _, WordLists as a, Readings as b, resolveConfig as c, CheckRule as d, ConditionalRule as f, Finding as g, ExistenceRule as h, ResolvedConfig as i, singularOf as j, phrase as k, CORE_RULES as l, Edit as m, LintConfig as n, defineConfig as o, CoreRule as p, previewOf as q, ProsebookConfig as r, loadConfig as s, CONFIG_FILES as t, CapitalizationRule as u, OccurrenceRule as v, SubstitutionRule as w, Report as x, Plugin as y, CatalogError as z };
517
- //# sourceMappingURL=config-_4HfnJQu.d.ts.map
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