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.
Files changed (125) hide show
  1. package/README.md +357 -26
  2. package/dist/bind-BIZNv8nU.js +62 -0
  3. package/dist/bind-BIZNv8nU.js.map +1 -0
  4. package/dist/bind-ZS86jHXk.d.ts +31 -0
  5. package/dist/bind-ZS86jHXk.d.ts.map +1 -0
  6. package/dist/bun.d.ts +12 -3
  7. package/dist/bun.d.ts.map +1 -1
  8. package/dist/bun.js +77 -26
  9. package/dist/bun.js.map +1 -1
  10. package/dist/{bundle-KD-DiiFa.d.ts → bundle-CLHY_97D.d.ts} +3 -3
  11. package/dist/bundle-CLHY_97D.d.ts.map +1 -0
  12. package/dist/bundle-Ut6Y2X6N.js +217 -0
  13. package/dist/bundle-Ut6Y2X6N.js.map +1 -0
  14. package/dist/cli.js +55 -12
  15. package/dist/cli.js.map +1 -1
  16. package/dist/{config-BPSmoYel.js → config-7T4hX_f7.js} +13 -5
  17. package/dist/{config-BPSmoYel.js.map → config-7T4hX_f7.js.map} +1 -1
  18. package/dist/{config-DdMkKt_N.d.ts → config-CzaJJQ60.d.ts} +19 -5
  19. package/dist/{config-DdMkKt_N.d.ts.map → config-CzaJJQ60.d.ts.map} +1 -1
  20. package/dist/config.d.ts +1 -1
  21. package/dist/config.js +1 -1
  22. package/dist/{copy-B3V_QRkT.d.ts → copy-BXn0aYQn.d.ts} +37 -3
  23. package/dist/copy-BXn0aYQn.d.ts.map +1 -0
  24. package/dist/copy-Bv6S-Hr2.js +235 -0
  25. package/dist/copy-Bv6S-Hr2.js.map +1 -0
  26. package/dist/endpoints-Dhgowovz.js +217 -0
  27. package/dist/endpoints-Dhgowovz.js.map +1 -0
  28. package/dist/generate-1BeQAd1O.d.ts +72 -0
  29. package/dist/generate-1BeQAd1O.d.ts.map +1 -0
  30. package/dist/{generate-9pR3i5Bj.js → generate-CKv86Iki.js} +1724 -627
  31. package/dist/generate-CKv86Iki.js.map +1 -0
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +2 -2
  34. package/dist/next-loader.d.ts +9 -2
  35. package/dist/next-loader.d.ts.map +1 -1
  36. package/dist/next-loader.js +12 -3
  37. package/dist/next-loader.js.map +1 -1
  38. package/dist/next.d.ts +11 -3
  39. package/dist/next.d.ts.map +1 -1
  40. package/dist/next.js +94 -21
  41. package/dist/next.js.map +1 -1
  42. package/dist/preact/index.d.ts +34 -0
  43. package/dist/preact/index.d.ts.map +1 -0
  44. package/dist/preact/index.js +72 -0
  45. package/dist/preact/index.js.map +1 -0
  46. package/dist/react/index.d.ts +5 -14
  47. package/dist/react/index.d.ts.map +1 -1
  48. package/dist/react/index.js +9 -33
  49. package/dist/react/index.js.map +1 -1
  50. package/dist/react/server.d.ts +1 -1
  51. package/dist/react/server.js +1 -1
  52. package/dist/{sarif-CWyTAQ0y.js → sarif-BnwYC78_.js} +8 -98
  53. package/dist/sarif-BnwYC78_.js.map +1 -0
  54. package/dist/{scan-Bx0_yDSi.js → scan-C0ap4Mpp.js} +145 -20
  55. package/dist/scan-C0ap4Mpp.js.map +1 -0
  56. package/dist/solid/index.d.ts +29 -0
  57. package/dist/solid/index.d.ts.map +1 -0
  58. package/dist/solid/index.js +48 -0
  59. package/dist/solid/index.js.map +1 -0
  60. package/dist/svelte/Text.svelte +13 -0
  61. package/dist/svelte/Text.svelte.d.ts +20 -0
  62. package/dist/svelte/index.d.ts +31 -0
  63. package/dist/svelte/index.d.ts.map +1 -0
  64. package/dist/svelte/index.js +35 -0
  65. package/dist/svelte/index.js.map +1 -0
  66. package/dist/tools.d.ts +44 -65
  67. package/dist/tools.d.ts.map +1 -1
  68. package/dist/tools.js +5 -5
  69. package/dist/vite.d.ts +13 -3
  70. package/dist/vite.d.ts.map +1 -1
  71. package/dist/vite.js +82 -25
  72. package/dist/vite.js.map +1 -1
  73. package/dist/vue/index.d.ts +43 -0
  74. package/dist/vue/index.d.ts.map +1 -0
  75. package/dist/vue/index.js +76 -0
  76. package/dist/vue/index.js.map +1 -0
  77. package/package.json +63 -7
  78. package/src/bun.ts +77 -20
  79. package/src/cli.ts +62 -8
  80. package/src/index.ts +1 -1
  81. package/src/next-loader.ts +17 -5
  82. package/src/next.ts +120 -16
  83. package/src/preact/index.ts +91 -0
  84. package/src/react/index.tsx +12 -42
  85. package/src/runtime/bind.ts +96 -0
  86. package/src/runtime/copy.ts +148 -24
  87. package/src/solid/index.ts +67 -0
  88. package/src/svelte/Text.svelte +13 -0
  89. package/src/svelte/Text.svelte.d.ts +20 -0
  90. package/src/svelte/index.ts +65 -0
  91. package/src/tools/bundle.ts +143 -2
  92. package/src/tools/catalog.ts +10 -1
  93. package/src/tools/chunks.ts +142 -0
  94. package/src/tools/config.ts +25 -7
  95. package/src/tools/entry.ts +19 -20
  96. package/src/tools/exchange.ts +275 -0
  97. package/src/tools/extract.ts +2 -2
  98. package/src/tools/generate.ts +7 -1
  99. package/src/tools/index.ts +2 -1
  100. package/src/tools/native/android.ts +186 -0
  101. package/src/tools/native/apple.ts +201 -0
  102. package/src/tools/native/date.ts +219 -0
  103. package/src/tools/native/index.ts +17 -5
  104. package/src/tools/native/kotlin.ts +47 -6
  105. package/src/tools/native/model.ts +67 -29
  106. package/src/tools/native/printf.ts +137 -0
  107. package/src/tools/native/rust.ts +86 -5
  108. package/src/tools/native/swift.ts +62 -6
  109. package/src/tools/scan.ts +146 -25
  110. package/src/tools/todo.ts +6 -1
  111. package/src/vite.ts +121 -28
  112. package/src/vue/index.ts +117 -0
  113. package/dist/bundle-DboItAre.js +0 -111
  114. package/dist/bundle-DboItAre.js.map +0 -1
  115. package/dist/bundle-KD-DiiFa.d.ts.map +0 -1
  116. package/dist/compile-xF3F4oIp.d.ts +0 -16
  117. package/dist/compile-xF3F4oIp.d.ts.map +0 -1
  118. package/dist/copy-B3V_QRkT.d.ts.map +0 -1
  119. package/dist/copy-Bo_FIoLy.js +0 -154
  120. package/dist/copy-Bo_FIoLy.js.map +0 -1
  121. package/dist/endpoints-CnUfCkIk.js +0 -96
  122. package/dist/endpoints-CnUfCkIk.js.map +0 -1
  123. package/dist/generate-9pR3i5Bj.js.map +0 -1
  124. package/dist/sarif-CWyTAQ0y.js.map +0 -1
  125. package/dist/scan-Bx0_yDSi.js.map +0 -1
