prosebook 0.3.0 → 0.5.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 +357 -26
- package/dist/bind-BIZNv8nU.js +62 -0
- package/dist/bind-BIZNv8nU.js.map +1 -0
- package/dist/bind-ZS86jHXk.d.ts +31 -0
- package/dist/bind-ZS86jHXk.d.ts.map +1 -0
- package/dist/bun.d.ts +12 -3
- package/dist/bun.d.ts.map +1 -1
- package/dist/bun.js +77 -26
- package/dist/bun.js.map +1 -1
- package/dist/{bundle-KD-DiiFa.d.ts → bundle-CLHY_97D.d.ts} +3 -3
- package/dist/bundle-CLHY_97D.d.ts.map +1 -0
- package/dist/bundle-Ut6Y2X6N.js +217 -0
- package/dist/bundle-Ut6Y2X6N.js.map +1 -0
- package/dist/cli.js +55 -12
- package/dist/cli.js.map +1 -1
- package/dist/{config-BPSmoYel.js → config-7T4hX_f7.js} +13 -5
- package/dist/{config-BPSmoYel.js.map → config-7T4hX_f7.js.map} +1 -1
- package/dist/{config-DdMkKt_N.d.ts → config-CzaJJQ60.d.ts} +19 -5
- package/dist/{config-DdMkKt_N.d.ts.map → config-CzaJJQ60.d.ts.map} +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/{copy-B3V_QRkT.d.ts → copy-BXn0aYQn.d.ts} +37 -3
- package/dist/copy-BXn0aYQn.d.ts.map +1 -0
- package/dist/copy-Bv6S-Hr2.js +235 -0
- package/dist/copy-Bv6S-Hr2.js.map +1 -0
- package/dist/endpoints-Dhgowovz.js +217 -0
- package/dist/endpoints-Dhgowovz.js.map +1 -0
- package/dist/generate-1BeQAd1O.d.ts +72 -0
- package/dist/generate-1BeQAd1O.d.ts.map +1 -0
- package/dist/{generate-9pR3i5Bj.js → generate-CKv86Iki.js} +1724 -627
- package/dist/generate-CKv86Iki.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/next-loader.d.ts +9 -2
- package/dist/next-loader.d.ts.map +1 -1
- package/dist/next-loader.js +12 -3
- package/dist/next-loader.js.map +1 -1
- package/dist/next.d.ts +11 -3
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +94 -21
- package/dist/next.js.map +1 -1
- package/dist/preact/index.d.ts +34 -0
- package/dist/preact/index.d.ts.map +1 -0
- package/dist/preact/index.js +72 -0
- package/dist/preact/index.js.map +1 -0
- package/dist/react/index.d.ts +5 -14
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +9 -33
- package/dist/react/index.js.map +1 -1
- package/dist/react/server.d.ts +1 -1
- package/dist/react/server.js +1 -1
- package/dist/{sarif-CWyTAQ0y.js → sarif-BnwYC78_.js} +8 -98
- package/dist/sarif-BnwYC78_.js.map +1 -0
- package/dist/{scan-Bx0_yDSi.js → scan-C0ap4Mpp.js} +145 -20
- package/dist/scan-C0ap4Mpp.js.map +1 -0
- package/dist/solid/index.d.ts +29 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.js +48 -0
- package/dist/solid/index.js.map +1 -0
- package/dist/svelte/Text.svelte +13 -0
- package/dist/svelte/Text.svelte.d.ts +20 -0
- package/dist/svelte/index.d.ts +31 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +35 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/tools.d.ts +44 -65
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +5 -5
- package/dist/vite.d.ts +13 -3
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +82 -25
- package/dist/vite.js.map +1 -1
- package/dist/vue/index.d.ts +43 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +76 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +63 -7
- package/src/bun.ts +77 -20
- package/src/cli.ts +62 -8
- package/src/index.ts +1 -1
- package/src/next-loader.ts +17 -5
- package/src/next.ts +120 -16
- package/src/preact/index.ts +91 -0
- package/src/react/index.tsx +12 -42
- package/src/runtime/bind.ts +96 -0
- package/src/runtime/copy.ts +148 -24
- package/src/solid/index.ts +67 -0
- package/src/svelte/Text.svelte +13 -0
- package/src/svelte/Text.svelte.d.ts +20 -0
- package/src/svelte/index.ts +65 -0
- package/src/tools/bundle.ts +143 -2
- package/src/tools/catalog.ts +10 -1
- package/src/tools/chunks.ts +142 -0
- package/src/tools/config.ts +25 -7
- package/src/tools/entry.ts +19 -20
- package/src/tools/exchange.ts +275 -0
- package/src/tools/extract.ts +2 -2
- package/src/tools/generate.ts +7 -1
- package/src/tools/index.ts +2 -1
- package/src/tools/native/android.ts +186 -0
- package/src/tools/native/apple.ts +201 -0
- package/src/tools/native/date.ts +219 -0
- package/src/tools/native/index.ts +17 -5
- package/src/tools/native/kotlin.ts +47 -6
- package/src/tools/native/model.ts +67 -29
- package/src/tools/native/printf.ts +137 -0
- package/src/tools/native/rust.ts +86 -5
- package/src/tools/native/swift.ts +62 -6
- package/src/tools/scan.ts +146 -25
- package/src/tools/todo.ts +6 -1
- package/src/vite.ts +121 -28
- package/src/vue/index.ts +117 -0
- package/dist/bundle-DboItAre.js +0 -111
- package/dist/bundle-DboItAre.js.map +0 -1
- package/dist/bundle-KD-DiiFa.d.ts.map +0 -1
- package/dist/compile-xF3F4oIp.d.ts +0 -16
- package/dist/compile-xF3F4oIp.d.ts.map +0 -1
- package/dist/copy-B3V_QRkT.d.ts.map +0 -1
- package/dist/copy-Bo_FIoLy.js +0 -154
- package/dist/copy-Bo_FIoLy.js.map +0 -1
- package/dist/endpoints-CnUfCkIk.js +0 -96
- package/dist/endpoints-CnUfCkIk.js.map +0 -1
- package/dist/generate-9pR3i5Bj.js.map +0 -1
- package/dist/sarif-CWyTAQ0y.js.map +0 -1
- package/dist/scan-Bx0_yDSi.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as RichParam, S as RichKey, T as TagFn, _ as MessageShapes, a as createCopy, b as RegisteredLocale, c as ArgsParam, d as CompiledMessages, f as DefaultTags, g as MessageShape, h as HotHook, i as addArea, l as Chunks, m as Helpers, n as Copy, o as defineCatalog, p as DevHooks, r as CopyOptions, s as AnyTagFn, t as Catalog, u as Compiled, v as ReadHook, w as RichValues, x as RegisteredMessages, y as Register } from "./copy-BXn0aYQn.js";
|
|
2
2
|
/**
|
|
3
3
|
* Picks the best available locale for what was asked, in the order it was asked.
|
|
4
4
|
*
|
|
@@ -30,5 +30,5 @@ export declare function createHelpers(locale: string, report?: (error: Error) =>
|
|
|
30
30
|
* `null`, `undefined` and booleans dropped, as React drops them.
|
|
31
31
|
*/
|
|
32
32
|
export declare function flatten(output: unknown): unknown[];
|
|
33
|
-
export { type AnyTagFn, type ArgsParam, type Catalog, type Chunks, type Compiled, type CompiledMessages, type Copy, type CopyOptions, type DefaultTags, type DevHooks, type Helpers, type HotHook, type MessageShape, type MessageShapes, type ReadHook, type Register, type RegisteredLocale, type RegisteredMessages, type RichKey, type RichParam, type RichValues, type TagFn, createCopy, defineCatalog };
|
|
33
|
+
export { type AnyTagFn, type ArgsParam, type Catalog, type Chunks, type Compiled, type CompiledMessages, type Copy, type CopyOptions, type DefaultTags, type DevHooks, type Helpers, type HotHook, type MessageShape, type MessageShapes, type ReadHook, type Register, type RegisteredLocale, type RegisteredMessages, type RichKey, type RichParam, type RichValues, type TagFn, addArea, createCopy, defineCatalog };
|
|
34
34
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as defineCatalog, t as
|
|
1
|
+
import { n as createCopy, r as defineCatalog, t as addArea } from "./copy-Bv6S-Hr2.js";
|
|
2
2
|
import { i as preferredLocales, n as chainOf, r as negotiate, t as acceptedLocales } from "./locale-DpQA1pqH.js";
|
|
3
3
|
import { n as flatten, t as createHelpers } from "./helpers-BWGcD0kS.js";
|
|
4
|
-
export { acceptedLocales, chainOf, createCopy, createHelpers, defineCatalog, flatten, negotiate, preferredLocales };
|
|
4
|
+
export { acceptedLocales, addArea, chainOf, createCopy, createHelpers, defineCatalog, flatten, negotiate, preferredLocales };
|
package/dist/next-loader.d.ts
CHANGED
|
@@ -4,8 +4,15 @@ export type LoaderOptions = {
|
|
|
4
4
|
prepend: string;
|
|
5
5
|
} | {
|
|
6
6
|
locale: string;
|
|
7
|
+
} | {
|
|
8
|
+
areas: string[];
|
|
9
|
+
dir: string;
|
|
7
10
|
};
|
|
8
|
-
|
|
11
|
+
type Context = {
|
|
9
12
|
getOptions(): LoaderOptions;
|
|
10
|
-
|
|
13
|
+
resourcePath?: string;
|
|
14
|
+
rootContext?: string;
|
|
15
|
+
};
|
|
16
|
+
export default function loader(this: Context, source: string): string;
|
|
17
|
+
export {};
|
|
11
18
|
//# sourceMappingURL=next-loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-loader.d.ts","names":[],"sources":["../src/next-loader.ts"],"mappings":"A,
|
|
1
|
+
{"version":3,"file":"next-loader.d.ts","names":[],"sources":["../src/next-loader.ts"],"mappings":"A,YAaY;EAAkB;;EAAmB;;EAAsB;;EAAqB;EAAiB;;KAExG;EAAY,cAAc;EAAe;EAAuB;;wBAE7C,OAAO,MAAM,SAAS"}
|
package/dist/next-loader.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as relativeImport, f as importingAreas, i as areaFile, o as areasNamed, v as notifyingLocale } from "./bundle-Ut6Y2X6N.js";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
2
3
|
// src/next-loader.ts
|
|
3
4
|
/**
|
|
4
5
|
* The Turbopack loader `prosebook/next` sets on the generated modules. In a build it hands over a
|
|
5
6
|
* locale module `withProsebook` has already pruned, since that reads the source once for every
|
|
6
7
|
* locale. In dev, in the browser, it puts what `withProsebook` hands it before the index (the hot
|
|
7
8
|
* hooks, and the inspector), and makes each locale module update the copies holding it whenever Fast
|
|
8
|
-
* Refresh runs it again.
|
|
9
|
+
* Refresh runs it again. With `split`, it also runs over the app's own source in the browser, and
|
|
10
|
+
* appends to each file an import of every area whose keys it names, from the folder of area files
|
|
11
|
+
* `withProsebook` writes.
|
|
9
12
|
*/
|
|
10
13
|
function loader(source) {
|
|
14
|
+
const { resourcePath = "", rootContext = process.cwd() } = this;
|
|
11
15
|
const options = this.getOptions();
|
|
12
16
|
if ("code" in options) return options.code;
|
|
13
|
-
|
|
17
|
+
if ("prepend" in options) return options.prepend + source;
|
|
18
|
+
if ("locale" in options) return notifyingLocale(source, options.locale);
|
|
19
|
+
const areas = areasNamed(source, new Set(options.areas));
|
|
20
|
+
if (areas.length === 0) return source;
|
|
21
|
+
const file = isAbsolute(resourcePath) ? resourcePath : resolve(rootContext, resourcePath);
|
|
22
|
+
return importingAreas(source, file, areas, (area) => relativeImport(dirname(file), join(options.dir, areaFile(area))));
|
|
14
23
|
}
|
|
15
24
|
export { loader as default };
|
|
16
25
|
|
package/dist/next-loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-loader.js","names":[],"sources":["../src/next-loader.ts"],"sourcesContent":["/**\n * The Turbopack loader `prosebook/next` sets on the generated modules. In a build it hands over a\n * locale module `withProsebook` has already pruned, since that reads the source once for every\n * locale. In dev, in the browser, it puts what `withProsebook` hands it before the index (the hot\n * hooks, and the inspector), and makes each locale module update the copies holding it whenever Fast\n * Refresh runs it again.\n */\nimport { notifyingLocale } from './tools/bundle.ts';\n\nexport type LoaderOptions = { code: string } | { prepend: string } | { locale: string };\n\
|
|
1
|
+
{"version":3,"file":"next-loader.js","names":[],"sources":["../src/next-loader.ts"],"sourcesContent":["/**\n * The Turbopack loader `prosebook/next` sets on the generated modules. In a build it hands over a\n * locale module `withProsebook` has already pruned, since that reads the source once for every\n * locale. In dev, in the browser, it puts what `withProsebook` hands it before the index (the hot\n * hooks, and the inspector), and makes each locale module update the copies holding it whenever Fast\n * Refresh runs it again. With `split`, it also runs over the app's own source in the browser, and\n * appends to each file an import of every area whose keys it names, from the folder of area files\n * `withProsebook` writes.\n */\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { areaFile, areasNamed, importingAreas, notifyingLocale, relativeImport } from './tools/bundle.ts';\n\nexport type LoaderOptions = { code: string } | { prepend: string } | { locale: string } | { areas: string[]; dir: string };\n\ntype Context = { getOptions(): LoaderOptions; resourcePath?: string; rootContext?: string };\n\nexport default function loader(this: Context, source: string): string {\n const { resourcePath = '', rootContext = process.cwd() } = this;\n const options = this.getOptions();\n if ('code' in options) return options.code;\n if ('prepend' in options) return options.prepend + source;\n if ('locale' in options) return notifyingLocale(source, options.locale);\n const areas = areasNamed(source, new Set(options.areas));\n if (areas.length === 0) return source;\n const file = isAbsolute(resourcePath) ? resourcePath : resolve(rootContext, resourcePath);\n return importingAreas(source, file, areas, (area) => relativeImport(dirname(file), join(options.dir, areaFile(area))));\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAwB,OAAsB,QAAwB;CACpE,MAAM,EAAE,eAAe,IAAI,cAAc,QAAQ,IAAI,MAAM;CAC3D,MAAM,UAAU,KAAK,WAAW;CAChC,IAAI,UAAU,SAAS,OAAO,QAAQ;CACtC,IAAI,aAAa,SAAS,OAAO,QAAQ,UAAU;CACnD,IAAI,YAAY,SAAS,OAAO,gBAAgB,QAAQ,QAAQ,MAAM;CACtE,MAAM,QAAQ,WAAW,QAAQ,IAAI,IAAI,QAAQ,KAAK,CAAC;CACvD,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,OAAO,WAAW,YAAY,IAAI,eAAe,QAAQ,aAAa,YAAY;CACxF,OAAO,eAAe,QAAQ,MAAM,QAAQ,SAAS,eAAe,QAAQ,IAAI,GAAG,KAAK,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC;AACvH"}
|
package/dist/next.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Prune } from "./bundle-
|
|
2
|
-
import { i as ProsebookConfig } from "./config-
|
|
1
|
+
import { t as Prune } from "./bundle-CLHY_97D.js";
|
|
2
|
+
import { i as ProsebookConfig } from "./config-CzaJJQ60.js";
|
|
3
3
|
export interface ProsebookNextOptions {
|
|
4
4
|
/** The config, inline. Without it, `prosebook.config.*` at `root` is read. */
|
|
5
5
|
config?: ProsebookConfig;
|
|
@@ -11,8 +11,16 @@ export interface ProsebookNextOptions {
|
|
|
11
11
|
* every message.
|
|
12
12
|
*/
|
|
13
13
|
prune?: Prune;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. With
|
|
16
|
+
* `split`, their areas load with the catalog.
|
|
17
|
+
*/
|
|
15
18
|
keep?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Split each locale by area in client components, as the Vite plugin's `split` does, so a route's
|
|
21
|
+
* client code brings only the messages it reads. Server components read whole locales.
|
|
22
|
+
*/
|
|
23
|
+
split?: boolean;
|
|
16
24
|
/** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */
|
|
17
25
|
inspect?: boolean;
|
|
18
26
|
}
|
package/dist/next.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.d.ts","names":[],"sources":["../src/next.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"next.d.ts","names":[],"sources":["../src/next.ts"],"mappings":";;iBAwDiB;;EAEf,SAAS;;EAET;;;;;;EAMA,QAAQ;;;;;EAKR;;;;;EAKA;;EAEA;;KAMG,eAAe,MAAM,eAAe;EAAW,eAAe;MAAQ,IAAI,QAAQ;;;;;wBAoBvE,cAAc,kBAC5B,OAAM,IAAI,eAAe,IACzB,UAAS,wBACP,eAAe;EAAW,eAAe;MAAQ,QAAQ"}
|
package/dist/next.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as generate,
|
|
3
|
-
import { r as
|
|
4
|
-
import {
|
|
1
|
+
import { C as unseenMessage, S as splitIndex, T as watchCatalog, _ as membersOf, a as areaModule, c as beside, g as lazyAreas, h as keeper, i as areaFile, m as installInspector, p as inAreas, r as HOT_SET, s as areasOf, u as fileSpecifiers, x as scanSources, y as prunedModule } from "./bundle-Ut6Y2X6N.js";
|
|
2
|
+
import { T as CatalogError, n as generate, o as writeFiles } from "./generate-CKv86Iki.js";
|
|
3
|
+
import { i as areaGroups, n as nodeMiddleware, r as redirect, t as inspectorEndpoints } from "./endpoints-Dhgowovz.js";
|
|
4
|
+
import { d as compileLocale, f as localeExpression } from "./scan-C0ap4Mpp.js";
|
|
5
|
+
import { r as loadConfig } from "./config-7T4hX_f7.js";
|
|
5
6
|
import { join, relative, resolve } from "node:path";
|
|
6
7
|
import { createServer } from "node:http";
|
|
7
8
|
// src/next.ts
|
|
@@ -21,6 +22,12 @@ import { createServer } from "node:http";
|
|
|
21
22
|
* route of the app without a file in it, so a server of their own answers them on a local port, and a
|
|
22
23
|
* rewrite sends `/__prosebook/*` there; an entry opens through Next's own `/__nextjs_launch-editor`.
|
|
23
24
|
*
|
|
25
|
+
* With `split`, client components get the catalog split by area, as under Vite (`tools/bundle.ts`).
|
|
26
|
+
* Turbopack has no virtual modules, so the areas are files, written under `node_modules/.prosebook`,
|
|
27
|
+
* where they are a dependency's code to Turbopack and not the app's; and it has no transform hook, so
|
|
28
|
+
* a loader rule over the app's own source in the browser appends the imports of the areas each file
|
|
29
|
+
* names. Server components and the server render read whole locales.
|
|
30
|
+
*
|
|
24
31
|
* Turbopack only: Next 16 fails a Turbopack build whose config has a `webpack` key, so none is added.
|
|
25
32
|
*/
|
|
26
33
|
const DEV = "phase-development-server";
|
|
@@ -43,27 +50,27 @@ function withProsebook(next = {}, options = {}) {
|
|
|
43
50
|
if (config.language === "ts" && config.importExtension === ".js") throw new Error("prosebook: Turbopack doesn't resolve `./en.js` to `en.ts`: set `importExtension: ''` in the prosebook config");
|
|
44
51
|
const generated = generate(config);
|
|
45
52
|
if (phase === DEV) {
|
|
46
|
-
watch(config);
|
|
47
|
-
if (options.inspect === false) return withRules(resolved, hotRules(config, generated, HOT_SET));
|
|
53
|
+
watch(config, options);
|
|
54
|
+
if (options.inspect === false) return withRules(resolved, hotRules(config, generated, HOT_SET, options));
|
|
48
55
|
const base = `${resolved.basePath ?? ""}/`;
|
|
49
56
|
const path = relative(config.out, inspectorPath).replaceAll("\\", "/");
|
|
50
|
-
return withRewrite(withRules(resolved, hotRules(config, generated, HOT_SET + installInspector(path.startsWith(".") ? path : `./${path}`, base))), await serveInspector(config, base));
|
|
57
|
+
return withRewrite(withRules(resolved, hotRules(config, generated, HOT_SET + installInspector(path.startsWith(".") ? path : `./${path}`, base), options)), await serveInspector(config, base));
|
|
51
58
|
}
|
|
52
59
|
const prune = options.prune ?? "strict";
|
|
53
|
-
if (prune === false) return resolved;
|
|
54
|
-
return withRules(resolved,
|
|
60
|
+
if (prune === false && options.split !== true) return resolved;
|
|
61
|
+
return withRules(resolved, buildRules(config, generated, prune, options));
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
/** The app's rules and ours: where both have one for a glob, theirs first, then ours, in a list. */
|
|
57
65
|
function withRules(resolved, rules) {
|
|
58
66
|
const turbopack = resolved.turbopack;
|
|
67
|
+
const all = { ...turbopack?.rules };
|
|
68
|
+
for (const [glob, rule] of Object.entries(rules)) all[glob] = glob in all ? [all[glob], rule].flat() : rule;
|
|
59
69
|
return {
|
|
60
70
|
...resolved,
|
|
61
71
|
turbopack: {
|
|
62
72
|
...turbopack,
|
|
63
|
-
rules:
|
|
64
|
-
...turbopack?.rules,
|
|
65
|
-
...rules
|
|
66
|
-
}
|
|
73
|
+
rules: all
|
|
67
74
|
}
|
|
68
75
|
};
|
|
69
76
|
}
|
|
@@ -114,12 +121,13 @@ function serveInspector(config, base) {
|
|
|
114
121
|
serving.set(config.catalog, port);
|
|
115
122
|
return port;
|
|
116
123
|
}
|
|
117
|
-
function watch(config) {
|
|
124
|
+
function watch(config, options) {
|
|
118
125
|
if (watching.has(config.catalog)) return;
|
|
119
126
|
watching.add(config.catalog);
|
|
120
127
|
watchCatalog(config, () => {
|
|
121
128
|
try {
|
|
122
|
-
generate(config);
|
|
129
|
+
const generated = generate(config);
|
|
130
|
+
if (options.split === true) writeAreas(config, generated);
|
|
123
131
|
console.info(`prosebook: regenerated ${relative(config.root, config.out).replaceAll("\\", "/")}`);
|
|
124
132
|
} catch (e) {
|
|
125
133
|
console.error(describe(e));
|
|
@@ -145,15 +153,80 @@ function rule(config, name, options, browser = false) {
|
|
|
145
153
|
* In the browser: the index starts with `prepend`, the hot hooks and the inspector, there before any
|
|
146
154
|
* copy exists, and each locale updates the copies.
|
|
147
155
|
*/
|
|
148
|
-
function hotRules(config, generated, prepend) {
|
|
156
|
+
function hotRules(config, generated, prepend, options) {
|
|
149
157
|
const locales = [...generated.entries.keys()].map((locale) => rule(config, locale, { locale }, true));
|
|
158
|
+
if (options.split === true) return Object.fromEntries([...locales, ...splitRules(config, generated, options, prepend)]);
|
|
150
159
|
return Object.fromEntries([rule(config, "index", { prepend }, true), ...locales]);
|
|
151
160
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
function buildRules(config, generated, prune, options) {
|
|
162
|
+
let kept = () => true;
|
|
163
|
+
if (prune !== false) {
|
|
164
|
+
const { refs, unseen } = scanSources(config, prune === "strict");
|
|
165
|
+
if (unseen.length > 0) throw new Error(unseenMessage(unseen, config.root));
|
|
166
|
+
kept = keeper(refs, options.keep);
|
|
167
|
+
}
|
|
168
|
+
const locales = prune === false ? [] : [...generated.entries].map(([locale, entries]) => rule(config, locale, { code: prunedModule(entries, kept) }));
|
|
169
|
+
return Object.fromEntries([...locales, ...options.split === true ? splitRules(config, generated, options, "", kept) : []]);
|
|
170
|
+
}
|
|
171
|
+
/** Where a split catalog's area files go: out of the app's source, where Turbopack takes them for a dependency's. */
|
|
172
|
+
const areasDir = (config) => join(config.root, "node_modules", ".prosebook", "areas");
|
|
173
|
+
/**
|
|
174
|
+
* Writes each area's file, and in a build, with `kept`, each lazy locale's part of each group of
|
|
175
|
+
* areas that share a chunk (`tools/chunks.ts`), pruned; in dev an area holds its locales' generated
|
|
176
|
+
* modules whole, so their own updates reach the copies.
|
|
177
|
+
*/
|
|
178
|
+
function writeAreas(config, generated, kept, keep) {
|
|
179
|
+
const dir = areasDir(config);
|
|
180
|
+
const where = fileSpecifiers(dir, dir);
|
|
181
|
+
const inline = kept === void 0 ? void 0 : (_locale, entries) => localeExpression(compileLocale(entries, kept));
|
|
182
|
+
const groups = kept === void 0 ? /* @__PURE__ */ new Map() : areaGroups(config, generated, keep);
|
|
183
|
+
const files = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const area of areasOf(generated)) files.set(join(dir, areaFile(area)), areaModule(config, generated, area, inline, where, groups));
|
|
185
|
+
if (kept === void 0) {
|
|
186
|
+
writeFiles(files);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const lead of new Set(groups.values())) for (const [locale, entries] of generated.entries) {
|
|
190
|
+
const parts = inAreas(entries, membersOf(groups, lead));
|
|
191
|
+
if (parts.length > 0 && locale !== config.source && !config.bundled.includes(locale)) files.set(join(dir, areaFile(lead, locale)), prunedModule(parts, kept));
|
|
192
|
+
}
|
|
193
|
+
writeFiles(files);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* In the browser: the index of a split catalog after `prepend`, and a rule per kind of source file
|
|
197
|
+
* under `scan.dirs` that names an area's keys, which appends its imports of those areas.
|
|
198
|
+
*/
|
|
199
|
+
function splitRules(config, generated, options, prepend, kept) {
|
|
200
|
+
writeAreas(config, generated, kept, options.keep);
|
|
201
|
+
const index = rule(config, "index", { code: prepend + splitIndex(config, generated, options.keep, fileSpecifiers(config.out, areasDir(config))) }, true);
|
|
202
|
+
const areas = [...lazyAreas(generated, options.keep)];
|
|
203
|
+
if (areas.length === 0) return [index];
|
|
204
|
+
const under = (dir) => escape(relative(config.root, dir).replaceAll("\\", "/"));
|
|
205
|
+
const dirs = config.scan.dirs.map(under).filter((d) => d !== "");
|
|
206
|
+
const condition = { all: [
|
|
207
|
+
"browser",
|
|
208
|
+
{ not: "foreign" },
|
|
209
|
+
...dirs.length > 0 && dirs.length === config.scan.dirs.length ? [{ path: new RegExp(`(?:^|/)(?:${dirs.join("|")})/`) }] : [],
|
|
210
|
+
{ not: { path: new RegExp(`(?:^|/)${under(config.out)}/`) } },
|
|
211
|
+
{ content: new RegExp(`['"\`](?:${areas.map(escape).join("|")})\\.`) }
|
|
212
|
+
] };
|
|
213
|
+
const loaderOptions = {
|
|
214
|
+
areas,
|
|
215
|
+
dir: areasDir(config)
|
|
216
|
+
};
|
|
217
|
+
return [index, ...[
|
|
218
|
+
"*.ts",
|
|
219
|
+
"*.tsx",
|
|
220
|
+
"*.js",
|
|
221
|
+
"*.jsx"
|
|
222
|
+
].map((glob) => [glob, {
|
|
223
|
+
condition,
|
|
224
|
+
loaders: [{
|
|
225
|
+
loader: loaderPath,
|
|
226
|
+
options: loaderOptions
|
|
227
|
+
}],
|
|
228
|
+
as: glob
|
|
229
|
+
}])];
|
|
157
230
|
}
|
|
158
231
|
export { withProsebook as default, withProsebook };
|
|
159
232
|
|
package/dist/next.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.js","names":[],"sources":["../src/next.ts"],"sourcesContent":["/**\n * `prosebook/next`: the catalog as part of a Next.js app.\n *\n * Generates when `next dev` or `next build` loads the config, and in dev again whenever a catalog or\n * translation file changes, so Fast Refresh picks up the new modules and a broken entry shows in the\n * terminal. Server components read the new text on their next render; in the browser, each locale\n * module hands its new messages to the copies holding it, which re-render in the locale and state\n * they were in. `next build` ships only the messages the code names: each locale module is handed to a\n * Turbopack loader (`next-loader.ts`) that returns it pruned. Turbopack loads a module before it has\n * seen the code that reads it, so the keys come from the source under `scan.dirs` (`tools/bundle.ts`),\n * read here once, and each pruned module travels to the loader as its options.\n *\n * In dev the browser's index also starts the inspector (`inspector.ts`). Its endpoints can't be a\n * route of the app without a file in it, so a server of their own answers them on a local port, and a\n * rewrite sends `/__prosebook/*` there; an entry opens through Next's own `/__nextjs_launch-editor`.\n *\n * Turbopack only: Next 16 fails a Turbopack build whose config has a `webpack` key, so none is added.\n */\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { join, relative, resolve } from 'node:path';\n\nimport type { LoaderOptions } from './next-loader.ts';\nimport {\n beside,\n HOT_SET,\n installInspector,\n keeper,\n prunedModule,\n scanSources,\n unseenMessage,\n watchCatalog,\n type Prune,\n} from './tools/bundle.ts';\nimport { CatalogError } from './tools/catalog.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';\n\nexport interface ProsebookNextOptions {\n /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */\n config?: ProsebookConfig;\n /** The folder the config's paths resolve from, and where its file is found: the Next app's when unset. */\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)`. `loose` prunes without that check; `false` ships\n * 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\ntype Rules = { [glob: string]: unknown };\ntype Rewrite = { source: string; destination: string };\ntype Rewrites = Rewrite[] | { beforeFiles?: Rewrite[]; afterFiles?: Rewrite[]; fallback?: Rewrite[] };\ntype ConfigFunction<C> = (phase: string, context: { defaultConfig: C }) => C | Promise<C>;\n\nconst DEV = 'phase-development-server';\nconst BUILD = 'phase-production-build';\n\nconst loaderPath = beside(import.meta.url, 'next-loader');\nconst inspectorPath = beside(import.meta.url, 'inspector');\n\n// Next loads its config more than once in a process; one watcher and one inspector server per catalog\n// is enough.\nconst watching = new Set<string>();\nconst serving = new Map<string, Promise<number>>();\n\nconst describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\nconst escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Wraps the Next config: `export default withProsebook({ ... })`. Takes the config as an object or as\n * the function Next calls with the phase, and returns the function.\n */\nexport function withProsebook<C extends object>(\n next: C | ConfigFunction<C> = {} as C,\n options: ProsebookNextOptions = {},\n): (phase: string, context: { defaultConfig: C }) => Promise<C> {\n return async (phase, context) => {\n const resolved = typeof next === 'function' ? await next(phase, context) : next;\n if (phase !== DEV && phase !== BUILD) return resolved;\n const config = await loadConfig(resolve(options.root ?? process.cwd()), options.config);\n if (config.language === 'ts' && config.importExtension === '.js')\n throw new Error(\"prosebook: Turbopack doesn't resolve `./en.js` to `en.ts`: set `importExtension: ''` in the prosebook config\");\n const generated = generate(config);\n if (phase === DEV) {\n watch(config);\n if (options.inspect === false) return withRules(resolved, hotRules(config, generated, HOT_SET));\n const base = `${(resolved as { basePath?: string }).basePath ?? ''}/`;\n // A relative import, which Turbopack resolves as it would the app's own.\n const path = relative(config.out, inspectorPath).replaceAll('\\\\', '/');\n const prepend = HOT_SET + installInspector(path.startsWith('.') ? path : `./${path}`, base);\n return withRewrite(withRules(resolved, hotRules(config, generated, prepend)), await serveInspector(config, base));\n }\n const prune = options.prune ?? 'strict';\n if (prune === false) return resolved;\n return withRules(resolved, prunedRules(config, generated, prune, options.keep));\n };\n}\n\nfunction withRules<C extends object>(resolved: C, rules: Rules): C {\n const turbopack = (resolved as { turbopack?: { rules?: Rules } }).turbopack;\n return { ...resolved, turbopack: { ...turbopack, rules: { ...turbopack?.rules, ...rules } } };\n}\n\n/** Sends `/__prosebook/*` to the inspector's server, before any of the app's own rewrites. */\nfunction withRewrite<C extends object>(resolved: C, port: number): C {\n const theirs = (resolved as { rewrites?: () => Promise<Rewrites> }).rewrites;\n const ours = [{ source: '/__prosebook/:path*', destination: `http://127.0.0.1:${port}/__prosebook/:path*` }];\n const rewrites = async (): Promise<Rewrites> => {\n const r = (await theirs?.()) ?? [];\n return Array.isArray(r) ? { beforeFiles: ours, afterFiles: r } : { ...r, beforeFiles: [...ours, ...(r.beforeFiles ?? [])] };\n };\n return { ...resolved, rewrites };\n}\n\n/**\n * Serves the inspector's endpoints on a free local port, for as long as the process lives. Next's\n * rewrite forwards the page's host, so an edit is still checked against the page's origin.\n */\nfunction serveInspector(config: ResolvedConfig, base: string): Promise<number> {\n let port = serving.get(config.catalog);\n if (port !== undefined) return port;\n const handle = inspectorEndpoints(config, {\n open: ({ file, line, column }) =>\n redirect(`${base}__nextjs_launch-editor?${new URLSearchParams({ file, line1: String(line), column1: String(column) })}`),\n });\n const middleware = nodeMiddleware(handle);\n const server = createServer((req, res) =>\n middleware(req, res, () => {\n res.statusCode = 404;\n res.end();\n }),\n );\n port = new Promise<number>((done, fail) => {\n server.once('error', fail);\n server.listen(0, '127.0.0.1', () => done((server.address() as AddressInfo).port));\n });\n server.unref();\n serving.set(config.catalog, port);\n return port;\n}\n\nfunction watch(config: ResolvedConfig): void {\n if (watching.has(config.catalog)) return;\n watching.add(config.catalog);\n watchCatalog(config, () => {\n try {\n generate(config);\n console.info(`prosebook: regenerated ${relative(config.root, config.out).replaceAll('\\\\', '/')}`);\n } catch (e) {\n console.error(describe(e));\n }\n });\n}\n\n/**\n * A rule per generated module: matched by its file name, then by its path, which Turbopack gives from\n * its own root, so only the end is known.\n */\nfunction rule(config: ResolvedConfig, name: string, options: LoaderOptions, browser = false): [string, unknown] {\n const file = `${name}.${config.language === 'ts' ? 'ts' : 'js'}`;\n const path = { path: new RegExp(`(?:^|/)${escape(join(relative(config.root, config.out), file).replaceAll('\\\\', '/'))}$`) };\n return [file, { condition: browser ? { all: ['browser', path] } : path, loaders: [{ loader: loaderPath, options }] }];\n}\n\n/**\n * In the browser: the index starts with `prepend`, the hot hooks and the inspector, there before any\n * copy exists, and each locale updates the copies.\n */\nfunction hotRules(config: ResolvedConfig, generated: Generated, prepend: string): Rules {\n const locales = [...generated.entries.keys()].map((locale) => rule(config, locale, { locale }, true));\n return Object.fromEntries([rule(config, 'index', { prepend }, true), ...locales]);\n}\n\nfunction prunedRules(config: ResolvedConfig, generated: Generated, prune: 'strict' | 'loose', keep?: string[]): Rules {\n const { refs, unseen } = scanSources(config, prune === 'strict');\n if (unseen.length > 0) throw new Error(unseenMessage(unseen, config.root));\n const kept = keeper(refs, keep);\n return Object.fromEntries([...generated.entries].map(([locale, entries]) => rule(config, locale, { code: prunedModule(entries, kept) })));\n}\n\nexport default withProsebook;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"next.js","names":[],"sources":["../src/next.ts"],"sourcesContent":["/**\n * `prosebook/next`: the catalog as part of a Next.js app.\n *\n * Generates when `next dev` or `next build` loads the config, and in dev again whenever a catalog or\n * translation file changes, so Fast Refresh picks up the new modules and a broken entry shows in the\n * terminal. Server components read the new text on their next render; in the browser, each locale\n * module hands its new messages to the copies holding it, which re-render in the locale and state\n * they were in. `next build` ships only the messages the code names: each locale module is handed to a\n * Turbopack loader (`next-loader.ts`) that returns it pruned. Turbopack loads a module before it has\n * seen the code that reads it, so the keys come from the source under `scan.dirs` (`tools/bundle.ts`),\n * read here once, and each pruned module travels to the loader as its options.\n *\n * In dev the browser's index also starts the inspector (`inspector.ts`). Its endpoints can't be a\n * route of the app without a file in it, so a server of their own answers them on a local port, and a\n * rewrite sends `/__prosebook/*` there; an entry opens through Next's own `/__nextjs_launch-editor`.\n *\n * With `split`, client components get the catalog split by area, as under Vite (`tools/bundle.ts`).\n * Turbopack has no virtual modules, so the areas are files, written under `node_modules/.prosebook`,\n * where they are a dependency's code to Turbopack and not the app's; and it has no transform hook, so\n * a loader rule over the app's own source in the browser appends the imports of the areas each file\n * names. Server components and the server render read whole locales.\n *\n * Turbopack only: Next 16 fails a Turbopack build whose config has a `webpack` key, so none is added.\n */\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { join, relative, resolve } from 'node:path';\n\nimport type { LoaderOptions } from './next-loader.ts';\nimport {\n areaFile,\n areaModule,\n areasOf,\n beside,\n fileSpecifiers,\n HOT_SET,\n inAreas,\n installInspector,\n keeper,\n lazyAreas,\n membersOf,\n prunedModule,\n scanSources,\n splitIndex,\n unseenMessage,\n watchCatalog,\n type Prune,\n} from './tools/bundle.ts';\nimport { CatalogError } from './tools/catalog.ts';\nimport { areaGroups } from './tools/chunks.ts';\nimport { compileLocale, localeExpression } from './tools/compile.ts';\nimport type { Entry } from './tools/compile.ts';\nimport { loadConfig, type ProsebookConfig, type ResolvedConfig } from './tools/config.ts';\nimport { inspectorEndpoints, nodeMiddleware, redirect } from './tools/endpoints.ts';\nimport { generate, writeFiles, type Generated } from './tools/generate.ts';\n\nexport interface ProsebookNextOptions {\n /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */\n config?: ProsebookConfig;\n /** The folder the config's paths resolve from, and where its file is found: the Next app's when unset. */\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)`. `loose` prunes without that check; `false` ships\n * every message.\n */\n prune?: Prune;\n /**\n * Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. With\n * `split`, their areas load with the catalog.\n */\n keep?: string[];\n /**\n * Split each locale by area in client components, as the Vite plugin's `split` does, so a route's\n * client code brings only the messages it reads. Server components read whole locales.\n */\n split?: boolean;\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\ntype Rules = { [glob: string]: unknown };\ntype Rewrite = { source: string; destination: string };\ntype Rewrites = Rewrite[] | { beforeFiles?: Rewrite[]; afterFiles?: Rewrite[]; fallback?: Rewrite[] };\ntype ConfigFunction<C> = (phase: string, context: { defaultConfig: C }) => C | Promise<C>;\n\nconst DEV = 'phase-development-server';\nconst BUILD = 'phase-production-build';\n\nconst loaderPath = beside(import.meta.url, 'next-loader');\nconst inspectorPath = beside(import.meta.url, 'inspector');\n\n// Next loads its config more than once in a process; one watcher and one inspector server per catalog\n// is enough.\nconst watching = new Set<string>();\nconst serving = new Map<string, Promise<number>>();\n\nconst describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\nconst escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Wraps the Next config: `export default withProsebook({ ... })`. Takes the config as an object or as\n * the function Next calls with the phase, and returns the function.\n */\nexport function withProsebook<C extends object>(\n next: C | ConfigFunction<C> = {} as C,\n options: ProsebookNextOptions = {},\n): (phase: string, context: { defaultConfig: C }) => Promise<C> {\n return async (phase, context) => {\n const resolved = typeof next === 'function' ? await next(phase, context) : next;\n if (phase !== DEV && phase !== BUILD) return resolved;\n const config = await loadConfig(resolve(options.root ?? process.cwd()), options.config);\n if (config.language === 'ts' && config.importExtension === '.js')\n throw new Error(\"prosebook: Turbopack doesn't resolve `./en.js` to `en.ts`: set `importExtension: ''` in the prosebook config\");\n const generated = generate(config);\n if (phase === DEV) {\n watch(config, options);\n if (options.inspect === false) return withRules(resolved, hotRules(config, generated, HOT_SET, options));\n const base = `${(resolved as { basePath?: string }).basePath ?? ''}/`;\n // A relative import, which Turbopack resolves as it would the app's own.\n const path = relative(config.out, inspectorPath).replaceAll('\\\\', '/');\n const prepend = HOT_SET + installInspector(path.startsWith('.') ? path : `./${path}`, base);\n return withRewrite(withRules(resolved, hotRules(config, generated, prepend, options)), await serveInspector(config, base));\n }\n const prune = options.prune ?? 'strict';\n if (prune === false && options.split !== true) return resolved;\n return withRules(resolved, buildRules(config, generated, prune, options));\n };\n}\n\n/** The app's rules and ours: where both have one for a glob, theirs first, then ours, in a list. */\nfunction withRules<C extends object>(resolved: C, rules: Rules): C {\n const turbopack = (resolved as { turbopack?: { rules?: Rules } }).turbopack;\n const all: Rules = { ...turbopack?.rules };\n for (const [glob, rule] of Object.entries(rules)) all[glob] = glob in all ? [all[glob], rule].flat() : rule;\n return { ...resolved, turbopack: { ...turbopack, rules: all } };\n}\n\n/** Sends `/__prosebook/*` to the inspector's server, before any of the app's own rewrites. */\nfunction withRewrite<C extends object>(resolved: C, port: number): C {\n const theirs = (resolved as { rewrites?: () => Promise<Rewrites> }).rewrites;\n const ours = [{ source: '/__prosebook/:path*', destination: `http://127.0.0.1:${port}/__prosebook/:path*` }];\n const rewrites = async (): Promise<Rewrites> => {\n const r = (await theirs?.()) ?? [];\n return Array.isArray(r) ? { beforeFiles: ours, afterFiles: r } : { ...r, beforeFiles: [...ours, ...(r.beforeFiles ?? [])] };\n };\n return { ...resolved, rewrites };\n}\n\n/**\n * Serves the inspector's endpoints on a free local port, for as long as the process lives. Next's\n * rewrite forwards the page's host, so an edit is still checked against the page's origin.\n */\nfunction serveInspector(config: ResolvedConfig, base: string): Promise<number> {\n let port = serving.get(config.catalog);\n if (port !== undefined) return port;\n const handle = inspectorEndpoints(config, {\n open: ({ file, line, column }) =>\n redirect(`${base}__nextjs_launch-editor?${new URLSearchParams({ file, line1: String(line), column1: String(column) })}`),\n });\n const middleware = nodeMiddleware(handle);\n const server = createServer((req, res) =>\n middleware(req, res, () => {\n res.statusCode = 404;\n res.end();\n }),\n );\n port = new Promise<number>((done, fail) => {\n server.once('error', fail);\n server.listen(0, '127.0.0.1', () => done((server.address() as AddressInfo).port));\n });\n server.unref();\n serving.set(config.catalog, port);\n return port;\n}\n\nfunction watch(config: ResolvedConfig, options: ProsebookNextOptions): void {\n if (watching.has(config.catalog)) return;\n watching.add(config.catalog);\n watchCatalog(config, () => {\n try {\n const generated = generate(config);\n if (options.split === true) writeAreas(config, generated);\n console.info(`prosebook: regenerated ${relative(config.root, config.out).replaceAll('\\\\', '/')}`);\n } catch (e) {\n console.error(describe(e));\n }\n });\n}\n\n/**\n * A rule per generated module: matched by its file name, then by its path, which Turbopack gives from\n * its own root, so only the end is known.\n */\nfunction rule(config: ResolvedConfig, name: string, options: LoaderOptions, browser = false): [string, unknown] {\n const file = `${name}.${config.language === 'ts' ? 'ts' : 'js'}`;\n const path = { path: new RegExp(`(?:^|/)${escape(join(relative(config.root, config.out), file).replaceAll('\\\\', '/'))}$`) };\n return [file, { condition: browser ? { all: ['browser', path] } : path, loaders: [{ loader: loaderPath, options }] }];\n}\n\n/**\n * In the browser: the index starts with `prepend`, the hot hooks and the inspector, there before any\n * copy exists, and each locale updates the copies.\n */\nfunction hotRules(config: ResolvedConfig, generated: Generated, prepend: string, options: ProsebookNextOptions): Rules {\n const locales = [...generated.entries.keys()].map((locale) => rule(config, locale, { locale }, true));\n if (options.split === true) return Object.fromEntries([...locales, ...splitRules(config, generated, options, prepend)]);\n return Object.fromEntries([rule(config, 'index', { prepend }, true), ...locales]);\n}\n\nfunction buildRules(config: ResolvedConfig, generated: Generated, prune: Prune, options: ProsebookNextOptions): Rules {\n let kept: (key: string) => boolean = () => true;\n if (prune !== false) {\n const { refs, unseen } = scanSources(config, prune === 'strict');\n if (unseen.length > 0) throw new Error(unseenMessage(unseen, config.root));\n kept = keeper(refs, options.keep);\n }\n const locales =\n prune === false ? [] : [...generated.entries].map(([locale, entries]) => rule(config, locale, { code: prunedModule(entries, kept) }));\n return Object.fromEntries([...locales, ...(options.split === true ? splitRules(config, generated, options, '', kept) : [])]);\n}\n\n/** Where a split catalog's area files go: out of the app's source, where Turbopack takes them for a dependency's. */\nconst areasDir = (config: ResolvedConfig) => join(config.root, 'node_modules', '.prosebook', 'areas');\n\n/**\n * Writes each area's file, and in a build, with `kept`, each lazy locale's part of each group of\n * areas that share a chunk (`tools/chunks.ts`), pruned; in dev an area holds its locales' generated\n * modules whole, so their own updates reach the copies.\n */\nfunction writeAreas(config: ResolvedConfig, generated: Generated, kept?: (key: string) => boolean, keep?: string[]): void {\n const dir = areasDir(config);\n const where = fileSpecifiers(dir, dir);\n const inline = kept === undefined ? undefined : (_locale: string, entries: Entry[]) => localeExpression(compileLocale(entries, kept));\n const groups = kept === undefined ? new Map<string, string>() : areaGroups(config, generated, keep);\n const files = new Map<string, string>();\n for (const area of areasOf(generated)) files.set(join(dir, areaFile(area)), areaModule(config, generated, area, inline, where, groups));\n if (kept === undefined) {\n writeFiles(files);\n return;\n }\n for (const lead of new Set(groups.values())) {\n for (const [locale, entries] of generated.entries) {\n const parts = inAreas(entries, membersOf(groups, lead));\n if (parts.length > 0 && locale !== config.source && !config.bundled.includes(locale))\n files.set(join(dir, areaFile(lead, locale)), prunedModule(parts, kept));\n }\n }\n writeFiles(files);\n}\n\n/**\n * In the browser: the index of a split catalog after `prepend`, and a rule per kind of source file\n * under `scan.dirs` that names an area's keys, which appends its imports of those areas.\n */\nfunction splitRules(\n config: ResolvedConfig,\n generated: Generated,\n options: ProsebookNextOptions,\n prepend: string,\n kept?: (key: string) => boolean,\n) {\n writeAreas(config, generated, kept, options.keep);\n const index = rule(\n config,\n 'index',\n { code: prepend + splitIndex(config, generated, options.keep, fileSpecifiers(config.out, areasDir(config))) },\n true,\n );\n const areas = [...lazyAreas(generated, options.keep)];\n if (areas.length === 0) return [index];\n const under = (dir: string) => escape(relative(config.root, dir).replaceAll('\\\\', '/'));\n const dirs = config.scan.dirs.map(under).filter((d) => d !== '');\n const condition = {\n all: [\n 'browser',\n { not: 'foreign' },\n ...(dirs.length > 0 && dirs.length === config.scan.dirs.length ? [{ path: new RegExp(`(?:^|/)(?:${dirs.join('|')})/`) }] : []),\n { not: { path: new RegExp(`(?:^|/)${under(config.out)}/`) } },\n // As `keyRefs` reads a key: a quote, then the area.\n { content: new RegExp(`['\"\\`](?:${areas.map(escape).join('|')})\\\\.`) },\n ],\n };\n const loaderOptions: LoaderOptions = { areas, dir: areasDir(config) };\n const sources = ['*.ts', '*.tsx', '*.js', '*.jsx'].map((glob): [string, unknown] => [\n glob,\n { condition, loaders: [{ loader: loaderPath, options: loaderOptions }], as: glob },\n ]);\n return [index, ...sources];\n}\n\nexport default withProsebook;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { r as TextProps$1, t as BoundCopy } from "../bind-ZS86jHXk.js";
|
|
2
|
+
import { T as TagFn, b as RegisteredLocale, n as Copy, x as RegisteredMessages } from "../copy-BXn0aYQn.js";
|
|
3
|
+
import { ComponentChildren, VNode } from "preact";
|
|
4
|
+
type M = RegisteredMessages;
|
|
5
|
+
/** `Copy`, with `rich` returning a `VNode`. */
|
|
6
|
+
export type PreactCopy = BoundCopy<ComponentChildren, VNode>;
|
|
7
|
+
type Tags = {
|
|
8
|
+
readonly [tag: string]: TagFn<ComponentChildren>;
|
|
9
|
+
};
|
|
10
|
+
export interface CopyProviderProps {
|
|
11
|
+
copy: Copy;
|
|
12
|
+
/** Tag renderers every rich read under this provider gets, beside the copy's own. */
|
|
13
|
+
tags?: Tags;
|
|
14
|
+
children?: ComponentChildren;
|
|
15
|
+
}
|
|
16
|
+
export declare function CopyProvider({ copy, tags, children }: CopyProviderProps): VNode<import("preact").Attributes & {
|
|
17
|
+
value: {
|
|
18
|
+
copy: Copy;
|
|
19
|
+
tags: Tags;
|
|
20
|
+
} | null;
|
|
21
|
+
children?: ComponentChildren;
|
|
22
|
+
}>;
|
|
23
|
+
/** The catalog, in the current locale. Renders the component again when the locale changes, or in dev the catalog. */
|
|
24
|
+
export declare function useCopy(): PreactCopy;
|
|
25
|
+
/** The current locale alone, for components that only format or switch. */
|
|
26
|
+
export declare function useLocale(): RegisteredLocale;
|
|
27
|
+
export type TextProps<K extends Extract<keyof M, string>> = TextProps$1<K, ComponentChildren>;
|
|
28
|
+
/**
|
|
29
|
+
* A message as an element: `<Text id="cart.items" values={{ count }} />`. Rich messages render their
|
|
30
|
+
* tags through the provider's renderers and any given in `values`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function Text<K extends Extract<keyof M, string>>(props: TextProps<K>): VNode;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["Props"],"sources":["../../src/preact/index.ts"],"mappings":";;;KAYK,IAAI;;YAGG,aAAa,UAAU,mBAAmB;KAEjD;YAAmB,cAAc,MAAM;;iBAI3B;EACf,MAAM;;EAEN,OAAO;EACP,WAAW;;wBAGG,eAAe,MAAM,MAAW,YAAY,oBAAiB,uBAAA;;IATnC,MAAA;IAAY,MAAA;;;;;wBAoCtC,WAAW;;wBAQX,aAAa;YAiBjB,UAAU,UAAU,cAAc,cAAcA,YAAM,GAAG;;;;;wBAMrD,KAAK,UAAU,cAAc,YAAY,OAAO,UAAU,KAAK"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { t as bindCopy } from "../bind-BIZNv8nU.js";
|
|
2
|
+
import { Fragment, createContext, h, isValidElement } from "preact";
|
|
3
|
+
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
|
4
|
+
// src/preact/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* `prosebook/preact`: the catalog in Preact components, as `prosebook/react` has it in React's.
|
|
7
|
+
*
|
|
8
|
+
* Written with `preact/hooks` alone, so it needs no `preact/compat`.
|
|
9
|
+
*/
|
|
10
|
+
const CopyContext = createContext(null);
|
|
11
|
+
function CopyProvider({ copy, tags = {}, children }) {
|
|
12
|
+
const value = useMemo(() => ({
|
|
13
|
+
copy,
|
|
14
|
+
tags
|
|
15
|
+
}), [copy, tags]);
|
|
16
|
+
return h(CopyContext.Provider, { value }, children);
|
|
17
|
+
}
|
|
18
|
+
function useContextOf(hook) {
|
|
19
|
+
const context = useContext(CopyContext);
|
|
20
|
+
if (context === null) throw new Error(`prosebook: ${hook}() needs a <CopyProvider> above it`);
|
|
21
|
+
return context;
|
|
22
|
+
}
|
|
23
|
+
/** Renders the component again after each change to what the copy reads: a locale switch, or in dev a catalog edit. */
|
|
24
|
+
function useVersion(copy) {
|
|
25
|
+
const [, rerender] = useState(0);
|
|
26
|
+
const version = copy.version;
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const update = () => rerender((n) => n + 1);
|
|
29
|
+
const stop = copy.subscribe(update);
|
|
30
|
+
if (copy.version !== version) update();
|
|
31
|
+
return stop;
|
|
32
|
+
}, [copy]);
|
|
33
|
+
return version;
|
|
34
|
+
}
|
|
35
|
+
/** The catalog, in the current locale. Renders the component again when the locale changes, or in dev the catalog. */
|
|
36
|
+
function useCopy() {
|
|
37
|
+
const { copy, tags } = useContextOf("useCopy");
|
|
38
|
+
const version = useVersion(copy);
|
|
39
|
+
return useMemo(() => bindCopy(copy, tags, {
|
|
40
|
+
tag,
|
|
41
|
+
whole
|
|
42
|
+
}), [
|
|
43
|
+
copy,
|
|
44
|
+
tags,
|
|
45
|
+
version
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
/** The current locale alone, for components that only format or switch. */
|
|
49
|
+
function useLocale() {
|
|
50
|
+
const { copy } = useContextOf("useLocale");
|
|
51
|
+
useVersion(copy);
|
|
52
|
+
return copy.locale;
|
|
53
|
+
}
|
|
54
|
+
const tag = (render, chunks) => render(keyed(chunks));
|
|
55
|
+
const whole = (parts) => h(Fragment, null, keyed(parts));
|
|
56
|
+
/** Text joined where it can be, and each element under its own key, as `prosebook/react` does. */
|
|
57
|
+
function keyed(parts) {
|
|
58
|
+
if (parts.every((p) => typeof p === "string")) return [parts.join("")];
|
|
59
|
+
return parts.map((p, i) => isValidElement(p) || typeof p === "object" ? h(Fragment, { key: i }, p) : p);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A message as an element: `<Text id="cart.items" values={{ count }} />`. Rich messages render their
|
|
63
|
+
* tags through the provider's renderers and any given in `values`.
|
|
64
|
+
*/
|
|
65
|
+
function Text(props) {
|
|
66
|
+
const copy = useCopy();
|
|
67
|
+
const values = props.values;
|
|
68
|
+
return copy.rich(props.id, values);
|
|
69
|
+
}
|
|
70
|
+
export { CopyProvider, Text, useCopy, useLocale };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/preact/index.ts"],"sourcesContent":["/**\n * `prosebook/preact`: the catalog in Preact components, as `prosebook/react` has it in React's.\n *\n * Written with `preact/hooks` alone, so it needs no `preact/compat`.\n */\nimport { createContext, Fragment, h, isValidElement, type ComponentChildren, type VNode } from 'preact';\nimport { useContext, useEffect, useMemo, useState } from 'preact/hooks';\n\nimport { bindCopy, type BoundCopy, type TextProps as Props } from '#src/runtime/bind.ts';\nimport type { Copy } from '#src/runtime/copy.ts';\nimport type { RegisteredLocale, RegisteredMessages, TagFn } from '#src/runtime/types.ts';\n\ntype M = RegisteredMessages;\n\n/** `Copy`, with `rich` returning a `VNode`. */\nexport type PreactCopy = BoundCopy<ComponentChildren, VNode>;\n\ntype Tags = { readonly [tag: string]: TagFn<ComponentChildren> };\n\nconst CopyContext = createContext<{ copy: Copy; tags: Tags } | null>(null);\n\nexport interface CopyProviderProps {\n copy: Copy;\n /** Tag renderers every rich read under this provider gets, beside the copy's own. */\n tags?: Tags;\n children?: ComponentChildren;\n}\n\nexport function CopyProvider({ copy, tags = {}, children }: CopyProviderProps) {\n const value = useMemo(() => ({ copy, tags }), [copy, tags]);\n return h(CopyContext.Provider, { value }, children);\n}\n\nfunction useContextOf(hook: string) {\n const context = useContext(CopyContext);\n if (context === null) throw new Error(`prosebook: ${hook}() needs a <CopyProvider> above it`);\n return context;\n}\n\n/** Renders the component again after each change to what the copy reads: a locale switch, or in dev a catalog edit. */\nfunction useVersion(copy: Copy): number {\n const [, rerender] = useState(0);\n const version = copy.version;\n // Preact has no useSyncExternalStore outside compat: the effect subscribes once the component mounts.\n useEffect(() => {\n const update = () => rerender((n) => n + 1);\n const stop = copy.subscribe(update);\n // A change between this render and the subscription would otherwise go unseen.\n if (copy.version !== version) update();\n return stop;\n }, [copy]);\n return version;\n}\n\n/** The catalog, in the current locale. Renders the component again when the locale changes, or in dev the catalog. */\nexport function useCopy(): PreactCopy {\n const { copy, tags } = useContextOf('useCopy');\n const version = useVersion(copy);\n // Rebuilt on each version, so its identity changes when what it reads does.\n return useMemo(() => bindCopy(copy, tags, { tag, whole }), [copy, tags, version]);\n}\n\n/** The current locale alone, for components that only format or switch. */\nexport function useLocale(): RegisteredLocale {\n const { copy } = useContextOf('useLocale');\n useVersion(copy);\n return copy.locale;\n}\n\nconst tag = (render: (chunks: never) => unknown, chunks: unknown[]) => render(keyed(chunks) as never);\nconst whole = (parts: unknown[]): VNode => h(Fragment, null, keyed(parts));\n\n/** Text joined where it can be, and each element under its own key, as `prosebook/react` does. */\nfunction keyed(parts: unknown[]): ComponentChildren[] {\n if (parts.every((p) => typeof p === 'string')) return [parts.join('')];\n return parts.map((p, i) =>\n isValidElement(p) || typeof p === 'object' ? h(Fragment, { key: i }, p as ComponentChildren) : (p as ComponentChildren),\n );\n}\n\nexport type TextProps<K extends Extract<keyof M, string>> = Props<K, ComponentChildren>;\n\n/**\n * A message as an element: `<Text id=\"cart.items\" values={{ count }} />`. Rich messages render their\n * tags through the provider's renderers and any given in `values`.\n */\nexport function Text<K extends Extract<keyof M, string>>(props: TextProps<K>): VNode {\n const copy = useCopy();\n const values = (props as { values?: object }).values;\n return copy.rich(props.id as never, values as never);\n}\n"],"mappings":";;;;;;;;;AAmBA,MAAM,cAAc,cAAiD,IAAI;AASzE,SAAgB,aAAa,EAAE,MAAM,OAAO,CAAC,GAAG,YAA+B;CAC7E,MAAM,QAAQ,eAAe;EAAE;EAAM;CAAK,IAAI,CAAC,MAAM,IAAI,CAAC;CAC1D,OAAO,EAAE,YAAY,UAAU,EAAE,MAAM,GAAG,QAAQ;AACpD;AAEA,SAAS,aAAa,MAAc;CAClC,MAAM,UAAU,WAAW,WAAW;CACtC,IAAI,YAAY,MAAM,MAAM,IAAI,MAAM,cAAc,KAAK,mCAAmC;CAC5F,OAAO;AACT;;AAGA,SAAS,WAAW,MAAoB;CACtC,MAAM,GAAG,YAAY,SAAS,CAAC;CAC/B,MAAM,UAAU,KAAK;CAErB,gBAAgB;EACd,MAAM,eAAe,UAAU,MAAM,IAAI,CAAC;EAC1C,MAAM,OAAO,KAAK,UAAU,MAAM;EAElC,IAAI,KAAK,YAAY,SAAS,OAAO;EACrC,OAAO;CACT,GAAG,CAAC,IAAI,CAAC;CACT,OAAO;AACT;;AAGA,SAAgB,UAAsB;CACpC,MAAM,EAAE,MAAM,SAAS,aAAa,SAAS;CAC7C,MAAM,UAAU,WAAW,IAAI;CAE/B,OAAO,cAAc,SAAS,MAAM,MAAM;EAAE;EAAK;CAAM,CAAC,GAAG;EAAC;EAAM;EAAM;CAAO,CAAC;AAClF;;AAGA,SAAgB,YAA8B;CAC5C,MAAM,EAAE,SAAS,aAAa,WAAW;CACzC,WAAW,IAAI;CACf,OAAO,KAAK;AACd;AAEA,MAAM,OAAO,QAAoC,WAAsB,OAAO,MAAM,MAAM,CAAU;AACpG,MAAM,SAAS,UAA4B,EAAE,UAAU,MAAM,MAAM,KAAK,CAAC;;AAGzE,SAAS,MAAM,OAAuC;CACpD,IAAI,MAAM,OAAO,MAAM,OAAO,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,EAAE,CAAC;CACrE,OAAO,MAAM,KAAK,GAAG,MACnB,eAAe,CAAC,KAAK,OAAO,MAAM,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,CAAsB,IAAK,CAClG;AACF;;;;;AAQA,SAAgB,KAAyC,OAA4B;CACnF,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAU,MAA8B;CAC9C,OAAO,KAAK,KAAK,MAAM,IAAa,MAAe;AACrD"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as TextProps$1, t as BoundCopy } from "../bind-ZS86jHXk.js";
|
|
2
|
+
import { T as TagFn, b as RegisteredLocale, n as Copy, x as RegisteredMessages } from "../copy-BXn0aYQn.js";
|
|
2
3
|
import { ReactNode } from "react";
|
|
3
4
|
type M = RegisteredMessages;
|
|
4
|
-
type L = RegisteredLocale;
|
|
5
5
|
/** `Copy`, with `rich` returning a `ReactNode`. */
|
|
6
|
-
export
|
|
7
|
-
rich<K extends RichKey<M>>(this: void, key: K, ...values: RichParam<M[K], ReactNode>): ReactNode;
|
|
8
|
-
}
|
|
6
|
+
export type ReactCopy = BoundCopy<ReactNode>;
|
|
9
7
|
type Tags = {
|
|
10
8
|
readonly [tag: string]: TagFn<ReactNode>;
|
|
11
9
|
};
|
|
@@ -19,15 +17,8 @@ export declare function CopyProvider({ copy, tags, children }: CopyProviderProps
|
|
|
19
17
|
/** The catalog, in the current locale. Re-renders the component when the locale changes, or in dev the catalog. */
|
|
20
18
|
export declare function useCopy(): ReactCopy;
|
|
21
19
|
/** The current locale alone, for components that only format or switch. */
|
|
22
|
-
export declare function useLocale():
|
|
23
|
-
type
|
|
24
|
-
values?: S['args'] & { [T in S['tags']]?: TagFn<ReactNode>; };
|
|
25
|
-
} : {
|
|
26
|
-
values: RichParam<S, ReactNode>[0];
|
|
27
|
-
};
|
|
28
|
-
export type TextProps<K extends Extract<keyof M, string>> = {
|
|
29
|
-
id: K;
|
|
30
|
-
} & ValuesProp<M[K]>;
|
|
20
|
+
export declare function useLocale(): RegisteredLocale;
|
|
21
|
+
export type TextProps<K extends Extract<keyof M, string>> = TextProps$1<K, ReactNode>;
|
|
31
22
|
/**
|
|
32
23
|
* A message as an element: `<Text id="cart.items" values={{ count }} />`. Rich messages render their
|
|
33
24
|
* tags through the provider's renderers and any given in `values`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/react/index.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["Props"],"sources":["../../src/react/index.tsx"],"mappings":";;;KAaK,IAAI;;YAGG,YAAY,UAAU;KAI7B;YAAmB,cAAc,MAAM;;iBAE3B;EACf,MAAM;;EAEN,OAAO;EACP,WAAW;;wBAGG,eAAe,MAAM,MAAW,YAAY,oCAAiB,IAAA;;wBAM7D,WAAW;;wBAcX,aAAa;YAsBjB,UAAU,UAAU,cAAc,cAAcA,YAAM,GAAG;;;;;wBAMrD,KAAK,UAAU,cAAc,YAAY,OAAO,UAAU,qBAAE,IAAA"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as bindCopy } from "../bind-BIZNv8nU.js";
|
|
1
2
|
import { Fragment, createContext, isValidElement, use, useMemo, useSyncExternalStore } from "react";
|
|
2
3
|
import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
|
|
3
4
|
// src/react/index.tsx
|
|
@@ -9,13 +10,10 @@ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
|
|
|
9
10
|
* link inside it is one catalog entry and one call, with no wrapper to remember.
|
|
10
11
|
*/
|
|
11
12
|
const CopyContext = createContext(null);
|
|
12
|
-
function CopyProvider({ copy, tags, children }) {
|
|
13
|
+
function CopyProvider({ copy, tags = {}, children }) {
|
|
13
14
|
const value = useMemo(() => ({
|
|
14
15
|
copy,
|
|
15
|
-
tags
|
|
16
|
-
...copy.tags,
|
|
17
|
-
...tags
|
|
18
|
-
}
|
|
16
|
+
tags
|
|
19
17
|
}), [copy, tags]);
|
|
20
18
|
return /* @__PURE__ */ jsx(CopyContext, {
|
|
21
19
|
value,
|
|
@@ -28,7 +26,10 @@ function useCopy() {
|
|
|
28
26
|
if (context === null) throw new Error("prosebook: useCopy() needs a <CopyProvider> above it");
|
|
29
27
|
const { copy, tags } = context;
|
|
30
28
|
const version = useSyncExternalStore(copy.subscribe, () => copy.version, () => copy.version);
|
|
31
|
-
return useMemo(() =>
|
|
29
|
+
return useMemo(() => bindCopy(copy, tags, {
|
|
30
|
+
tag,
|
|
31
|
+
whole
|
|
32
|
+
}), [
|
|
32
33
|
copy,
|
|
33
34
|
tags,
|
|
34
35
|
version
|
|
@@ -40,33 +41,8 @@ function useLocale() {
|
|
|
40
41
|
if (context === null) throw new Error("prosebook: useLocale() needs a <CopyProvider> above it");
|
|
41
42
|
return useSyncExternalStore(context.copy.subscribe, () => context.copy.locale, () => context.copy.locale);
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
locale: copy.locale,
|
|
46
|
-
version,
|
|
47
|
-
locales: copy.locales,
|
|
48
|
-
source: copy.source,
|
|
49
|
-
tags: copy.tags,
|
|
50
|
-
ready: copy.ready,
|
|
51
|
-
t: copy.t,
|
|
52
|
-
has: copy.has,
|
|
53
|
-
setLocale: copy.setLocale,
|
|
54
|
-
subscribe: copy.subscribe,
|
|
55
|
-
number: copy.number,
|
|
56
|
-
date: copy.date,
|
|
57
|
-
list: copy.list,
|
|
58
|
-
relative: copy.relative,
|
|
59
|
-
rich(key, ...values) {
|
|
60
|
-
const given = values[0] ?? {};
|
|
61
|
-
const merged = {
|
|
62
|
-
...tags,
|
|
63
|
-
...given
|
|
64
|
-
};
|
|
65
|
-
for (const [name, value] of Object.entries(merged)) if (typeof value === "function") merged[name] = (chunks) => value(keyed(chunks));
|
|
66
|
-
return /* @__PURE__ */ jsx(Fragment$1, { children: keyed(copy.rich(key, merged)) });
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
44
|
+
const tag = (render, chunks) => render(keyed(chunks));
|
|
45
|
+
const whole = (parts) => /* @__PURE__ */ jsx(Fragment$1, { children: keyed(parts) });
|
|
70
46
|
/** Text joined where it can be, and each element under its own key, so React never warns about a list. */
|
|
71
47
|
function keyed(parts) {
|
|
72
48
|
if (parts.every((p) => typeof p === "string")) return [parts.join("")];
|