prosebook 0.1.0 → 0.3.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 +311 -57
- 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-DboItAre.js +111 -0
- package/dist/bundle-DboItAre.js.map +1 -0
- package/dist/bundle-KD-DiiFa.d.ts +6 -0
- package/dist/bundle-KD-DiiFa.d.ts.map +1 -0
- package/dist/cli.js +51 -56
- package/dist/cli.js.map +1 -1
- package/dist/compile-xF3F4oIp.d.ts +16 -0
- package/dist/compile-xF3F4oIp.d.ts.map +1 -0
- package/dist/{config-CxVMn19f.js → config-BPSmoYel.js} +82 -196
- package/dist/config-BPSmoYel.js.map +1 -0
- package/dist/{config-CF7HziDa.d.ts → config-DdMkKt_N.d.ts} +85 -7
- package/dist/config-DdMkKt_N.d.ts.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-CnUfCkIk.js +96 -0
- package/dist/endpoints-CnUfCkIk.js.map +1 -0
- package/dist/generate-9pR3i5Bj.js +5173 -0
- package/dist/generate-9pR3i5Bj.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-PkPvsB07.js → sarif-CWyTAQ0y.js} +103 -87
- package/dist/sarif-CWyTAQ0y.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 -64
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +5 -4
- package/dist/vite.d.ts +14 -4
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +60 -114
- 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 +61 -13
- package/src/tools/config.ts +58 -3
- package/src/tools/endpoints.ts +121 -0
- package/src/tools/entry.ts +77 -0
- package/src/tools/generate.ts +32 -32
- package/src/tools/icu.ts +93 -8
- package/src/tools/index.ts +20 -4
- package/src/tools/lint.ts +49 -12
- 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/emit.ts +87 -0
- package/src/tools/native/index.ts +36 -0
- package/src/tools/native/kotlin.ts +269 -0
- package/src/tools/native/model.ts +280 -0
- package/src/tools/native/number.ts +210 -0
- package/src/tools/native/plurals.ts +103 -0
- package/src/tools/native/rust.ts +367 -0
- package/src/tools/native/swift.ts +326 -0
- package/src/tools/scan.ts +134 -4
- package/src/tools/syntax.ts +3 -3
- package/src/tools/todo.ts +118 -0
- package/src/vite.ts +74 -116
- package/dist/config-CF7HziDa.d.ts.map +0 -1
- package/dist/config-CxVMn19f.js.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-PkPvsB07.js.map +0 -1
- package/dist/scan-B0tZ7y-D.js +0 -2447
- package/dist/scan-B0tZ7y-D.js.map +0 -1
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","names":["Edit"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","names":["Edit"],"sources":["../src/tools/scan.ts","../src/tools/extract.ts","../src/tools/entry.ts","../src/tools/lock.ts","../src/tools/generate.ts","../src/tools/diff.ts","../src/tools/pseudo.ts","../src/tools/mf1.ts","../src/tools/syntax.ts","../src/tools/migrate.ts","../src/tools/types.ts","../src/tools/todo.ts","../src/tools/sarif.ts"],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { A as writeMessage, C as entryLocation, D as whereOf, E as previewOf, F as toMf1, M as intlNumber, N as parseMf2, O as SYNTAX_NAME, P as toMf2, S as KEY_PATTERN, T as loadCatalog, _ as applyFixes, a as render, b as readEntry, c as pseudoText, d as hash, f as readLock, g as wordDiff, h as formatDiff, i as localesOf, j as intlDate, k as readMessage, l as changeOf, m as serializeLock, n as generate, o as writeFiles, p as reconcile, r as localeEntries, s as pseudoAst, t as catalogSchema, u as emptyLock, v as editEntry, x as CatalogError, y as importTranslations } from "./generate-9pR3i5Bj.js";
|
|
2
|
+
import { a as looksLikeCode, c as unseenReads, d as localeModule, i as keyRefs, l as compileLocale, n as hardcodedText, o as scan, r as isReferenced, s as sourceFiles, t as READERS, u as localeExpression } from "./scan-Bx0_yDSi.js";
|
|
3
|
+
import { a as applyExtract, c as planExtract, d as typescriptOracle, i as planMigrate, l as slugOf, n as todoOf, o as areaOf, r as applyMigrate, s as draftPlurals, t as SarifReport, u as syntaxKind } from "./sarif-CWyTAQ0y.js";
|
|
4
|
+
import { _ as words, a as DEFAULT_KINDS, c as CORE_RULES, d as compileRule, f as definePlugin, g as singularOf, h as sentences, i as resolveConfig, l as HOLE, m as phrase, n as defineConfig, o as DEFAULT_SURFACES, p as lint, r as loadConfig, s as STRICT_LISTS, u as applyEdits } from "./config-BPSmoYel.js";
|
|
5
|
+
export { CORE_RULES, CatalogError, DEFAULT_KINDS, DEFAULT_SURFACES, HOLE, KEY_PATTERN, READERS, STRICT_LISTS, SYNTAX_NAME, SarifReport, applyEdits, applyExtract, applyFixes, applyMigrate, areaOf, catalogSchema, changeOf, compileLocale, compileRule, defineConfig, definePlugin, draftPlurals, editEntry, emptyLock, entryLocation, formatDiff, generate, hardcodedText, hash, importTranslations, intlDate, intlNumber, isReferenced, keyRefs, lint, loadCatalog, loadConfig, localeEntries, localeExpression, localeModule, localesOf, looksLikeCode, parseMf2, phrase, planExtract, planMigrate, previewOf, pseudoAst, pseudoText, readEntry, readLock, readMessage, reconcile, render, resolveConfig, scan, sentences, serializeLock, singularOf, slugOf, sourceFiles, syntaxKind, toMf1, toMf2, todoOf, typescriptOracle, unseenReads, whereOf, wordDiff, words, writeFiles, writeMessage };
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as Prune } from "./bundle-KD-DiiFa.js";
|
|
2
|
+
import { i as ProsebookConfig } from "./config-DdMkKt_N.js";
|
|
2
3
|
import { Plugin } from "vite";
|
|
3
4
|
export interface ProsebookPluginOptions {
|
|
4
|
-
/** The config, inline. Without it, `prosebook.config.*` at
|
|
5
|
+
/** The config, inline. Without it, `prosebook.config.*` at `root` is read. */
|
|
5
6
|
config?: ProsebookConfig;
|
|
6
|
-
/**
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The folder the config's paths resolve from, and where its file is found: the Vite root when
|
|
9
|
+
* unset. Apps that share one catalog name its folder, so each build generates the same files.
|
|
10
|
+
*/
|
|
11
|
+
root?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Ship only the messages the code names. `strict`, the default, also fails the build on a read
|
|
14
|
+
* whose key it can't see, such as `t(item.key)`, which would show the key where its message was
|
|
15
|
+
* pruned. `loose` prunes without that check; `false` ships every message.
|
|
16
|
+
*/
|
|
17
|
+
prune?: Prune;
|
|
8
18
|
/** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */
|
|
9
19
|
keep?: string[];
|
|
10
20
|
/** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */
|
package/dist/vite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","names":[],"sources":["../src/vite.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"vite.d.ts","names":[],"sources":["../src/vite.ts"],"mappings":";;;iBA8BiB;;EAEf,SAAS;;;;;EAKT;;;;;;EAMA,QAAQ;;EAER;;EAEA;;wBAcc,UAAU,UAAS,yBAA8B"}
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as installInspector, d as unseenOf, i as hotLocale, n as HOT_SET, o as keeper, r as beside, t as HOT_INDEX, u as unseenMessage } from "./bundle-DboItAre.js";
|
|
2
|
+
import { n as generate, x as CatalogError } from "./generate-9pR3i5Bj.js";
|
|
3
|
+
import { c as unseenReads, i as keyRefs, l as compileLocale, u as localeExpression } from "./scan-Bx0_yDSi.js";
|
|
4
|
+
import { r as loadConfig } from "./config-BPSmoYel.js";
|
|
5
|
+
import { n as nodeMiddleware, r as redirect, t as inspectorEndpoints } from "./endpoints-CnUfCkIk.js";
|
|
6
|
+
import { relative, resolve } from "node:path";
|
|
4
7
|
import { fileURLToPath } from "node:url";
|
|
5
8
|
// src/vite.ts
|
|
6
9
|
/**
|
|
@@ -14,39 +17,32 @@ import { fileURLToPath } from "node:url";
|
|
|
14
17
|
* In dev an edit doesn't reload the page: each generated locale module accepts its own update and
|
|
15
18
|
* hands the new messages to every copy (`HotHook`), which re-renders in the locale and state it was in.
|
|
16
19
|
* The index accepts its own too, since its doc comments change with the source text, and reloads the
|
|
17
|
-
* page only when a locale was added or removed.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* page only when a locale was added or removed. In the browser the index imports the dev module first,
|
|
21
|
+
* which sets up the hooks and the inspector (`inspector.ts`) before any copy exists, so frameworks that
|
|
22
|
+
* render their own HTML (TanStack Start, React Router) get them too. The server opens the entry the
|
|
23
|
+
* inspector asks for through Vite's own `/__open-in-editor`, so the editor is whichever Vite already
|
|
24
|
+
* launches.
|
|
20
25
|
*/
|
|
21
26
|
const PLACEHOLDER = /__PROSEBOOK_LOCALE_(\d+)__/g;
|
|
27
|
+
const SCRIPT = /\.[cm]?[jt]sx?$/;
|
|
28
|
+
const DEV = "virtual:prosebook/dev";
|
|
22
29
|
const DEV_ID = "\0prosebook/dev";
|
|
23
|
-
const hotLocale = (locale) => `
|
|
24
|
-
if (import.meta.hot) {
|
|
25
|
-
import.meta.hot.accept((next) => {
|
|
26
|
-
const hot = globalThis.__PROSEBOOK_HOT__;
|
|
27
|
-
if (next === undefined || hot === undefined) return import.meta.hot.invalidate();
|
|
28
|
-
for (const update of hot) update(${JSON.stringify(locale)}, next.default);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
`;
|
|
32
|
-
const HOT_INDEX = `
|
|
33
|
-
if (import.meta.hot) {
|
|
34
|
-
import.meta.hot.accept((next) => {
|
|
35
|
-
if (next === undefined || next.catalog.locales.join() !== catalog.locales.join()) import.meta.hot.invalidate();
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
`;
|
|
39
30
|
const normalize = (path) => resolve(path).replaceAll("\\", "/");
|
|
40
|
-
const inspectorPath =
|
|
31
|
+
const inspectorPath = beside(import.meta.url, "inspector");
|
|
32
|
+
const ownDir = normalize(fileURLToPath(new URL(".", import.meta.url)));
|
|
41
33
|
function prosebook(options = {}) {
|
|
42
34
|
let config;
|
|
43
35
|
let generated;
|
|
44
36
|
let building = false;
|
|
45
37
|
let base = "/";
|
|
38
|
+
let viteRoot = "";
|
|
39
|
+
let kept = () => true;
|
|
46
40
|
let refs = {
|
|
47
41
|
exact: /* @__PURE__ */ new Set(),
|
|
48
42
|
prefixes: /* @__PURE__ */ new Set()
|
|
49
43
|
};
|
|
44
|
+
const reads = /* @__PURE__ */ new Map();
|
|
45
|
+
const prune = options.prune ?? "strict";
|
|
50
46
|
const placeholders = [];
|
|
51
47
|
const shipped = {
|
|
52
48
|
total: 0,
|
|
@@ -65,27 +61,16 @@ function prosebook(options = {}) {
|
|
|
65
61
|
const out = normalize(config.out);
|
|
66
62
|
return path === `${out}/index.ts` || path === `${out}/index.js`;
|
|
67
63
|
};
|
|
68
|
-
const kept = (key) => isReferenced(key, refs) || (options.keep ?? []).some((k) => k.endsWith(".*") ? key.startsWith(k.slice(0, -1)) : k === key);
|
|
69
64
|
return {
|
|
70
65
|
name: "prosebook",
|
|
71
66
|
async configResolved(resolved) {
|
|
72
67
|
building = resolved.command === "build";
|
|
73
68
|
base = resolved.base;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
transformIndexHtml() {
|
|
77
|
-
if (building) return [];
|
|
78
|
-
return [{
|
|
79
|
-
tag: "script",
|
|
80
|
-
attrs: {
|
|
81
|
-
type: "module",
|
|
82
|
-
src: `${base}@id/__x00__prosebook/dev`
|
|
83
|
-
},
|
|
84
|
-
injectTo: "head-prepend"
|
|
85
|
-
}];
|
|
69
|
+
viteRoot = resolved.root;
|
|
70
|
+
config = await loadConfig(options.root === void 0 ? resolved.root : resolve(resolved.root, options.root), options.config);
|
|
86
71
|
},
|
|
87
72
|
resolveId(id) {
|
|
88
|
-
return id ===
|
|
73
|
+
return id === DEV ? DEV_ID : null;
|
|
89
74
|
},
|
|
90
75
|
buildStart() {
|
|
91
76
|
try {
|
|
@@ -97,6 +82,8 @@ function prosebook(options = {}) {
|
|
|
97
82
|
exact: /* @__PURE__ */ new Set(),
|
|
98
83
|
prefixes: /* @__PURE__ */ new Set()
|
|
99
84
|
};
|
|
85
|
+
kept = keeper(refs, options.keep);
|
|
86
|
+
reads.clear();
|
|
100
87
|
placeholders.length = 0;
|
|
101
88
|
shipped.total = shipped.kept = 0;
|
|
102
89
|
},
|
|
@@ -108,7 +95,7 @@ function prosebook(options = {}) {
|
|
|
108
95
|
if (!path.endsWith(".yaml") || !dirs.some((d) => path.startsWith(`${d}/`))) return;
|
|
109
96
|
try {
|
|
110
97
|
generated = generate(config);
|
|
111
|
-
server.config.logger.info(`prosebook: regenerated from ${
|
|
98
|
+
server.config.logger.info(`prosebook: regenerated from ${relative(config.root, path).replaceAll("\\", "/")}`, { timestamp: true });
|
|
112
99
|
} catch (e) {
|
|
113
100
|
server.config.logger.error(describe(e), { timestamp: true });
|
|
114
101
|
server.ws.send({
|
|
@@ -125,92 +112,51 @@ function prosebook(options = {}) {
|
|
|
125
112
|
server.watcher.on("add", onChange);
|
|
126
113
|
server.watcher.on("unlink", onChange);
|
|
127
114
|
if (options.inspect === false) return;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (message === void 0) {
|
|
134
|
-
res.statusCode = 404;
|
|
135
|
-
res.end(`no entry for "${key}" in the catalog: has it been renamed since the page loaded?`);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const locale = url.searchParams.has("catalog") ? void 0 : url.searchParams.get("locale") ?? void 0;
|
|
139
|
-
const { file, line, column } = entryLocation(config, message, locale);
|
|
140
|
-
req.url = `${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`;
|
|
141
|
-
next();
|
|
142
|
-
});
|
|
143
|
-
server.middlewares.use((req, res, next) => {
|
|
144
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
145
|
-
if (url.pathname !== `${base}__prosebook/entry`) return next();
|
|
146
|
-
const reply = (status, body) => {
|
|
147
|
-
res.statusCode = status;
|
|
148
|
-
res.setHeader("Content-Type", "application/json");
|
|
149
|
-
res.end(JSON.stringify(body));
|
|
150
|
-
};
|
|
151
|
-
const fail = (e) => reply(e instanceof CatalogError ? 409 : 400, { problems: e instanceof CatalogError ? e.problems : [{
|
|
152
|
-
severity: "error",
|
|
153
|
-
message: e instanceof Error ? e.message : String(e)
|
|
154
|
-
}] });
|
|
155
|
-
if (req.method === "GET") {
|
|
156
|
-
try {
|
|
157
|
-
reply(200, readEntry(config, url.searchParams.get("key") ?? ""));
|
|
158
|
-
} catch (e) {
|
|
159
|
-
fail(e);
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const origin = req.headers.origin;
|
|
164
|
-
const sameOrigin = origin === void 0 || new URL(origin).host === req.headers.host;
|
|
165
|
-
if (req.method !== "POST" || !sameOrigin || req.headers["content-type"]?.startsWith("application/json") !== true) {
|
|
166
|
-
reply(403, { problems: [{
|
|
167
|
-
severity: "error",
|
|
168
|
-
message: "edits come from the page, as JSON"
|
|
169
|
-
}] });
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
let body = "";
|
|
173
|
-
req.setEncoding("utf8");
|
|
174
|
-
req.on("data", (chunk) => body += chunk);
|
|
175
|
-
req.on("end", () => {
|
|
176
|
-
try {
|
|
177
|
-
const { key, edit, write } = JSON.parse(body);
|
|
178
|
-
const result = editEntry(config, key, edit, write === true);
|
|
179
|
-
reply(result.problems.length > 0 ? 422 : 200, result);
|
|
180
|
-
} catch (e) {
|
|
181
|
-
fail(e);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
115
|
+
const editor = ({ file, line, column }) => redirect(`${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`);
|
|
116
|
+
server.middlewares.use(nodeMiddleware(inspectorEndpoints(config, {
|
|
117
|
+
base,
|
|
118
|
+
open: editor
|
|
119
|
+
})));
|
|
185
120
|
},
|
|
186
121
|
load(id) {
|
|
187
122
|
if (id === DEV_ID) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return `import { install } from ${JSON.stringify(inspectorPath)};\n${hot}install(${JSON.stringify({ base })});\n`;
|
|
123
|
+
if (options.inspect === false) return HOT_SET;
|
|
124
|
+
return HOT_SET + installInspector(inspectorPath, base);
|
|
191
125
|
}
|
|
192
|
-
if (!building ||
|
|
126
|
+
if (!building || prune === false || generated === void 0) return null;
|
|
193
127
|
const locale = localeOf(id);
|
|
194
128
|
if (locale === void 0) return null;
|
|
195
129
|
placeholders.push(locale);
|
|
196
130
|
return `export default __PROSEBOOK_LOCALE_${placeholders.length - 1}__;\n`;
|
|
197
131
|
},
|
|
198
|
-
transform
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
132
|
+
transform: {
|
|
133
|
+
order: "pre",
|
|
134
|
+
handler(code, id, transformOptions) {
|
|
135
|
+
if (!building) {
|
|
136
|
+
const locale = localeOf(id);
|
|
137
|
+
if (locale !== void 0) return {
|
|
138
|
+
code: code + hotLocale(locale),
|
|
139
|
+
map: null
|
|
140
|
+
};
|
|
141
|
+
if (!isIndex(id)) return null;
|
|
142
|
+
return {
|
|
143
|
+
code: (transformOptions?.ssr === true ? "" : `import ${JSON.stringify(DEV)};\n`) + code + HOT_INDEX,
|
|
144
|
+
map: null
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (prune === false || id.includes("/node_modules/") || id.startsWith("\0")) return null;
|
|
148
|
+
if (localeOf(id) !== void 0 || isIndex(id)) return null;
|
|
149
|
+
keyRefs(code, refs);
|
|
150
|
+
const path = id.split("?", 1)[0];
|
|
151
|
+
if (prune === "strict" && SCRIPT.test(path) && !normalize(path).startsWith(`${ownDir}/`)) reads.set(id, unseenReads(path, code));
|
|
152
|
+
return null;
|
|
209
153
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
154
|
+
},
|
|
155
|
+
buildEnd(error) {
|
|
156
|
+
if (error !== void 0 || prune !== "strict") return;
|
|
157
|
+
const live = new Set(this.getModuleIds());
|
|
158
|
+
const unseen = unseenOf([...reads].filter(([id]) => live.has(id)).map(([, r]) => r));
|
|
159
|
+
if (unseen.length > 0) this.error(unseenMessage(unseen, viteRoot));
|
|
214
160
|
},
|
|
215
161
|
renderChunk(code) {
|
|
216
162
|
if (!code.includes("__PROSEBOOK_LOCALE_") || generated === void 0) return null;
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * `prosebook/vite`: the catalog as part of the build.\n *\n * Generates on start and again whenever a catalog or translation file changes, so dev never runs on\n * stale modules and a broken entry shows in the error overlay. A build ships only the messages the\n * code names: each locale module is swapped for its messages less the keys no module references\n * (see `tools/scan.ts` for how a reference is found, and why it errs towards keeping).\n *\n * In dev an edit doesn't reload the page: each generated locale module accepts its own update and\n * hands the new messages to every copy (`HotHook`), which re-renders in the locale and state it was in.\n * The index accepts its own too, since its doc comments change with the source text, and reloads the\n * page only when a locale was added or removed. The page also gets the inspector (`inspector.ts`), and\n * the server opens the entry it asks for through Vite's own `/__open-in-editor`, so the editor is\n * whichever Vite already launches.\n */\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Plugin } from 'vite';\n\nimport { CatalogError, entryLocation } from './tools/catalog.ts';\nimport { compileLocale, localeExpression } from './tools/compile.ts';\nimport { loadConfig, type ProsebookConfig, type ResolvedConfig } from './tools/config.ts';\nimport { editEntry, readEntry, type EntryEdit } from './tools/entry.ts';\nimport { generate, type Generated } from './tools/generate.ts';\nimport { isReferenced, keyRefs, type KeyRefs } from './tools/scan.ts';\n\nexport interface ProsebookPluginOptions {\n /** The config, inline. Without it, `prosebook.config.*` at the Vite root is read. */\n config?: ProsebookConfig;\n /** Ship only the messages the code names. On by default. */\n prune?: boolean;\n /** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */\n keep?: string[];\n /** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */\n inspect?: boolean;\n}\n\nconst PLACEHOLDER = /__PROSEBOOK_LOCALE_(\\d+)__/g;\nconst DEV_ID = '\\0prosebook/dev';\n\nconst 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\nconst 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\nconst normalize = (path: string) => resolve(path).replaceAll('\\\\', '/');\n\n// Beside this module: the source when run from source, the built file in dist.\nconst inspectorPath = normalize(fileURLToPath(new URL(`./inspector${import.meta.url.endsWith('.ts') ? '.ts' : '.js'}`, import.meta.url)));\n\nexport function prosebook(options: ProsebookPluginOptions = {}): Plugin {\n let config: ResolvedConfig;\n let generated: Generated | undefined;\n let building = false;\n let base = '/';\n let refs: KeyRefs = { exact: new Set(), prefixes: new Set() };\n const placeholders: string[] = [];\n const shipped = { total: 0, kept: 0 };\n\n const describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\n\n const localeOf = (id: string): string | undefined => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n if (!path.startsWith(`${out}/`)) return undefined;\n const name = path.slice(out.length + 1).replace(/\\.[jt]s$/, '');\n return generated?.entries.has(name) === true ? name : undefined;\n };\n\n const isIndex = (id: string) => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n return path === `${out}/index.ts` || path === `${out}/index.js`;\n };\n\n const kept = (key: string) =>\n isReferenced(key, refs) || (options.keep ?? []).some((k) => (k.endsWith('.*') ? key.startsWith(k.slice(0, -1)) : k === key));\n\n return {\n name: 'prosebook',\n\n async configResolved(resolved) {\n building = resolved.command === 'build';\n base = resolved.base;\n config = await loadConfig(resolved.root, options.config);\n },\n\n // First in <head>, so the hot hooks are there before any copy is created.\n transformIndexHtml() {\n if (building) return [];\n return [{ tag: 'script', attrs: { type: 'module', src: `${base}@id/__x00__prosebook/dev` }, injectTo: 'head-prepend' }];\n },\n\n resolveId(id) {\n return id === DEV_ID ? id : null;\n },\n\n buildStart() {\n try {\n generated = generate(config);\n } catch (e) {\n this.error(describe(e));\n }\n refs = { exact: new Set(), prefixes: new Set() };\n placeholders.length = 0;\n shipped.total = shipped.kept = 0;\n },\n\n configureServer(server) {\n const dirs = [normalize(config.catalog), normalize(config.locales)];\n server.watcher.add(dirs);\n const onChange = (file: string) => {\n const path = normalize(file);\n if (!path.endsWith('.yaml') || !dirs.some((d) => path.startsWith(`${d}/`))) return;\n try {\n generated = generate(config);\n server.config.logger.info(`prosebook: regenerated from ${path.slice(normalize(config.root).length + 1)}`, { timestamp: true });\n } catch (e) {\n server.config.logger.error(describe(e), { timestamp: true });\n server.ws.send({ type: 'error', err: { message: describe(e), stack: '', plugin: 'prosebook' } });\n }\n };\n server.watcher.on('change', onChange);\n server.watcher.on('add', onChange);\n server.watcher.on('unlink', onChange);\n if (options.inspect === false) return;\n\n // `?key=cart.items&locale=de`: the translation's entry, or the catalog's with `catalog` or when\n // the locale has none. Handed on to Vite's `/__open-in-editor`, which comes later in the stack.\n server.middlewares.use((req, res, next) => {\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname !== `${base}__prosebook/open`) return next();\n const key = url.searchParams.get('key') ?? '';\n const message = generated?.catalog.messages.find((m) => m.key === key);\n if (message === undefined) {\n res.statusCode = 404;\n res.end(`no entry for \"${key}\" in the catalog: has it been renamed since the page loaded?`);\n return;\n }\n const locale = url.searchParams.has('catalog') ? undefined : (url.searchParams.get('locale') ?? undefined);\n const { file, line, column } = entryLocation(config, message, locale);\n req.url = `${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`;\n next();\n });\n\n // The inspector's dialog: GET `?key=…` reads an entry in every locale, POST `{ key, edit, write }`\n // applies an edit (`tools/entry.ts`). A POST writes files, so only the page itself may send one:\n // same origin, and JSON, which a form on another site can't send without asking first.\n server.middlewares.use((req, res, next) => {\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname !== `${base}__prosebook/entry`) return next();\n const reply = (status: number, body: unknown) => {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(body));\n };\n const fail = (e: unknown) =>\n reply(e instanceof CatalogError ? 409 : 400, {\n problems: e instanceof CatalogError ? e.problems : [{ severity: 'error', message: e instanceof Error ? e.message : String(e) }],\n });\n if (req.method === 'GET') {\n try {\n reply(200, readEntry(config, url.searchParams.get('key') ?? ''));\n } catch (e) {\n fail(e);\n }\n return;\n }\n const origin = req.headers.origin;\n const sameOrigin = origin === undefined || new URL(origin).host === req.headers.host;\n if (req.method !== 'POST' || !sameOrigin || req.headers['content-type']?.startsWith('application/json') !== true) {\n reply(403, { problems: [{ severity: 'error', message: 'edits come from the page, as JSON' }] });\n return;\n }\n let body = '';\n req.setEncoding('utf8');\n req.on('data', (chunk: string) => (body += chunk));\n req.on('end', () => {\n try {\n const { key, edit, write } = JSON.parse(body) as { key: string; edit: EntryEdit; write?: boolean };\n const result = editEntry(config, key, edit, write === true);\n reply(result.problems.length > 0 ? 422 : 200, result);\n } catch (e) {\n fail(e);\n }\n });\n });\n },\n\n load(id) {\n if (id === DEV_ID) {\n const hot = 'globalThis.__PROSEBOOK_HOT__ ??= new Set();\\n';\n if (options.inspect === false) return hot;\n return `import { install } from ${JSON.stringify(inspectorPath)};\\n${hot}install(${JSON.stringify({ base })});\\n`;\n }\n if (!building || options.prune === false || generated === undefined) return null;\n const locale = localeOf(id);\n if (locale === undefined) return null;\n placeholders.push(locale);\n return `export default __PROSEBOOK_LOCALE_${placeholders.length - 1}__;\\n`;\n },\n\n transform(code, id) {\n if (!building) {\n const locale = localeOf(id);\n if (locale !== undefined) return { code: code + hotLocale(locale), map: null };\n return isIndex(id) ? { code: code + HOT_INDEX, map: null } : null;\n }\n if (options.prune === false || id.includes('/node_modules/') || id.startsWith('\\0')) return null;\n if (localeOf(id) !== undefined) return null;\n keyRefs(code, refs);\n return null;\n },\n\n renderChunk(code) {\n if (!code.includes('__PROSEBOOK_LOCALE_') || generated === undefined) return null;\n const entries = generated.entries;\n const replaced = code.replace(PLACEHOLDER, (_match, index: string) => {\n const locale = placeholders[Number(index)]!;\n const list = entries.get(locale) ?? [];\n const compiled = compileLocale(list, kept);\n shipped.total += list.length;\n shipped.kept += compiled.rows.length;\n return localeExpression(compiled);\n });\n return { code: replaced, map: null };\n },\n\n generateBundle() {\n if (shipped.total === 0) return;\n this.info(\n `shipped ${shipped.kept} of ${shipped.total} messages across ${placeholders.length} locale(s); pruned ${shipped.total - shipped.kept} no code names`,\n );\n },\n };\n}\n\nexport default prosebook;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * `prosebook/vite`: the catalog as part of the build.\n *\n * Generates on start and again whenever a catalog or translation file changes, so dev never runs on\n * stale modules and a broken entry shows in the error overlay. A build ships only the messages the\n * code names: each locale module is swapped for its messages less the keys no module references\n * (see `tools/scan.ts` for how a reference is found, and why it errs towards keeping).\n *\n * In dev an edit doesn't reload the page: each generated locale module accepts its own update and\n * hands the new messages to every copy (`HotHook`), which re-renders in the locale and state it was in.\n * The index accepts its own too, since its doc comments change with the source text, and reloads the\n * page only when a locale was added or removed. In the browser the index imports the dev module first,\n * which sets up the hooks and the inspector (`inspector.ts`) before any copy exists, so frameworks that\n * render their own HTML (TanStack Start, React Router) get them too. The server opens the entry the\n * inspector asks for through Vite's own `/__open-in-editor`, so the editor is whichever Vite already\n * launches.\n */\nimport { relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Plugin } from 'vite';\n\nimport { beside, HOT_INDEX, HOT_SET, hotLocale, installInspector, keeper, unseenMessage, unseenOf, type Prune } from './tools/bundle.ts';\nimport { CatalogError, type EntryLocation } from './tools/catalog.ts';\nimport { compileLocale, localeExpression } from './tools/compile.ts';\nimport { loadConfig, type ProsebookConfig, type ResolvedConfig } from './tools/config.ts';\nimport { inspectorEndpoints, nodeMiddleware, redirect } from './tools/endpoints.ts';\nimport { generate, type Generated } from './tools/generate.ts';\nimport { keyRefs, unseenReads, type KeyRefs, type Reads } from './tools/scan.ts';\n\nexport interface ProsebookPluginOptions {\n /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */\n config?: ProsebookConfig;\n /**\n * The folder the config's paths resolve from, and where its file is found: the Vite root when\n * unset. Apps that share one catalog name its folder, so each build generates the same files.\n */\n root?: string;\n /**\n * Ship only the messages the code names. `strict`, the default, also fails the build on a read\n * whose key it can't see, such as `t(item.key)`, which would show the key where its message was\n * pruned. `loose` prunes without that check; `false` ships every message.\n */\n prune?: Prune;\n /** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */\n keep?: string[];\n /** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */\n inspect?: boolean;\n}\n\nconst PLACEHOLDER = /__PROSEBOOK_LOCALE_(\\d+)__/g;\nconst SCRIPT = /\\.[cm]?[jt]sx?$/;\nconst DEV = 'virtual:prosebook/dev';\nconst DEV_ID = '\\0prosebook/dev';\n\nconst normalize = (path: string) => resolve(path).replaceAll('\\\\', '/');\n\nconst inspectorPath = beside(import.meta.url, 'inspector');\n// prosebook's own modules, which read keys handed to them: `<Text>` calls `rich(props.id)`.\nconst ownDir = normalize(fileURLToPath(new URL('.', import.meta.url)));\n\nexport function prosebook(options: ProsebookPluginOptions = {}): Plugin {\n let config: ResolvedConfig;\n let generated: Generated | undefined;\n let building = false;\n let base = '/';\n let viteRoot = '';\n let kept: (key: string) => boolean = () => true;\n let refs: KeyRefs = { exact: new Set(), prefixes: new Set() };\n const reads = new Map<string, Reads>();\n const prune = options.prune ?? 'strict';\n const placeholders: string[] = [];\n const shipped = { total: 0, kept: 0 };\n\n const describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\n\n const localeOf = (id: string): string | undefined => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n if (!path.startsWith(`${out}/`)) return undefined;\n const name = path.slice(out.length + 1).replace(/\\.[jt]s$/, '');\n return generated?.entries.has(name) === true ? name : undefined;\n };\n\n const isIndex = (id: string) => {\n const path = normalize(id.split('?', 1)[0]!);\n const out = normalize(config.out);\n return path === `${out}/index.ts` || path === `${out}/index.js`;\n };\n\n return {\n name: 'prosebook',\n\n async configResolved(resolved) {\n building = resolved.command === 'build';\n base = resolved.base;\n viteRoot = resolved.root;\n config = await loadConfig(options.root === undefined ? resolved.root : resolve(resolved.root, options.root), options.config);\n },\n\n resolveId(id) {\n return id === DEV ? DEV_ID : null;\n },\n\n buildStart() {\n try {\n generated = generate(config);\n } catch (e) {\n this.error(describe(e));\n }\n refs = { exact: new Set(), prefixes: new Set() };\n kept = keeper(refs, options.keep);\n reads.clear();\n placeholders.length = 0;\n shipped.total = shipped.kept = 0;\n },\n\n configureServer(server) {\n const dirs = [normalize(config.catalog), normalize(config.locales)];\n server.watcher.add(dirs);\n const onChange = (file: string) => {\n const path = normalize(file);\n if (!path.endsWith('.yaml') || !dirs.some((d) => path.startsWith(`${d}/`))) return;\n try {\n generated = generate(config);\n server.config.logger.info(`prosebook: regenerated from ${relative(config.root, path).replaceAll('\\\\', '/')}`, {\n timestamp: true,\n });\n } catch (e) {\n server.config.logger.error(describe(e), { timestamp: true });\n server.ws.send({ type: 'error', err: { message: describe(e), stack: '', plugin: 'prosebook' } });\n }\n };\n server.watcher.on('change', onChange);\n server.watcher.on('add', onChange);\n server.watcher.on('unlink', onChange);\n if (options.inspect === false) return;\n\n // Each entry opens through Vite's own `/__open-in-editor`, so the editor is whichever Vite launches.\n const editor = ({ file, line, column }: EntryLocation) =>\n redirect(`${base}__open-in-editor?file=${encodeURIComponent(`${file}:${line}:${column}`)}`);\n server.middlewares.use(nodeMiddleware(inspectorEndpoints(config, { base, open: editor })));\n },\n\n load(id) {\n if (id === DEV_ID) {\n if (options.inspect === false) return HOT_SET;\n return HOT_SET + installInspector(inspectorPath, base);\n }\n if (!building || prune === false || generated === undefined) return null;\n const locale = localeOf(id);\n if (locale === undefined) return null;\n placeholders.push(locale);\n return `export default __PROSEBOOK_LOCALE_${placeholders.length - 1}__;\\n`;\n },\n\n // Before other plugins, so a read is found in the source as written: `<Text id>` still JSX, and\n // the line it is reported at, and its `prosebook-ignore`, where the author wrote them.\n transform: {\n order: 'pre',\n handler(code, id, transformOptions) {\n if (!building) {\n const locale = localeOf(id);\n if (locale !== undefined) return { code: code + hotLocale(locale), map: null };\n if (!isIndex(id)) return null;\n // A server render has no page to inspect, and its copies are made per request.\n const dev = transformOptions?.ssr === true ? '' : `import ${JSON.stringify(DEV)};\\n`;\n return { code: dev + code + HOT_INDEX, map: null };\n }\n if (prune === false || id.includes('/node_modules/') || id.startsWith('\\0')) return null;\n // The index's `Messages` type names every key, and is read here before its types are stripped.\n if (localeOf(id) !== undefined || isIndex(id)) return null;\n keyRefs(code, refs);\n const path = id.split('?', 1)[0]!;\n if (prune === 'strict' && SCRIPT.test(path) && !normalize(path).startsWith(`${ownDir}/`)) reads.set(id, unseenReads(path, code));\n return null;\n },\n },\n\n buildEnd(error) {\n if (error !== undefined || prune !== 'strict') return;\n const live = new Set(this.getModuleIds());\n const unseen = unseenOf([...reads].filter(([id]) => live.has(id)).map(([, r]) => r));\n if (unseen.length > 0) this.error(unseenMessage(unseen, viteRoot));\n },\n\n renderChunk(code) {\n if (!code.includes('__PROSEBOOK_LOCALE_') || generated === undefined) return null;\n const entries = generated.entries;\n const replaced = code.replace(PLACEHOLDER, (_match, index: string) => {\n const locale = placeholders[Number(index)]!;\n const list = entries.get(locale) ?? [];\n const compiled = compileLocale(list, kept);\n shipped.total += list.length;\n shipped.kept += compiled.rows.length;\n return localeExpression(compiled);\n });\n return { code: replaced, map: null };\n },\n\n generateBundle() {\n if (shipped.total === 0) return;\n this.info(\n `shipped ${shipped.kept} of ${shipped.total} messages across ${placeholders.length} locale(s); pruned ${shipped.total - shipped.kept} no code names`,\n );\n },\n };\n}\n\nexport default prosebook;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prosebook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Every string your app shows, in one typed catalog: ICU messages compiled to plain functions, for React and any JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"bun",
|
|
6
7
|
"i18n",
|
|
7
8
|
"icu",
|
|
8
9
|
"intl",
|
|
@@ -10,10 +11,12 @@
|
|
|
10
11
|
"localization",
|
|
11
12
|
"messageformat",
|
|
12
13
|
"messageformat2",
|
|
14
|
+
"nextjs",
|
|
13
15
|
"react",
|
|
14
16
|
"translation",
|
|
15
17
|
"typescript",
|
|
16
|
-
"ux-writing"
|
|
18
|
+
"ux-writing",
|
|
19
|
+
"vite"
|
|
17
20
|
],
|
|
18
21
|
"homepage": "https://gkurt.com/prosebook/",
|
|
19
22
|
"bugs": {
|
|
@@ -32,6 +35,9 @@
|
|
|
32
35
|
"bin": {
|
|
33
36
|
"prosebook": "./dist/cli.js"
|
|
34
37
|
},
|
|
38
|
+
"workspaces": [
|
|
39
|
+
"examples/*"
|
|
40
|
+
],
|
|
35
41
|
"files": [
|
|
36
42
|
"dist",
|
|
37
43
|
"src",
|
|
@@ -59,6 +65,13 @@
|
|
|
59
65
|
"default": "./dist/react/index.js"
|
|
60
66
|
}
|
|
61
67
|
},
|
|
68
|
+
"./react/server": {
|
|
69
|
+
"prosebook@dev": "./src/react/server.ts",
|
|
70
|
+
"import": {
|
|
71
|
+
"types": "./dist/react/server.d.ts",
|
|
72
|
+
"default": "./dist/react/server.js"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
62
75
|
"./vite": {
|
|
63
76
|
"prosebook@dev": "./src/vite.ts",
|
|
64
77
|
"import": {
|
|
@@ -66,6 +79,20 @@
|
|
|
66
79
|
"default": "./dist/vite.js"
|
|
67
80
|
}
|
|
68
81
|
},
|
|
82
|
+
"./next": {
|
|
83
|
+
"prosebook@dev": "./src/next.ts",
|
|
84
|
+
"default": {
|
|
85
|
+
"types": "./dist/next.d.ts",
|
|
86
|
+
"default": "./dist/next.js"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"./bun": {
|
|
90
|
+
"prosebook@dev": "./src/bun.ts",
|
|
91
|
+
"import": {
|
|
92
|
+
"types": "./dist/bun.d.ts",
|
|
93
|
+
"default": "./dist/bun.js"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
69
96
|
"./config": {
|
|
70
97
|
"prosebook@dev": "./src/tools/config.ts",
|
|
71
98
|
"import": {
|
|
@@ -93,8 +120,9 @@
|
|
|
93
120
|
"build": "tsdown",
|
|
94
121
|
"prepack": "tsdown",
|
|
95
122
|
"cli": "bun --conditions=prosebook@dev src/cli.ts",
|
|
96
|
-
"example": "
|
|
97
|
-
"
|
|
123
|
+
"example": "tsdown && bun run --cwd examples/vite dev",
|
|
124
|
+
"examples:generate": "tsdown && bun run --filter 'example-*' generate",
|
|
125
|
+
"examples:smoke": "bun scripts/smoke.mts",
|
|
98
126
|
"site": "rm -rf _site && mkdir -p _site && cp -R site/* _site/",
|
|
99
127
|
"prepare": "husky",
|
|
100
128
|
"tegami": "bun scripts/tegami.mts"
|
|
@@ -111,6 +139,7 @@
|
|
|
111
139
|
"@types/react": "^19.3.0",
|
|
112
140
|
"@types/react-dom": "^19.3.0",
|
|
113
141
|
"@vitejs/plugin-react": "^6.1.2",
|
|
142
|
+
"cldr-core": "48",
|
|
114
143
|
"husky": "^9.1.7",
|
|
115
144
|
"lint-staged": "^17.6.0",
|
|
116
145
|
"oxfmt": "^0.75.0",
|
|
@@ -124,6 +153,7 @@
|
|
|
124
153
|
"vite": "^8.3.5"
|
|
125
154
|
},
|
|
126
155
|
"peerDependencies": {
|
|
156
|
+
"next": ">=16",
|
|
127
157
|
"react": ">=18",
|
|
128
158
|
"typescript": ">=7",
|
|
129
159
|
"vite": ">=6"
|
|
@@ -137,6 +167,9 @@
|
|
|
137
167
|
},
|
|
138
168
|
"typescript": {
|
|
139
169
|
"optional": true
|
|
170
|
+
},
|
|
171
|
+
"next": {
|
|
172
|
+
"optional": true
|
|
140
173
|
}
|
|
141
174
|
},
|
|
142
175
|
"lint-staged": {
|
package/src/bun.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `prosebook/bun`: the catalog as part of a Bun build, or of Bun's dev server.
|
|
3
|
+
*
|
|
4
|
+
* Generates when the plugin is set up. Given to `Bun.build`, it ships only the messages the code
|
|
5
|
+
* names: Bun loads a locale module before it has seen the code that reads it, so the keys come from
|
|
6
|
+
* the source under `scan.dirs` (`tools/bundle.ts`) and each locale loads already pruned. The `bun
|
|
7
|
+
* build` command reads no plugins, so a build that prunes is a script that calls `Bun.build`.
|
|
8
|
+
*
|
|
9
|
+
* Named in bunfig.toml's `[serve.static] plugins`, it serves `Bun.serve`'s bundler instead, which
|
|
10
|
+
* names no entry points: it regenerates whenever a catalog or translation file changes, and each
|
|
11
|
+
* locale module accepts its own update and hands the new messages to every copy, as under Vite. The
|
|
12
|
+
* index starts the inspector on the page too, whose endpoints are `inspector()`'s routes, spread into
|
|
13
|
+
* `Bun.serve`'s; an entry opens in the editor `Bun.openInEditor` finds.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { basename, resolve } from 'node:path';
|
|
17
|
+
|
|
18
|
+
import type { BunPlugin } from 'bun';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
beside,
|
|
22
|
+
HOT_INDEX,
|
|
23
|
+
HOT_SET,
|
|
24
|
+
hotLocale,
|
|
25
|
+
installInspector,
|
|
26
|
+
keeper,
|
|
27
|
+
prunedModule,
|
|
28
|
+
scanSources,
|
|
29
|
+
unseenMessage,
|
|
30
|
+
watchCatalog,
|
|
31
|
+
type Prune,
|
|
32
|
+
} from './tools/bundle.ts';
|
|
33
|
+
import { CatalogError } from './tools/catalog.ts';
|
|
34
|
+
import { loadConfig, type ProsebookConfig } from './tools/config.ts';
|
|
35
|
+
import { inspectorEndpoints } from './tools/endpoints.ts';
|
|
36
|
+
import { generate } from './tools/generate.ts';
|
|
37
|
+
|
|
38
|
+
export interface ProsebookBunOptions {
|
|
39
|
+
/** The config, inline. Without it, `prosebook.config.*` at `root` is read. */
|
|
40
|
+
config?: ProsebookConfig;
|
|
41
|
+
/** The folder the config's paths resolve from, and where its file is found: the working directory when unset. */
|
|
42
|
+
root?: string;
|
|
43
|
+
/**
|
|
44
|
+
* In `Bun.build`, ship only the messages the code names. `strict`, the default, also fails the
|
|
45
|
+
* build on a read whose key it can't see, such as `t(item.key)`. `loose` prunes without that
|
|
46
|
+
* check; `false` ships every message.
|
|
47
|
+
*/
|
|
48
|
+
prune?: Prune;
|
|
49
|
+
/** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */
|
|
50
|
+
keep?: string[];
|
|
51
|
+
/** Under the dev server, Alt-click a string on the page to open its entry in the editor. On by default. */
|
|
52
|
+
inspect?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const inspectorPath = beside(import.meta.url, 'inspector');
|
|
56
|
+
|
|
57
|
+
const describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));
|
|
58
|
+
const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
59
|
+
|
|
60
|
+
export function prosebook(options: ProsebookBunOptions = {}): BunPlugin {
|
|
61
|
+
return {
|
|
62
|
+
name: 'prosebook',
|
|
63
|
+
async setup(build) {
|
|
64
|
+
const config = await loadConfig(resolve(options.root ?? process.cwd()), options.config);
|
|
65
|
+
let generated = generate(config);
|
|
66
|
+
// The generated modules, by path as Bun gives it: either separator on Windows.
|
|
67
|
+
const folder = config.out.split(/[\\/]/).map(escape).join('[\\\\/]');
|
|
68
|
+
const filter = new RegExp(`^${folder}[\\\\/][^\\\\/]+\\.[jt]s$`);
|
|
69
|
+
const nameOf = (path: string) => basename(path).replace(/\.[jt]s$/, '');
|
|
70
|
+
|
|
71
|
+
if ('entrypoints' in build.config) {
|
|
72
|
+
const prune = options.prune ?? 'strict';
|
|
73
|
+
if (prune === false) return;
|
|
74
|
+
const { refs, unseen } = scanSources(config, prune === 'strict');
|
|
75
|
+
if (unseen.length > 0) throw new Error(unseenMessage(unseen, config.root));
|
|
76
|
+
const kept = keeper(refs, options.keep);
|
|
77
|
+
build.onLoad({ filter }, ({ path }) => {
|
|
78
|
+
const entries = generated.entries.get(nameOf(path));
|
|
79
|
+
return entries === undefined ? undefined : { contents: prunedModule(entries, kept), loader: 'js' };
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const dev = process.env.NODE_ENV !== 'production';
|
|
85
|
+
const inspector = dev && options.inspect !== false ? installInspector(inspectorPath, '/') : '';
|
|
86
|
+
if (dev) {
|
|
87
|
+
watchCatalog(config, () => {
|
|
88
|
+
try {
|
|
89
|
+
generated = generate(config);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.error(describe(e));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
build.onLoad({ filter }, ({ path }) => {
|
|
96
|
+
const name = nameOf(path);
|
|
97
|
+
const loader = path.endsWith('.ts') ? 'ts' : 'js';
|
|
98
|
+
if (generated.entries.has(name)) return { contents: readFileSync(path, 'utf8') + hotLocale(name), loader };
|
|
99
|
+
return name === 'index' ? { contents: HOT_SET + inspector + readFileSync(path, 'utf8') + HOT_INDEX, loader } : undefined;
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface InspectorRoutesOptions {
|
|
106
|
+
/** The config, inline. Without it, `prosebook.config.*` at `root` is read. */
|
|
107
|
+
config?: ProsebookConfig;
|
|
108
|
+
/** The folder the config's paths resolve from, and where its file is found: the working directory when unset. */
|
|
109
|
+
root?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The inspector's endpoints as `Bun.serve` routes: `routes: { ...inspector(), '/': index }`. For dev
|
|
114
|
+
* only: they read and write the catalog.
|
|
115
|
+
*/
|
|
116
|
+
export function inspector(options: InspectorRoutesOptions = {}): Record<string, (req: Request) => Promise<Response>> {
|
|
117
|
+
const handle = inspectorEndpoints(loadConfig(resolve(options.root ?? process.cwd()), options.config), {
|
|
118
|
+
open: ({ file, line, column }) => {
|
|
119
|
+
try {
|
|
120
|
+
Bun.openInEditor(file, { line, column });
|
|
121
|
+
return new Response(null, { status: 204 });
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return new Response(`${e instanceof Error ? e.message : String(e)}: set VISUAL or EDITOR to the editor's command`, { status: 500 });
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const route = async (req: Request) => (await handle(req)) ?? new Response(null, { status: 404 });
|
|
128
|
+
return { '/__prosebook/open': route, '/__prosebook/entry': route };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// bunfig.toml names the module, and Bun takes its default export as the plugin.
|
|
132
|
+
export default prosebook();
|