@@ -0,0 +1,235 @@
1
+ import { n as flatten, t as createHelpers } from "./helpers-BWGcD0kS.js";
2
+ import { n as chainOf, r as negotiate } from "./locale-DpQA1pqH.js";
3
+ // src/runtime/copy.ts
4
+ const registries = /* @__PURE__ */ new Map();
5
+ function registry(id) {
6
+ let r = registries.get(id);
7
+ if (r === void 0) registries.set(id, r = {
8
+ areas: /* @__PURE__ */ new Map(),
9
+ copies: /* @__PURE__ */ new Set()
10
+ });
11
+ return r;
12
+ }
13
+ /**
14
+ * Adds an area's messages to a split catalog. Called by generated code, from a module beside the code
15
+ * that reads the area; settles once every copy holds the area in its locale, so that code's first
16
+ * render reads it. A locale that fails to load reads in its fallback, and is reported.
17
+ */
18
+ async function addArea(id, area, messages, loaders) {
19
+ const r = registry(id);
20
+ if (r.areas.has(area)) return;
21
+ r.areas.set(area, {
22
+ messages,
23
+ loaders
24
+ });
25
+ for (const result of await Promise.allSettled([...r.copies].map((take) => take(area)))) if (result.status === "rejected") console.error(`prosebook: the ${area} area did not load:`, result.reason);
26
+ }
27
+ /** Ties a generated catalog to its message shapes. Called by generated code. */
28
+ function defineCatalog(catalog) {
29
+ return catalog;
30
+ }
31
+ /** The one area of a catalog that isn't split. */
32
+ const WHOLE = "";
33
+ const areaOf = (key) => key.slice(0, key.indexOf("."));
34
+ function pick(messages, keep) {
35
+ const out = {};
36
+ for (const key in messages) if (keep(key)) out[key] = messages[key];
37
+ return out;
38
+ }
39
+ /** A message as the text it puts on screen: rendered again with its tags left as their text. */
40
+ function textOf(message, values, h) {
41
+ if (typeof message === "string") return message;
42
+ const plain = Object.fromEntries(Object.entries(values).filter(([, v]) => typeof v !== "function"));
43
+ return flatten(message(plain, h)).join("");
44
+ }
45
+ /** A catalog reader with a current locale. Create one per app, or one per request on a server. */
46
+ function createCopy(catalog, options = {}) {
47
+ const loaded = /* @__PURE__ */ new Map();
48
+ const pending = /* @__PURE__ */ new Map();
49
+ const listeners = /* @__PURE__ */ new Set();
50
+ const defaultTags = options.tags ?? {};
51
+ const formatters = /* @__PURE__ */ new Map();
52
+ const split = catalog.split === void 0 ? void 0 : registry(catalog.split.id);
53
+ let reading;
54
+ const preloaded = /* @__PURE__ */ new Set();
55
+ const report = (error, key, l) => {
56
+ if (options.onError) options.onError(error, key, l);
57
+ else console.error(`prosebook: ${key} in ${l}: ${error.message}`);
58
+ };
59
+ const helpers = (l) => createHelpers(l, (error) => {
60
+ if (reading !== void 0) report(error, reading, l);
61
+ });
62
+ const areas = () => split === void 0 ? [WHOLE] : [...split.areas.keys()];
63
+ const piecesOf = (area) => area === WHOLE ? catalog : split.areas.get(area);
64
+ function layerOf(l) {
65
+ let layer = loaded.get(l);
66
+ if (layer === void 0) loaded.set(l, layer = {
67
+ messages: {},
68
+ h: helpers(l),
69
+ areas: /* @__PURE__ */ new Set()
70
+ });
71
+ return layer;
72
+ }
73
+ function take(l, area, messages) {
74
+ const layer = layerOf(l);
75
+ layer.areas.add(area);
76
+ layer.messages = area === WHOLE ? messages : {
77
+ ...layer.messages,
78
+ ...pick(messages, (key) => areaOf(key) === area)
79
+ };
80
+ }
81
+ function takeBundled(area) {
82
+ for (const [l, messages] of Object.entries(piecesOf(area).messages)) take(l, area, messages);
83
+ }
84
+ for (const area of areas()) takeBundled(area);
85
+ let locale = catalog.source;
86
+ let chain = layers(chainOf(locale, catalog.locales, catalog.source));
87
+ let wanted = chainOf(locale, catalog.locales, catalog.source);
88
+ let latest = 0;
89
+ let version = 0;
90
+ function changed() {
91
+ version++;
92
+ for (const listener of listeners) listener();
93
+ }
94
+ globalThis.__PROSEBOOK_HOT__?.add((l, messages) => {
95
+ const layer = loaded.get(l);
96
+ if (layer === void 0) return;
97
+ layer.messages = layer.areas.has(WHOLE) ? messages : pick(messages, (key) => layer.areas.has(areaOf(key)));
98
+ changed();
99
+ });
100
+ split?.copies.add(async (area) => {
101
+ takeBundled(area);
102
+ const locales = /* @__PURE__ */ new Set([...chainOf(locale, catalog.locales, catalog.source), ...wanted]);
103
+ await Promise.all([...locales].map((l) => load(l, area)));
104
+ changed();
105
+ });
106
+ function layers(locales) {
107
+ return locales.map(layerOf);
108
+ }
109
+ function load(l, area) {
110
+ if (loaded.get(l)?.areas.has(area) === true) return Promise.resolve();
111
+ const id = `${area}/${l}`;
112
+ let p = pending.get(id);
113
+ if (p === void 0) {
114
+ const loader = piecesOf(area).loaders[l];
115
+ if (loader === void 0) {
116
+ if (split === void 0) return Promise.reject(/* @__PURE__ */ new Error(`prosebook: no messages for locale "${l}"`));
117
+ take(l, area, {});
118
+ return Promise.resolve();
119
+ }
120
+ p = loader().then((mod) => {
121
+ take(l, area, mod.default);
122
+ pending.delete(id);
123
+ });
124
+ p.catch(() => pending.delete(id));
125
+ pending.set(id, p);
126
+ }
127
+ return p;
128
+ }
129
+ async function setLocale(next) {
130
+ const target = negotiate(next, catalog.locales, catalog.source);
131
+ const ticket = ++latest;
132
+ const locales = chainOf(target, catalog.locales, catalog.source);
133
+ wanted = locales;
134
+ await Promise.all(locales.flatMap((l) => areas().map((area) => load(l, area))));
135
+ if (ticket !== latest || target === locale) return;
136
+ locale = target;
137
+ chain = layers(locales);
138
+ formatters.clear();
139
+ changed();
140
+ }
141
+ function find(key) {
142
+ for (const layer of chain) {
143
+ const message = layer.messages[key];
144
+ if (message !== void 0) return [message, layer.h];
145
+ }
146
+ }
147
+ function preload(area) {
148
+ const files = catalog.areaFiles?.[area];
149
+ const read = `${area}/${locale}`;
150
+ if (files === void 0 || options.preload === void 0 || preloaded.has(read)) return;
151
+ preloaded.add(read);
152
+ for (const l of chainOf(locale, catalog.locales, catalog.source)) {
153
+ const href = files[l];
154
+ if (href === void 0 || preloaded.has(href)) continue;
155
+ preloaded.add(href);
156
+ options.preload(href);
157
+ }
158
+ }
159
+ function render(key, values) {
160
+ preload(areaOf(key));
161
+ const found = find(key);
162
+ if (found === void 0) {
163
+ const area = areaOf(key);
164
+ if (split !== void 0 && !split.areas.has(area) && catalog.split.areas.includes(area)) {
165
+ const fix = `name one of its keys in the module that reads it, or keep it: \`keep: ['${area}.*']\``;
166
+ report(/* @__PURE__ */ new Error(`the ${area} area has not loaded: ${fix}`), key, locale);
167
+ }
168
+ return options.onMissing ? options.onMissing(key, locale) : key;
169
+ }
170
+ const [message, h] = found;
171
+ reading = key;
172
+ let out;
173
+ try {
174
+ out = typeof message === "string" ? message : message(values, h);
175
+ } finally {
176
+ reading = void 0;
177
+ }
178
+ const read = globalThis.__PROSEBOOK_READ__;
179
+ if (read !== void 0) read(key, typeof out === "string" ? out : textOf(message, values, h), h.locale);
180
+ return out;
181
+ }
182
+ function formatter(kind, options, make) {
183
+ const id = `${kind}${JSON.stringify(options ?? {})}`;
184
+ let made = formatters.get(id);
185
+ if (made === void 0) formatters.set(id, made = make());
186
+ return made;
187
+ }
188
+ const ready = options.locale === void 0 ? Promise.resolve() : setLocale(options.locale);
189
+ return {
190
+ get locale() {
191
+ return locale;
192
+ },
193
+ get version() {
194
+ return version;
195
+ },
196
+ locales: catalog.locales,
197
+ source: catalog.source,
198
+ tags: defaultTags,
199
+ ready,
200
+ t(key, ...args) {
201
+ const out = render(key, args[0] ?? {});
202
+ return typeof out === "string" ? out : flatten(out).join("");
203
+ },
204
+ rich(key, ...values) {
205
+ const out = render(key, {
206
+ ...defaultTags,
207
+ ...values[0]
208
+ });
209
+ return flatten(out);
210
+ },
211
+ has(key) {
212
+ return find(key) !== void 0;
213
+ },
214
+ setLocale,
215
+ subscribe(listener) {
216
+ listeners.add(listener);
217
+ return () => listeners.delete(listener);
218
+ },
219
+ number(value, opts) {
220
+ return formatter("n", opts, () => new Intl.NumberFormat(locale, opts)).format(value);
221
+ },
222
+ date(value, opts) {
223
+ return formatter("d", opts, () => new Intl.DateTimeFormat(locale, opts)).format(value);
224
+ },
225
+ list(items, opts) {
226
+ return formatter("l", opts, () => new Intl.ListFormat(locale, opts)).format(items);
227
+ },
228
+ relative(value, unit, opts) {
229
+ return formatter("r", opts, () => new Intl.RelativeTimeFormat(locale, opts)).format(value, unit);
230
+ }
231
+ };
232
+ }
233
+ export { createCopy as n, defineCatalog as r, addArea as t };
234
+
235
+ //# sourceMappingURL=copy-Bv6S-Hr2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-Bv6S-Hr2.js","names":[],"sources":["../src/runtime/copy.ts"],"sourcesContent":["import { createHelpers, flatten } from './helpers.ts';\nimport { chainOf, negotiate } from './locale.ts';\nimport type {\n AnyTagFn,\n ArgsParam,\n Chunks,\n Compiled,\n CompiledMessages,\n Helpers,\n DevHooks,\n MessageShapes,\n RichKey,\n RichParam,\n Values,\n} from './types.ts';\n\ntype Loader = () => Promise<{ default: CompiledMessages }>;\n\n/** Messages that ship in the bundle and loaders for the rest, per locale: a catalog's, or one area's. */\ninterface Pieces<L extends string = string> {\n readonly messages: { readonly [locale in L]?: CompiledMessages };\n readonly loaders: { readonly [locale in L]?: Loader };\n}\n\n/** A generated catalog: which locales exist, which ship in the bundle and how to fetch the rest. */\nexport interface Catalog<M extends MessageShapes = MessageShapes, L extends string = string> {\n /** The locale the catalog is written in, which every other falls back to. */\n readonly source: L;\n readonly locales: readonly L[];\n /** Locales whose messages ship in the bundle. Always holds the source. */\n readonly messages: { readonly [locale in L]?: CompiledMessages };\n /** Locales fetched when first asked for. */\n readonly loaders: { readonly [locale in L]?: Loader };\n /**\n * Set when a build splits the catalog by area (the Vite plugin's `split`): `messages` and `loaders`\n * are empty, and each area adds its own (`addArea`) as the code reading it loads.\n */\n readonly split?: { readonly id: string; readonly areas: readonly string[] };\n /**\n * Set in a server build whose client is split (the Vite plugin's `split`): the file the browser\n * loads each area's messages from, per lazy locale, which `CopyOptions.preload` is handed.\n */\n readonly areaFiles?: { readonly [area: string]: { readonly [locale in L]?: string } };\n /** Type-only: carries the message shapes from the generated module to `createCopy`. */\n readonly '~messages'?: M;\n}\n\n/** A split catalog's areas, as they load, and each copy's hook for taking a new one in. */\ninterface Registry {\n areas: Map<string, Pieces>;\n copies: Set<(area: string) => Promise<unknown>>;\n}\n\n// By `split.id`, not by catalog: in dev an edit runs the index again, and an area that loads after it\n// must still reach the copies made from the catalog before.\nconst registries = new Map<string, Registry>();\n\nfunction registry(id: string): Registry {\n let r = registries.get(id);\n if (r === undefined) registries.set(id, (r = { areas: new Map(), copies: new Set() }));\n return r;\n}\n\n/**\n * Adds an area's messages to a split catalog. Called by generated code, from a module beside the code\n * that reads the area; settles once every copy holds the area in its locale, so that code's first\n * render reads it. A locale that fails to load reads in its fallback, and is reported.\n */\nexport async function addArea(id: string, area: string, messages: Pieces['messages'], loaders: Pieces['loaders']): Promise<void> {\n const r = registry(id);\n if (r.areas.has(area)) return;\n r.areas.set(area, { messages, loaders });\n for (const result of await Promise.allSettled([...r.copies].map((take) => take(area))))\n if (result.status === 'rejected') console.error(`prosebook: the ${area} area did not load:`, result.reason);\n}\n\n/** Ties a generated catalog to its message shapes. Called by generated code. */\nexport function defineCatalog<M extends MessageShapes, L extends string = string>(\n catalog: Omit<Catalog<M, L>, '~messages'>,\n): Catalog<M, L> {\n return catalog;\n}\n\nexport interface CopyOptions<L extends string> {\n /** The locale to start in, or the locales a reader prefers, most preferred first. Defaults to the source. */\n locale?: L | (string & {}) | readonly string[];\n /** Tag renderers every rich read gets unless it supplies its own, such as `b` or `link`. */\n tags?: { [tag: string]: AnyTagFn };\n /** What a key the catalog lacks reads as. Defaults to the key itself. */\n onMissing?: (key: string, locale: L) => string;\n /**\n * Told of a placeholder that had no value, or one `Intl` refused (`currency: 'euro'`): the message\n * still renders, showing `{name}` there. In a split build, also of a key read before its area\n * loaded. Defaults to `console.error`.\n */\n onError?: (error: Error, key: string, locale: L) => void;\n /**\n * On a server, handed each file the browser will load for the areas this copy reads, once each,\n * so the page can link it and the browser fetch it beside the code reading it rather than after:\n * `(href) => preloadModule(href, { as: 'script' })` under React. Only a catalog built for a split\n * client has such files (`Catalog.areaFiles`), and only its lazy locales.\n */\n preload?: (href: string) => void;\n}\n\n/** Its methods don't use `this`, so they can be passed around and destructured: `const { t } = copy`. */\nexport interface Copy<M extends MessageShapes = MessageShapes, L extends string = string> {\n /** The locale messages are coming back in. */\n readonly locale: L;\n readonly locales: readonly L[];\n readonly source: L;\n /** The tag renderers rich reads fall back to, from `CopyOptions.tags`. */\n readonly tags: { readonly [tag: string]: AnyTagFn };\n /** Settles once the starting locale's messages are loaded. */\n readonly ready: Promise<void>;\n /** A message as plain text. Tags render as the text they wrap. */\n t<K extends keyof M & string>(this: void, key: K, ...args: ArgsParam<M[K]>): string;\n /** A message with elements inside it: each tag's function turns what it wraps into a node. */\n rich<K extends RichKey<M>, N = unknown>(this: void, key: K, ...values: RichParam<M[K], N>): Chunks<N>;\n has(this: void, key: string): key is keyof M & string;\n /** Switches locale once its messages (and its fallbacks') are loaded. */\n setLocale(this: void, next: L | (string & {}) | readonly string[]): Promise<void>;\n /** Counts changes to what messages read as: each locale switch, and each catalog edit in dev. */\n readonly version: number;\n /** Called after each locale switch, and in dev after each catalog edit. Returns the unsubscribe. */\n subscribe(this: void, listener: () => void): () => void;\n /** Formats a number in the current locale. */\n number(this: void, value: number | bigint, options?: Intl.NumberFormatOptions): string;\n /** Formats a date in the current locale. */\n date(this: void, value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /** Joins items as a list in the current locale: `a, b and c`. */\n list(this: void, items: Iterable<string>, options?: Intl.ListFormatOptions): string;\n /** `in 3 days`, `2 hours ago`, in the current locale. */\n relative(this: void, value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;\n}\n\n/** A locale's messages, from each area loaded in it so far. */\ntype Layer = { messages: CompiledMessages; h: Helpers; areas: Set<string> };\n\n/** The one area of a catalog that isn't split. */\nconst WHOLE = '';\n\nconst areaOf = (key: string) => key.slice(0, key.indexOf('.'));\n\nfunction pick(messages: CompiledMessages, keep: (key: string) => boolean): CompiledMessages {\n const out: Record<string, Compiled> = {};\n for (const key in messages) if (keep(key)) out[key] = messages[key]!;\n return out;\n}\n\n/** A message as the text it puts on screen: rendered again with its tags left as their text. */\nfunction textOf(message: Compiled, values: Values, h: Helpers): string {\n if (typeof message === 'string') return message;\n const plain = Object.fromEntries(Object.entries(values).filter(([, v]) => typeof v !== 'function'));\n return flatten(message(plain, h)).join('');\n}\n\n/** A catalog reader with a current locale. Create one per app, or one per request on a server. */\nexport function createCopy<M extends MessageShapes, L extends string>(catalog: Catalog<M, L>, options: CopyOptions<L> = {}): Copy<M, L> {\n const loaded = new Map<L, Layer>();\n const pending = new Map<string, Promise<void>>();\n const listeners = new Set<() => void>();\n const defaultTags = options.tags ?? {};\n const formatters = new Map<string, Intl.NumberFormat | Intl.DateTimeFormat | Intl.ListFormat | Intl.RelativeTimeFormat>();\n const split = catalog.split === undefined ? undefined : registry(catalog.split.id);\n // The key being rendered, which a helper's report names; none while the inspector renders it again.\n let reading: string | undefined;\n // The files handed to `preload`, and each area and locale read, which hands on nothing new after.\n const preloaded = new Set<string>();\n const report = (error: Error, key: string, l: L) => {\n if (options.onError) options.onError(error, key, l);\n else console.error(`prosebook: ${key} in ${l}: ${error.message}`);\n };\n const helpers = (l: L) =>\n createHelpers(l, (error) => {\n if (reading !== undefined) report(error, reading, l);\n });\n\n const areas = () => (split === undefined ? [WHOLE] : [...split.areas.keys()]);\n const piecesOf = (area: string): Pieces<L> => (area === WHOLE ? catalog : split!.areas.get(area)!);\n\n function layerOf(l: L): Layer {\n let layer = loaded.get(l);\n if (layer === undefined) loaded.set(l, (layer = { messages: {}, h: helpers(l), areas: new Set() }));\n return layer;\n }\n\n function take(l: L, area: string, messages: CompiledMessages) {\n const layer = layerOf(l);\n layer.areas.add(area);\n // In dev an area is handed its locales whole; only the area's own keys are its.\n layer.messages = area === WHOLE ? messages : { ...layer.messages, ...pick(messages, (key) => areaOf(key) === area) };\n }\n\n function takeBundled(area: string) {\n for (const [l, messages] of Object.entries(piecesOf(area).messages) as [L, CompiledMessages][]) take(l, area, messages);\n }\n\n for (const area of areas()) takeBundled(area);\n\n let locale = catalog.source;\n let chain: Layer[] = layers(chainOf(locale, catalog.locales, catalog.source));\n // The locales the latest switch asked for, which an area arriving during it loads in too.\n let wanted: L[] = chainOf(locale, catalog.locales, catalog.source);\n let latest = 0;\n let version = 0;\n\n function changed() {\n version++;\n for (const listener of listeners) listener();\n }\n\n // A layer's messages are swapped in place, so the chain that holds it reads the new ones.\n (globalThis as DevHooks).__PROSEBOOK_HOT__?.add((l, messages) => {\n const layer = loaded.get(l as L);\n if (layer === undefined) return;\n layer.messages = layer.areas.has(WHOLE) ? messages : pick(messages, (key) => layer.areas.has(areaOf(key)));\n changed();\n });\n\n // An area that loads after the copy was made comes in, in the locale shown and the one being switched to.\n split?.copies.add(async (area) => {\n takeBundled(area);\n const locales = new Set([...chainOf(locale, catalog.locales, catalog.source), ...wanted]);\n await Promise.all([...locales].map((l) => load(l, area)));\n changed();\n });\n\n function layers(locales: L[]): Layer[] {\n return locales.map(layerOf);\n }\n\n function load(l: L, area: string): Promise<void> {\n if (loaded.get(l)?.areas.has(area) === true) return Promise.resolve();\n const id = `${area}/${l}`;\n let p = pending.get(id);\n if (p === undefined) {\n // oxlint-disable-next-line typescript/no-unsafe-enum-assignment -- `L` is a string union, not an enum; the rule misreads the mapped type\n const loader = piecesOf(area).loaders[l];\n if (loader === undefined) {\n if (split === undefined) return Promise.reject(new Error(`prosebook: no messages for locale \"${l}\"`));\n // An area with nothing translated in a locale has no loader for it.\n take(l, area, {});\n return Promise.resolve();\n }\n p = loader().then((mod) => {\n take(l, area, mod.default);\n pending.delete(id);\n });\n p.catch(() => pending.delete(id));\n pending.set(id, p);\n }\n return p;\n }\n\n async function setLocale(next: L | (string & {}) | readonly string[]): Promise<void> {\n const target = negotiate(next, catalog.locales, catalog.source);\n const ticket = ++latest;\n const locales = chainOf(target, catalog.locales, catalog.source);\n wanted = locales;\n await Promise.all(locales.flatMap((l) => areas().map((area) => load(l, area))));\n // A slower load must not undo a later switch.\n if (ticket !== latest || target === locale) return;\n locale = target;\n chain = layers(locales);\n formatters.clear();\n changed();\n }\n\n function find(key: string): [Compiled, Helpers] | undefined {\n for (const layer of chain) {\n const message = layer.messages[key];\n if (message !== undefined) return [message, layer.h];\n }\n return undefined;\n }\n\n function preload(area: string) {\n const files = catalog.areaFiles?.[area];\n const read = `${area}/${locale}`;\n if (files === undefined || options.preload === undefined || preloaded.has(read)) return;\n preloaded.add(read);\n for (const l of chainOf(locale, catalog.locales, catalog.source)) {\n const href = files[l];\n if (href === undefined || preloaded.has(href)) continue;\n preloaded.add(href);\n options.preload(href);\n }\n }\n\n function render(key: string, values: Values): unknown {\n preload(areaOf(key));\n const found = find(key);\n if (found === undefined) {\n const area = areaOf(key);\n if (split !== undefined && !split.areas.has(area) && catalog.split!.areas.includes(area)) {\n const fix = `name one of its keys in the module that reads it, or keep it: \\`keep: ['${area}.*']\\``;\n report(new Error(`the ${area} area has not loaded: ${fix}`), key, locale);\n }\n return options.onMissing ? options.onMissing(key, locale) : key;\n }\n const [message, h] = found;\n reading = key;\n let out: unknown;\n try {\n out = typeof message === 'string' ? message : message(values, h);\n } finally {\n reading = undefined;\n }\n const read = (globalThis as DevHooks).__PROSEBOOK_READ__;\n if (read !== undefined) read(key, typeof out === 'string' ? out : textOf(message, values, h), h.locale);\n return out;\n }\n\n function formatter<F>(kind: string, options: object | undefined, make: () => F): F {\n const id = `${kind}${JSON.stringify(options ?? {})}`;\n let made = formatters.get(id) as F | undefined;\n if (made === undefined) formatters.set(id, (made = make()) as never);\n return made;\n }\n\n const ready = options.locale === undefined ? Promise.resolve() : setLocale(options.locale);\n\n return {\n get locale() {\n return locale;\n },\n get version() {\n return version;\n },\n locales: catalog.locales,\n source: catalog.source,\n tags: defaultTags,\n ready,\n t(key, ...args) {\n const out = render(key, (args[0] as Values | undefined) ?? {});\n return typeof out === 'string' ? out : flatten(out).join('');\n },\n rich(key, ...values) {\n const out = render(key, { ...defaultTags, ...(values[0] as Values | undefined) });\n return flatten(out) as Chunks<never>;\n },\n has(key): key is keyof M & string {\n return find(key) !== undefined;\n },\n setLocale,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n number(value, opts) {\n return formatter('n', opts, () => new Intl.NumberFormat(locale, opts)).format(value);\n },\n date(value, opts) {\n return formatter('d', opts, () => new Intl.DateTimeFormat(locale, opts)).format(value);\n },\n list(items, opts) {\n return formatter('l', opts, () => new Intl.ListFormat(locale, opts)).format(items);\n },\n relative(value, unit, opts) {\n return formatter('r', opts, () => new Intl.RelativeTimeFormat(locale, opts)).format(value, unit);\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,217 @@
1
+ import { c as scriptBlocks, l as sourceFiles, n as SFC } from "./scan-C0ap4Mpp.js";
2
+ import { l as eagerAreas, o as areasNamed, s as areasOf } from "./bundle-Ut6Y2X6N.js";
3
+ import { A as loadCatalog, O as entryLocation, T as CatalogError, f as readEntry, u as editEntry } from "./generate-CKv86Iki.js";
4
+ import { readFileSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { parseSync } from "oxc-parser";
7
+ // src/tools/chunks.ts
8
+ /**
9
+ * Which areas of a split catalog a bundler puts in one chunk, so a lazy locale's messages for them
10
+ * can be one file: a route reading three areas loads German in one request, not three.
11
+ *
12
+ * Read from the source's imports ahead of the build, as Bun and Next give a plugin no say in chunks
13
+ * and Vite loads an area before it has seen every module naming it. Bundlers place a module by the
14
+ * entries that load it: the app's own, and each dynamic import, less those that only ever load once
15
+ * it is already in memory, as a route its shell imports. This reads the same from the files under
16
+ * `scan.dirs`, with relative imports resolved. It is a guess, and only a request rides on it: an
17
+ * import it can't resolve (an alias, a package) leaves apart areas that may share a chunk, and two
18
+ * areas grouped in different chunks load each other's messages, which the copy leaves unread.
19
+ */
20
+ /** Tests and stories import components but load in no page, and as entries of their own they would part the areas they reach. */
21
+ const NOT_IN_APP = /\.(?:test|spec|stories)\.[^./\\]+$|[\\/]__tests__[\\/]/;
22
+ /** A module's imports that load code: `import type` and `export type … from` load none. */
23
+ function importsOf(file, code) {
24
+ const pieces = SFC.test(file) ? scriptBlocks(code) : [{
25
+ code,
26
+ at: 0
27
+ }];
28
+ const found = {
29
+ static: [],
30
+ dynamic: []
31
+ };
32
+ for (const piece of pieces) {
33
+ const { module } = parseSync(SFC.test(file) ? "component.ts" : file, piece.code);
34
+ for (const i of module.staticImports) if (i.entries.length === 0 || i.entries.some((e) => !e.isType)) found.static.push(i.moduleRequest.value);
35
+ for (const e of module.staticExports.flatMap((s) => s.entries)) if (e.moduleRequest !== null && !e.isType) found.static.push(e.moduleRequest.value);
36
+ for (const d of module.dynamicImports) {
37
+ const literal = /^(['"`])([^'"`$]*)\1$/.exec(piece.code.slice(d.moduleRequest.start, d.moduleRequest.end).trim());
38
+ if (literal !== null) found.dynamic.push(literal[2]);
39
+ }
40
+ }
41
+ return found;
42
+ }
43
+ /** The file a relative specifier names, as a bundler finds it: as written, with an extension, as `./x.js` for `x.ts`, or as a folder's index. */
44
+ function resolver(files, extensions) {
45
+ return (from, specifier) => {
46
+ if (!specifier.startsWith(".")) return void 0;
47
+ const base = resolve(dirname(from), specifier);
48
+ const stem = base.replace(/\.[cm]?jsx?$/, "");
49
+ return [
50
+ base,
51
+ ...extensions.map((e) => base + e),
52
+ ...extensions.map((e) => stem + e),
53
+ ...extensions.map((e) => join(base, `index${e}`))
54
+ ].find((c) => files.has(c));
55
+ };
56
+ }
57
+ /**
58
+ * The groups of a split catalog's areas. Areas `keep` names load with the index, together; one no
59
+ * file under `scan.dirs` names is a group of its own.
60
+ */
61
+ function areaGroups(config, generated, keep) {
62
+ const eager = eagerAreas(generated, keep);
63
+ const lazy = new Set([...areasOf(generated)].filter((a) => !eager.has(a)));
64
+ const files = sourceFiles(config.scan.dirs, config.scan.extensions, [config.out]).filter((f) => !NOT_IN_APP.test(f)).map((f) => resolve(f));
65
+ const find = resolver(new Set(files), config.scan.extensions);
66
+ const node = (area) => `\0${area}`;
67
+ const statics = /* @__PURE__ */ new Map();
68
+ const dynamics = /* @__PURE__ */ new Map();
69
+ const imported = /* @__PURE__ */ new Set();
70
+ for (const file of files) {
71
+ const code = readFileSync(file, "utf8");
72
+ const { static: s, dynamic: d } = importsOf(file, code);
73
+ const to = (specifiers) => specifiers.map((spec) => find(file, spec)).filter((f) => f !== void 0);
74
+ statics.set(file, [...to(s), ...areasNamed(code, lazy).map(node)]);
75
+ for (const target of to(d)) {
76
+ if (!dynamics.has(target)) dynamics.set(target, /* @__PURE__ */ new Set());
77
+ dynamics.get(target).add(file);
78
+ }
79
+ for (const f of [...to(s), ...to(d)]) imported.add(f);
80
+ }
81
+ const entries = [.../* @__PURE__ */ new Set([...files.filter((f) => !imported.has(f)), ...dynamics.keys()])];
82
+ const closure = /* @__PURE__ */ new Map();
83
+ const loadedBy = /* @__PURE__ */ new Map();
84
+ for (const entry of entries) {
85
+ const seen = /* @__PURE__ */ new Set();
86
+ const visit = (n) => {
87
+ if (seen.has(n)) return;
88
+ seen.add(n);
89
+ if (!loadedBy.has(n)) loadedBy.set(n, /* @__PURE__ */ new Set());
90
+ loadedBy.get(n).add(entry);
91
+ for (const next of statics.get(n) ?? []) visit(next);
92
+ };
93
+ visit(entry);
94
+ closure.set(entry, seen);
95
+ }
96
+ const inMemory = new Map([...dynamics.keys()].map((d) => [d, /* @__PURE__ */ new Set()]));
97
+ const nodes = [...loadedBy.keys()];
98
+ for (let changed = true; changed;) {
99
+ changed = false;
100
+ for (const [entry, importers] of dynamics) {
101
+ const by = [...importers].map((i) => [...loadedBy.get(i) ?? []]);
102
+ if (by.some((e) => e.length === 0)) continue;
103
+ const held = (n) => by.every((e) => e.every((root) => closure.get(root).has(n) || inMemory.get(root)?.has(n) === true));
104
+ const next = new Set(nodes.filter(held));
105
+ if (next.size > inMemory.get(entry).size) {
106
+ inMemory.set(entry, next);
107
+ changed = true;
108
+ }
109
+ }
110
+ }
111
+ const groups = /* @__PURE__ */ new Map();
112
+ const byChunk = /* @__PURE__ */ new Map();
113
+ const sorted = [...lazy].toSorted();
114
+ for (const area of sorted) {
115
+ const loading = [...loadedBy.get(node(area)) ?? []].filter((e) => inMemory.get(e)?.has(node(area)) !== true).toSorted();
116
+ const chunk = loading.length === 0 ? node(area) : loading.join("\n");
117
+ if (!byChunk.has(chunk)) byChunk.set(chunk, []);
118
+ byChunk.get(chunk).push(area);
119
+ }
120
+ for (const members of [...byChunk.values(), [...eager].toSorted()]) for (const area of members) groups.set(area, members[0]);
121
+ return groups;
122
+ }
123
+ // src/tools/endpoints.ts
124
+ /** Answers the inspector's requests, and nothing for any other path. */
125
+ function inspectorEndpoints(config, { base = "/", open }) {
126
+ return async (req) => {
127
+ const url = new URL(req.url);
128
+ if (url.pathname === `${base}__prosebook/open`) return openEntry(await config, url, open);
129
+ if (url.pathname === `${base}__prosebook/entry`) return entry(await config, req, url);
130
+ };
131
+ }
132
+ /**
133
+ * `?key=cart.items&locale=de`: the translation's entry, or the catalog's with `catalog` or when the
134
+ * locale has none. Read from disk, so a key added since the page loaded is found.
135
+ */
136
+ async function openEntry(config, url, open) {
137
+ const key = url.searchParams.get("key") ?? "";
138
+ let message;
139
+ try {
140
+ message = loadCatalog(config).messages.find((m) => m.key === key);
141
+ } catch (e) {
142
+ return failed(e);
143
+ }
144
+ if (message === void 0) return new Response(`no entry for "${key}" in the catalog: has it been renamed since the page loaded?`, { status: 404 });
145
+ const locale = url.searchParams.has("catalog") ? void 0 : url.searchParams.get("locale") ?? void 0;
146
+ return open(entryLocation(config, message, locale));
147
+ }
148
+ /**
149
+ * The inspector's dialog: GET `?key=…` reads an entry in every locale, POST `{ key, edit, write }`
150
+ * applies an edit (`entry.ts`). A POST writes files, so only the page itself may send one: same origin,
151
+ * and JSON, which a form on another site can't send without asking first.
152
+ */
153
+ async function entry(config, req, url) {
154
+ if (req.method === "GET") try {
155
+ return Response.json(readEntry(config, url.searchParams.get("key") ?? ""));
156
+ } catch (e) {
157
+ return failed(e);
158
+ }
159
+ const origin = req.headers.get("origin");
160
+ const sameOrigin = origin === null || new URL(origin).host === url.host;
161
+ if (req.method !== "POST" || !sameOrigin || req.headers.get("content-type")?.startsWith("application/json") !== true) return Response.json({ problems: [{
162
+ severity: "error",
163
+ message: "edits come from the page, as JSON"
164
+ }] }, { status: 403 });
165
+ try {
166
+ const { key, edit, write } = await req.json();
167
+ const result = editEntry(config, key, edit, write === true);
168
+ return Response.json(result, { status: result.problems.length > 0 ? 422 : 200 });
169
+ } catch (e) {
170
+ return failed(e);
171
+ }
172
+ }
173
+ /** A redirect within the page's origin, to a launcher the dev server answers itself. */
174
+ const redirect = (path) => new Response(null, {
175
+ status: 307,
176
+ headers: { Location: path }
177
+ });
178
+ function failed(e) {
179
+ return Response.json({ problems: e instanceof CatalogError ? e.problems : [{
180
+ severity: "error",
181
+ message: e instanceof Error ? e.message : String(e)
182
+ }] }, { status: e instanceof CatalogError ? 409 : 400 });
183
+ }
184
+ /**
185
+ * The endpoints as Node middleware, for Vite's server and Next's. The request's host is the one the
186
+ * page was served from: a proxy's `x-forwarded-host`, as Next's rewrite sets, before its own.
187
+ */
188
+ function nodeMiddleware(handle) {
189
+ return (req, res, next) => {
190
+ if (req.url?.includes("/__prosebook/") !== true) return next();
191
+ const forwarded = req.headers["x-forwarded-host"];
192
+ const host = (Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? req.headers.host ?? "localhost";
193
+ const headers = new Headers();
194
+ for (const [name, value] of Object.entries(req.headers)) for (const v of Array.isArray(value) ? value : value === void 0 ? [] : [value]) headers.append(name, v);
195
+ const chunks = [];
196
+ req.on("data", (chunk) => chunks.push(chunk));
197
+ req.on("end", () => {
198
+ const body = req.method === "GET" || req.method === "HEAD" ? void 0 : Buffer.concat(chunks);
199
+ handle(new Request(`http://${host}${req.url}`, {
200
+ method: req.method,
201
+ headers,
202
+ body
203
+ })).then(async (response) => {
204
+ if (response === void 0) return next();
205
+ res.statusCode = response.status;
206
+ response.headers.forEach((value, name) => res.setHeader(name, value));
207
+ res.end(Buffer.from(await response.arrayBuffer()));
208
+ }).catch((e) => {
209
+ res.statusCode = 500;
210
+ res.end(e instanceof Error ? e.message : String(e));
211
+ });
212
+ });
213
+ };
214
+ }
215
+ export { areaGroups as i, nodeMiddleware as n, redirect as r, inspectorEndpoints as t };
216
+
217
+ //# sourceMappingURL=endpoints-Dhgowovz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoints-Dhgowovz.js","names":[],"sources":["../src/tools/chunks.ts","../src/tools/endpoints.ts"],"sourcesContent":["/**\n * Which areas of a split catalog a bundler puts in one chunk, so a lazy locale's messages for them\n * can be one file: a route reading three areas loads German in one request, not three.\n *\n * Read from the source's imports ahead of the build, as Bun and Next give a plugin no say in chunks\n * and Vite loads an area before it has seen every module naming it. Bundlers place a module by the\n * entries that load it: the app's own, and each dynamic import, less those that only ever load once\n * it is already in memory, as a route its shell imports. This reads the same from the files under\n * `scan.dirs`, with relative imports resolved. It is a guess, and only a request rides on it: an\n * import it can't resolve (an alias, a package) leaves apart areas that may share a chunk, and two\n * areas grouped in different chunks load each other's messages, which the copy leaves unread.\n */\nimport { readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport { parseSync } from 'oxc-parser';\n\nimport { areasNamed, areasOf, eagerAreas, type Groups } from './bundle.ts';\nimport type { ResolvedConfig } from './config.ts';\nimport type { Generated } from './generate.ts';\nimport { scriptBlocks, SFC, sourceFiles } from './scan.ts';\n\n/** Tests and stories import components but load in no page, and as entries of their own they would part the areas they reach. */\nconst NOT_IN_APP = /\\.(?:test|spec|stories)\\.[^./\\\\]+$|[\\\\/]__tests__[\\\\/]/;\n\ninterface Imports {\n static: string[];\n dynamic: string[];\n}\n\n/** A module's imports that load code: `import type` and `export type … from` load none. */\nfunction importsOf(file: string, code: string): Imports {\n const pieces = SFC.test(file) ? scriptBlocks(code) : [{ code, at: 0 }];\n const found: Imports = { static: [], dynamic: [] };\n for (const piece of pieces) {\n const { module } = parseSync(SFC.test(file) ? 'component.ts' : file, piece.code);\n for (const i of module.staticImports)\n if (i.entries.length === 0 || i.entries.some((e) => !e.isType)) found.static.push(i.moduleRequest.value);\n for (const e of module.staticExports.flatMap((s) => s.entries))\n if (e.moduleRequest !== null && !e.isType) found.static.push(e.moduleRequest.value);\n for (const d of module.dynamicImports) {\n // Only a literal names a module; `import(\\`./pages/${name}.tsx\\`)` could be any.\n const literal = /^(['\"`])([^'\"`$]*)\\1$/.exec(piece.code.slice(d.moduleRequest.start, d.moduleRequest.end).trim());\n if (literal !== null) found.dynamic.push(literal[2]!);\n }\n }\n return found;\n}\n\n/** The file a relative specifier names, as a bundler finds it: as written, with an extension, as `./x.js` for `x.ts`, or as a folder's index. */\nfunction resolver(files: ReadonlySet<string>, extensions: string[]): (from: string, specifier: string) => string | undefined {\n return (from, specifier) => {\n if (!specifier.startsWith('.')) return undefined;\n const base = resolve(dirname(from), specifier);\n const stem = base.replace(/\\.[cm]?jsx?$/, '');\n const candidates = [\n base,\n ...extensions.map((e) => base + e),\n ...extensions.map((e) => stem + e),\n ...extensions.map((e) => join(base, `index${e}`)),\n ];\n return candidates.find((c) => files.has(c));\n };\n}\n\n/**\n * The groups of a split catalog's areas. Areas `keep` names load with the index, together; one no\n * file under `scan.dirs` names is a group of its own.\n */\nexport function areaGroups(config: ResolvedConfig, generated: Generated, keep?: string[]): Groups {\n const eager = eagerAreas(generated, keep);\n const lazy = new Set([...areasOf(generated)].filter((a) => !eager.has(a)));\n const files = sourceFiles(config.scan.dirs, config.scan.extensions, [config.out])\n .filter((f) => !NOT_IN_APP.test(f))\n .map((f) => resolve(f));\n const find = resolver(new Set(files), config.scan.extensions);\n\n // The module graph, an area a node of its own that each file naming its keys imports.\n const node = (area: string) => `\\0${area}`;\n const statics = new Map<string, string[]>();\n const dynamics = new Map<string, Set<string>>();\n const imported = new Set<string>();\n for (const file of files) {\n const code = readFileSync(file, 'utf8');\n const { static: s, dynamic: d } = importsOf(file, code);\n const to = (specifiers: string[]) => specifiers.map((spec) => find(file, spec)).filter((f) => f !== undefined);\n statics.set(file, [...to(s), ...areasNamed(code, lazy).map(node)]);\n for (const target of to(d)) {\n if (!dynamics.has(target)) dynamics.set(target, new Set());\n dynamics.get(target)!.add(file);\n }\n for (const f of [...to(s), ...to(d)]) imported.add(f);\n }\n\n // Entries: each dynamic import, and each file nothing imports, as a page or the app's own entry.\n const entries = [...new Set([...files.filter((f) => !imported.has(f)), ...dynamics.keys()])];\n const closure = new Map<string, Set<string>>();\n const loadedBy = new Map<string, Set<string>>();\n for (const entry of entries) {\n const seen = new Set<string>();\n const visit = (n: string) => {\n if (seen.has(n)) return;\n seen.add(n);\n if (!loadedBy.has(n)) loadedBy.set(n, new Set());\n loadedBy.get(n)!.add(entry);\n for (const next of statics.get(n) ?? []) visit(next);\n };\n visit(entry);\n closure.set(entry, seen);\n }\n\n // What is in memory whenever a dynamic entry loads: what every module importing it brought, by\n // every entry that loads that module. It only grows, so it settles.\n const inMemory = new Map<string, Set<string>>([...dynamics.keys()].map((d) => [d, new Set()]));\n const nodes = [...loadedBy.keys()];\n for (let changed = true; changed;) {\n changed = false;\n for (const [entry, importers] of dynamics) {\n const by = [...importers].map((i) => [...(loadedBy.get(i) ?? [])]);\n if (by.some((e) => e.length === 0)) continue;\n const held = (n: string) => by.every((e) => e.every((root) => closure.get(root)!.has(n) || inMemory.get(root)?.has(n) === true));\n const next = new Set(nodes.filter(held));\n if (next.size > inMemory.get(entry)!.size) {\n inMemory.set(entry, next);\n changed = true;\n }\n }\n }\n\n const groups = new Map<string, string>();\n const byChunk = new Map<string, string[]>();\n const sorted = [...lazy].toSorted();\n for (const area of sorted) {\n const loading = [...(loadedBy.get(node(area)) ?? [])].filter((e) => inMemory.get(e)?.has(node(area)) !== true).toSorted();\n // Named by no file read here, so where it lands is unknown: a group of its own.\n const chunk = loading.length === 0 ? node(area) : loading.join('\\n');\n if (!byChunk.has(chunk)) byChunk.set(chunk, []);\n byChunk.get(chunk)!.push(area);\n }\n for (const members of [...byChunk.values(), [...eager].toSorted()]) for (const area of members) groups.set(area, members[0]!);\n return groups;\n}\n","/**\n * The dev server's half of the inspector (`inspector.ts`, `inspector-dialog.ts`): `__prosebook/open`,\n * which opens an entry in the editor, and `__prosebook/entry`, which reads and edits one. Written over\n * `Request` and `Response`, so each integration mounts them its own way: Vite as middleware, Bun as\n * `Bun.serve` routes, Next behind a rewrite to a server of their own. Each opens the editor its dev\n * server already launches.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { CatalogError, entryLocation, loadCatalog, type EntryLocation } from './catalog.ts';\nimport type { ResolvedConfig } from './config.ts';\nimport { editEntry, readEntry, type EntryEdit } from './entry.ts';\n\nexport interface EndpointOptions {\n /** The path the endpoints sit under, ending in `/`: `/` by default. */\n base?: string;\n /** Opens an entry in the editor: a redirect to the dev server's own launcher, or a reply once it has. */\n open: (at: EntryLocation) => Response | Promise<Response>;\n}\n\n/** Answers the inspector's requests, and nothing for any other path. */\nexport function inspectorEndpoints(\n config: ResolvedConfig | Promise<ResolvedConfig>,\n { base = '/', open }: EndpointOptions,\n): (req: Request) => Promise<Response | undefined> {\n return async (req) => {\n const url = new URL(req.url);\n if (url.pathname === `${base}__prosebook/open`) return openEntry(await config, url, open);\n if (url.pathname === `${base}__prosebook/entry`) return entry(await config, req, url);\n return undefined;\n };\n}\n\n/**\n * `?key=cart.items&locale=de`: the translation's entry, or the catalog's with `catalog` or when the\n * locale has none. Read from disk, so a key added since the page loaded is found.\n */\nasync function openEntry(config: ResolvedConfig, url: URL, open: EndpointOptions['open']): Promise<Response> {\n const key = url.searchParams.get('key') ?? '';\n let message;\n try {\n message = loadCatalog(config).messages.find((m) => m.key === key);\n } catch (e) {\n return failed(e);\n }\n if (message === undefined)\n return new Response(`no entry for \"${key}\" in the catalog: has it been renamed since the page loaded?`, { status: 404 });\n const locale = url.searchParams.has('catalog') ? undefined : (url.searchParams.get('locale') ?? undefined);\n return open(entryLocation(config, message, locale));\n}\n\n/**\n * The inspector's dialog: GET `?key=…` reads an entry in every locale, POST `{ key, edit, write }`\n * applies an edit (`entry.ts`). A POST writes files, so only the page itself may send one: same origin,\n * and JSON, which a form on another site can't send without asking first.\n */\nasync function entry(config: ResolvedConfig, req: Request, url: URL): Promise<Response> {\n if (req.method === 'GET') {\n try {\n return Response.json(readEntry(config, url.searchParams.get('key') ?? ''));\n } catch (e) {\n return failed(e);\n }\n }\n const origin = req.headers.get('origin');\n const sameOrigin = origin === null || new URL(origin).host === url.host;\n if (req.method !== 'POST' || !sameOrigin || req.headers.get('content-type')?.startsWith('application/json') !== true)\n return Response.json({ problems: [{ severity: 'error', message: 'edits come from the page, as JSON' }] }, { status: 403 });\n try {\n const { key, edit, write } = (await req.json()) as { key: string; edit: EntryEdit; write?: boolean };\n const result = editEntry(config, key, edit, write === true);\n return Response.json(result, { status: result.problems.length > 0 ? 422 : 200 });\n } catch (e) {\n return failed(e);\n }\n}\n\n/** A redirect within the page's origin, to a launcher the dev server answers itself. */\nexport const redirect = (path: string) => new Response(null, { status: 307, headers: { Location: path } });\n\nfunction failed(e: unknown): Response {\n return Response.json(\n { problems: e instanceof CatalogError ? e.problems : [{ severity: 'error', message: e instanceof Error ? e.message : String(e) }] },\n { status: e instanceof CatalogError ? 409 : 400 },\n );\n}\n\n/**\n * The endpoints as Node middleware, for Vite's server and Next's. The request's host is the one the\n * page was served from: a proxy's `x-forwarded-host`, as Next's rewrite sets, before its own.\n */\nexport function nodeMiddleware(\n handle: (req: Request) => Promise<Response | undefined>,\n): (req: IncomingMessage, res: ServerResponse, next: () => void) => void {\n return (req, res, next) => {\n // Before reading the body, which a request for anything else leaves to whoever answers it.\n if (req.url?.includes('/__prosebook/') !== true) return next();\n const forwarded = req.headers['x-forwarded-host'];\n const host = (Array.isArray(forwarded) ? forwarded[0] : forwarded) ?? req.headers.host ?? 'localhost';\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers))\n for (const v of Array.isArray(value) ? value : value === undefined ? [] : [value]) headers.append(name, v);\n\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => {\n const body = req.method === 'GET' || req.method === 'HEAD' ? undefined : Buffer.concat(chunks);\n handle(new Request(`http://${host}${req.url}`, { method: req.method, headers, body }))\n .then(async (response) => {\n if (response === undefined) return next();\n res.statusCode = response.status;\n response.headers.forEach((value, name) => res.setHeader(name, value));\n res.end(Buffer.from(await response.arrayBuffer()));\n })\n .catch((e: unknown) => {\n res.statusCode = 500;\n res.end(e instanceof Error ? e.message : String(e));\n });\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,aAAa;;AAQnB,SAAS,UAAU,MAAc,MAAuB;CACtD,MAAM,SAAS,IAAI,KAAK,IAAI,IAAI,aAAa,IAAI,IAAI,CAAC;EAAE;EAAM,IAAI;CAAE,CAAC;CACrE,MAAM,QAAiB;EAAE,QAAQ,CAAC;EAAG,SAAS,CAAC;CAAE;CACjD,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,EAAE,WAAW,UAAU,IAAI,KAAK,IAAI,IAAI,iBAAiB,MAAM,MAAM,IAAI;EAC/E,KAAK,MAAM,KAAK,OAAO,eACrB,IAAI,EAAE,QAAQ,WAAW,KAAK,EAAE,QAAQ,MAAM,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,OAAO,KAAK,EAAE,cAAc,KAAK;EACzG,KAAK,MAAM,KAAK,OAAO,cAAc,SAAS,MAAM,EAAE,OAAO,GAC3D,IAAI,EAAE,kBAAkB,QAAQ,CAAC,EAAE,QAAQ,MAAM,OAAO,KAAK,EAAE,cAAc,KAAK;EACpF,KAAK,MAAM,KAAK,OAAO,gBAAgB;GAErC,MAAM,UAAU,wBAAwB,KAAK,MAAM,KAAK,MAAM,EAAE,cAAc,OAAO,EAAE,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC;GAChH,IAAI,YAAY,MAAM,MAAM,QAAQ,KAAK,QAAQ,EAAG;EACtD;CACF;CACA,OAAO;AACT;;AAGA,SAAS,SAAS,OAA4B,YAA+E;CAC3H,QAAQ,MAAM,cAAc;EAC1B,IAAI,CAAC,UAAU,WAAW,GAAG,GAAG,OAAO,KAAA;EACvC,MAAM,OAAO,QAAQ,QAAQ,IAAI,GAAG,SAAS;EAC7C,MAAM,OAAO,KAAK,QAAQ,gBAAgB,EAAE;EAO5C,OAAO;GALL;GACA,GAAG,WAAW,KAAK,MAAM,OAAO,CAAC;GACjC,GAAG,WAAW,KAAK,MAAM,OAAO,CAAC;GACjC,GAAG,WAAW,KAAK,MAAM,KAAK,MAAM,QAAQ,GAAG,CAAC;EAElC,CAAC,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC;CAC5C;AACF;;;;;AAMA,SAAgB,WAAW,QAAwB,WAAsB,MAAyB;CAChG,MAAM,QAAQ,WAAW,WAAW,IAAI;CACxC,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,QAAQ,SAAS,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;CACzE,MAAM,QAAQ,YAAY,OAAO,KAAK,MAAM,OAAO,KAAK,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC,CAC9E,QAAQ,MAAM,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,CAClC,KAAK,MAAM,QAAQ,CAAC,CAAC;CACxB,MAAM,OAAO,SAAS,IAAI,IAAI,KAAK,GAAG,OAAO,KAAK,UAAU;CAG5D,MAAM,QAAQ,SAAiB,KAAK;CACpC,MAAM,0BAAU,IAAI,IAAsB;CAC1C,MAAM,2BAAW,IAAI,IAAyB;CAC9C,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,MAAM,EAAE,QAAQ,GAAG,SAAS,MAAM,UAAU,MAAM,IAAI;EACtD,MAAM,MAAM,eAAyB,WAAW,KAAK,SAAS,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,KAAA,CAAS;EAC7G,QAAQ,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,WAAW,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;EACjE,KAAK,MAAM,UAAU,GAAG,CAAC,GAAG;GAC1B,IAAI,CAAC,SAAS,IAAI,MAAM,GAAG,SAAS,IAAI,wBAAQ,IAAI,IAAI,CAAC;GACzD,SAAS,IAAI,MAAM,CAAC,CAAE,IAAI,IAAI;EAChC;EACA,KAAK,MAAM,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,SAAS,IAAI,CAAC;CACtD;CAGA,MAAM,UAAU,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,MAAM,QAAQ,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC,GAAG,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC;CAC3F,MAAM,0BAAU,IAAI,IAAyB;CAC7C,MAAM,2BAAW,IAAI,IAAyB;CAC9C,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,uBAAO,IAAI,IAAY;EAC7B,MAAM,SAAS,MAAc;GAC3B,IAAI,KAAK,IAAI,CAAC,GAAG;GACjB,KAAK,IAAI,CAAC;GACV,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG,SAAS,IAAI,mBAAG,IAAI,IAAI,CAAC;GAC/C,SAAS,IAAI,CAAC,CAAC,CAAE,IAAI,KAAK;GAC1B,KAAK,MAAM,QAAQ,QAAQ,IAAI,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI;EACrD;EACA,MAAM,KAAK;EACX,QAAQ,IAAI,OAAO,IAAI;CACzB;CAIA,MAAM,WAAW,IAAI,IAAyB,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,mBAAG,IAAI,IAAI,CAAC,CAAC,CAAC;CAC7F,MAAM,QAAQ,CAAC,GAAG,SAAS,KAAK,CAAC;CACjC,KAAK,IAAI,UAAU,MAAM,UAAU;EACjC,UAAU;EACV,KAAK,MAAM,CAAC,OAAO,cAAc,UAAU;GACzC,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC,CAAC,KAAK,MAAM,CAAC,GAAI,SAAS,IAAI,CAAC,KAAK,CAAC,CAAE,CAAC;GACjE,IAAI,GAAG,MAAM,MAAM,EAAE,WAAW,CAAC,GAAG;GACpC,MAAM,QAAQ,MAAc,GAAG,OAAO,MAAM,EAAE,OAAO,SAAS,QAAQ,IAAI,IAAI,CAAC,CAAE,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;GAC/H,MAAM,OAAO,IAAI,IAAI,MAAM,OAAO,IAAI,CAAC;GACvC,IAAI,KAAK,OAAO,SAAS,IAAI,KAAK,CAAC,CAAE,MAAM;IACzC,SAAS,IAAI,OAAO,IAAI;IACxB,UAAU;GACZ;EACF;CACF;CAEA,MAAM,yBAAS,IAAI,IAAoB;CACvC,MAAM,0BAAU,IAAI,IAAsB;CAC1C,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,SAAS;CAClC,KAAK,MAAM,QAAQ,QAAQ;EACzB,MAAM,UAAU,CAAC,GAAI,SAAS,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,CAAE,CAAC,CAAC,QAAQ,MAAM,SAAS,IAAI,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS;EAExH,MAAM,QAAQ,QAAQ,WAAW,IAAI,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI;EACnE,IAAI,CAAC,QAAQ,IAAI,KAAK,GAAG,QAAQ,IAAI,OAAO,CAAC,CAAC;EAC9C,QAAQ,IAAI,KAAK,CAAC,CAAE,KAAK,IAAI;CAC/B;CACA,KAAK,MAAM,WAAW,CAAC,GAAG,QAAQ,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,GAAG,KAAK,MAAM,QAAQ,SAAS,OAAO,IAAI,MAAM,QAAQ,EAAG;CAC5H,OAAO;AACT;;;ACxHA,SAAgB,mBACd,QACA,EAAE,OAAO,KAAK,QACmC;CACjD,OAAO,OAAO,QAAQ;EACpB,MAAM,MAAM,IAAI,IAAI,IAAI,GAAG;EAC3B,IAAI,IAAI,aAAa,GAAG,KAAK,mBAAmB,OAAO,UAAU,MAAM,QAAQ,KAAK,IAAI;EACxF,IAAI,IAAI,aAAa,GAAG,KAAK,oBAAoB,OAAO,MAAM,MAAM,QAAQ,KAAK,GAAG;CAEtF;AACF;;;;;AAMA,eAAe,UAAU,QAAwB,KAAU,MAAkD;CAC3G,MAAM,MAAM,IAAI,aAAa,IAAI,KAAK,KAAK;CAC3C,IAAI;CACJ,IAAI;EACF,UAAU,YAAY,MAAM,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,QAAQ,GAAG;CAClE,SAAS,GAAG;EACV,OAAO,OAAO,CAAC;CACjB;CACA,IAAI,YAAY,KAAA,GACd,OAAO,IAAI,SAAS,iBAAiB,IAAI,+DAA+D,EAAE,QAAQ,IAAI,CAAC;CACzH,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI,KAAA,IAAa,IAAI,aAAa,IAAI,QAAQ,KAAK,KAAA;CAChG,OAAO,KAAK,cAAc,QAAQ,SAAS,MAAM,CAAC;AACpD;;;;;;AAOA,eAAe,MAAM,QAAwB,KAAc,KAA6B;CACtF,IAAI,IAAI,WAAW,OACjB,IAAI;EACF,OAAO,SAAS,KAAK,UAAU,QAAQ,IAAI,aAAa,IAAI,KAAK,KAAK,EAAE,CAAC;CAC3E,SAAS,GAAG;EACV,OAAO,OAAO,CAAC;CACjB;CAEF,MAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ;CACvC,MAAM,aAAa,WAAW,QAAQ,IAAI,IAAI,MAAM,CAAC,CAAC,SAAS,IAAI;CACnE,IAAI,IAAI,WAAW,UAAU,CAAC,cAAc,IAAI,QAAQ,IAAI,cAAc,CAAC,EAAE,WAAW,kBAAkB,MAAM,MAC9G,OAAO,SAAS,KAAK,EAAE,UAAU,CAAC;EAAE,UAAU;EAAS,SAAS;CAAoC,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;CAC3H,IAAI;EACF,MAAM,EAAE,KAAK,MAAM,UAAW,MAAM,IAAI,KAAK;EAC7C,MAAM,SAAS,UAAU,QAAQ,KAAK,MAAM,UAAU,IAAI;EAC1D,OAAO,SAAS,KAAK,QAAQ,EAAE,QAAQ,OAAO,SAAS,SAAS,IAAI,MAAM,IAAI,CAAC;CACjF,SAAS,GAAG;EACV,OAAO,OAAO,CAAC;CACjB;AACF;;AAGA,MAAa,YAAY,SAAiB,IAAI,SAAS,MAAM;CAAE,QAAQ;CAAK,SAAS,EAAE,UAAU,KAAK;AAAE,CAAC;AAEzG,SAAS,OAAO,GAAsB;CACpC,OAAO,SAAS,KACd,EAAE,UAAU,aAAa,eAAe,EAAE,WAAW,CAAC;EAAE,UAAU;EAAS,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;CAAE,CAAC,EAAE,GAClI,EAAE,QAAQ,aAAa,eAAe,MAAM,IAAI,CAClD;AACF;;;;;AAMA,SAAgB,eACd,QACuE;CACvE,QAAQ,KAAK,KAAK,SAAS;EAEzB,IAAI,IAAI,KAAK,SAAS,eAAe,MAAM,MAAM,OAAO,KAAK;EAC7D,MAAM,YAAY,IAAI,QAAQ;EAC9B,MAAM,QAAQ,MAAM,QAAQ,SAAS,IAAI,UAAU,KAAK,cAAc,IAAI,QAAQ,QAAQ;EAC1F,MAAM,UAAU,IAAI,QAAQ;EAC5B,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,IAAI,OAAO,GACpD,KAAK,MAAM,KAAK,MAAM,QAAQ,KAAK,IAAI,QAAQ,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,OAAO,MAAM,CAAC;EAE3G,MAAM,SAAmB,CAAC;EAC1B,IAAI,GAAG,SAAS,UAAkB,OAAO,KAAK,KAAK,CAAC;EACpD,IAAI,GAAG,aAAa;GAClB,MAAM,OAAO,IAAI,WAAW,SAAS,IAAI,WAAW,SAAS,KAAA,IAAY,OAAO,OAAO,MAAM;GAC7F,OAAO,IAAI,QAAQ,UAAU,OAAO,IAAI,OAAO;IAAE,QAAQ,IAAI;IAAQ;IAAS;GAAK,CAAC,CAAC,CAAC,CACnF,KAAK,OAAO,aAAa;IACxB,IAAI,aAAa,KAAA,GAAW,OAAO,KAAK;IACxC,IAAI,aAAa,SAAS;IAC1B,SAAS,QAAQ,SAAS,OAAO,SAAS,IAAI,UAAU,MAAM,KAAK,CAAC;IACpE,IAAI,IAAI,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;GACnD,CAAC,CAAC,CACD,OAAO,MAAe;IACrB,IAAI,aAAa;IACjB,IAAI,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;GACpD,CAAC;EACL,CAAC;CACH;AACF"}
@@ -0,0 +1,72 @@
1
+ import { G as Catalog, a as ResolvedConfig, nt as MessageFormatElement } from "./config-CzaJJQ60.js";
2
+ type Entry = readonly [key: string, ast: MessageFormatElement[]];
3
+ interface CompiledLocale {
4
+ /** `const oN = {…};` lines the messages refer to. */
5
+ declarations: string[];
6
+ /** `"key": message,` lines. */
7
+ rows: string[];
8
+ }
9
+ /** One locale's messages as code, keeping only `keep` when given. */
10
+ declare function compileLocale(entries: Iterable<Entry>, keep?: (key: string) => boolean): CompiledLocale;
11
+ /** The locale as an ES module whose default export is its messages. */
12
+ declare function localeModule(compiled: CompiledLocale, banner: string, ts: boolean): string;
13
+ /** The locale as one expression, for a bundler to inline in place of the module. */
14
+ declare function localeExpression(compiled: CompiledLocale): string;
15
+ interface Lock {
16
+ version: 2;
17
+ /** Source text by hash: every text some translation is anchored to. */
18
+ sources: Record<string, string>;
19
+ /** Per locale, per key: `<source hash>:<translation hash>`. */
20
+ translations: Record<string, Record<string, string>>;
21
+ }
22
+ declare function hash(text: string): string;
23
+ declare function emptyLock(): Lock;
24
+ /** The lock at `path`. A version 1 lock, which held only the translations, reads with no source texts. */
25
+ declare function readLock(path: string): Lock;
26
+ declare function serializeLock(lock: Lock): string;
27
+ interface StaleTranslation {
28
+ /** The source the translation was made from, where the lock recorded it. */
29
+ was: string | undefined;
30
+ /** The source now. */
31
+ now: string;
32
+ /** The translation, made from `was`. */
33
+ translation: string;
34
+ }
35
+ /** What changed in the source of a stale translation, as a word diff, or why that isn't known. */
36
+ declare function changeOf(stale: StaleTranslation): string;
37
+ interface Reconciled {
38
+ lock: Lock;
39
+ /** Per locale, per key: the translations made from a source that has changed since. */
40
+ stale: Map<string, Map<string, StaleTranslation>>;
41
+ }
42
+ /**
43
+ * The lock brought up to date with the catalog, and what is stale. `accept` re-anchors the given
44
+ * translations (per locale, keys, or `true` for all of them) to the current source.
45
+ */
46
+ declare function reconcile(catalog: Catalog, previous: Lock, accept?: Map<string, Set<string> | true>): Reconciled;
47
+ /** Every locale the app can switch to, in order: the source, the translations, the pseudo-locale. */
48
+ declare function localesOf(catalog: Catalog, config: ResolvedConfig): string[];
49
+ /** Per locale, the messages its module holds. A translated locale holds only what it translates. */
50
+ declare function localeEntries(catalog: Catalog, config: ResolvedConfig, stale: Map<string, Map<string, StaleTranslation>>): Map<string, Entry[]>;
51
+ /** The JSON Schema an editor reads to complete and explain catalog entries. */
52
+ declare function catalogSchema(config: ResolvedConfig): object;
53
+ interface Generated {
54
+ catalog: Catalog;
55
+ /** Absolute path to content. */
56
+ files: Map<string, string>;
57
+ lock: Lock;
58
+ stale: Map<string, Map<string, StaleTranslation>>;
59
+ entries: Map<string, Entry[]>;
60
+ }
61
+ /** Everything `generate` writes, in memory. */
62
+ declare function render(config: ResolvedConfig, accept?: Map<string, Set<string> | true>): Generated;
63
+ /** Writes what changed. With `check`, writes nothing and returns what would have changed. */
64
+ declare function writeFiles(files: Map<string, string>, check?: boolean): string[];
65
+ declare function generate(config: ResolvedConfig, options?: {
66
+ check?: boolean;
67
+ accept?: Map<string, Set<string> | true>;
68
+ }): Generated & {
69
+ changed: string[];
70
+ };
71
+ export { Entry as _, localesOf as a, localeModule as b, Lock as c, emptyLock as d, hash as f, CompiledLocale as g, serializeLock as h, localeEntries as i, StaleTranslation as l, reconcile as m, catalogSchema as n, render as o, readLock as p, generate as r, writeFiles as s, Generated as t, changeOf as u, compileLocale as v, localeExpression as y };
72
+ //# sourceMappingURL=generate-1BeQAd1O.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-1BeQAd1O.d.ts","names":[],"sources":["../src/tools/compile.ts","../src/tools/lock.ts","../src/tools/generate.ts"],"mappings":";KA0BY,kBAAkB,aAAa,KAAK;UAoG/B;;EAEf;;EAEA;;;iBAIc,cAAc,SAAS,SAAS,QAAQ,QAAQ,0BAA0B;;iBAW1E,aAAa,UAAU,gBAAgB,gBAAgB;;iBAUvD,iBAAiB,UAAU;UCzI1B;EACf;;EAEA,SAAS;;EAET,cAAc,eAAe;;iBAGf,KAAK;iBAIL,aAAa;;iBAKb,SAAS,eAAe;iBASxB,cAAc,MAAM;UASnB;;EAEf;;EAEA;;EAEA;;;iBAIc,SAAS,OAAO;UAKf;EACf,MAAM;;EAEN,OAAO,YAAY,YAAY;;;;;;iBAOjB,UAAU,SAAS,SAAS,UAAU,MAAM,SAAQ,YAAY,sBAAkC;;iBCnDlG,UAAU,SAAS,SAAS,QAAQ;;iBAKpC,cACd,SAAS,SACT,QAAQ,gBACR,OAAO,YAAY,YAAY,qBAC9B,YAAY;;iBA6FC,cAAc,QAAQ;UAwCrB;EACf,SAAS;;EAET,OAAO;EACP,MAAM;EACN,OAAO,YAAY,YAAY;EAC/B,SAAS,YAAY;;;iBAIP,OAAO,QAAQ,gBAAgB,SAAS,YAAY,sBAAsB;;iBAkE1E,WAAW,OAAO,qBAAqB;iBAavC,SACd,QAAQ,gBACR;EAAW;EAAiB,SAAS,YAAY;IAChD;EAAc"}