@nifrajs/web-preact 5.1.0 → 5.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,1187 @@
1
+ # @nifrajs/web-preact
2
+
3
+ ## 5.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1d20d1f: An app registers its locale routing once: `export const i18n = urls` (the router `defineI18nRouting()` returns) from `backend/framework.ts`, which `nifra dev`, `nifra build` and the generated server entry pass to `createWebApp({ i18n })`. Every document then takes `<html lang dir>` from the registry's `documentMeta(localeOf(path))` unless the route's meta sets `lang`, on a hard load and after each client navigation, and carries `Content-Language`. Each adapter's new `useLocale()` returns the page's locale key, and `<I18nProvider>` formats in the page's locale when no `locale` is passed, with no loader handing either over. A route under a `[lang]` or `[[lang]]` directory runs `matchSegment()` before any middleware: an unserved locale, or a page missing from it, is a 404 through the app's `_404`, and a non-canonical prefix such as `/en/x` with an unprefixed default is a 308 to `/x`.
8
+
9
+ ### Patch Changes
10
+
11
+ - 9498cf8: The package ships its `CHANGELOG.md`, so the installed package has the notes for every version, as the GitHub release does. Before, the tarball held only the code, `README.md`, `LICENSE` and `package.json`.
12
+ - Updated dependencies [9498cf8]
13
+ - Updated dependencies [1f3ff32]
14
+ - Updated dependencies [4bb8e52]
15
+ - Updated dependencies [cdff19d]
16
+ - Updated dependencies [5edd0ba]
17
+ - Updated dependencies [000b4cb]
18
+ - Updated dependencies [c5455fb]
19
+ - Updated dependencies [6082ba5]
20
+ - Updated dependencies [fd58beb]
21
+ - Updated dependencies [ea25a61]
22
+ - Updated dependencies [ed13e36]
23
+ - Updated dependencies [281a48e]
24
+ - Updated dependencies [281a48e]
25
+ - Updated dependencies [1d20d1f]
26
+ - Updated dependencies [1d20d1f]
27
+ - Updated dependencies [746ada9]
28
+ - Updated dependencies [1d20d1f]
29
+ - Updated dependencies [b9e7c6b]
30
+ - @nifrajs/core@5.2.0
31
+ - @nifrajs/i18n@5.2.0
32
+ - @nifrajs/image@5.2.0
33
+ - @nifrajs/web@5.2.0
34
+
35
+ ## 5.1.0
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [78b2a04]
40
+ - Updated dependencies [638efe5]
41
+ - Updated dependencies [2a0a5d3]
42
+ - Updated dependencies [5372e3a]
43
+ - Updated dependencies [b276c67]
44
+ - Updated dependencies [29d6649]
45
+ - Updated dependencies [2a0a5d3]
46
+ - Updated dependencies [72e1b4b]
47
+ - Updated dependencies [7bb8a67]
48
+ - Updated dependencies [513ec28]
49
+ - @nifrajs/web@5.1.0
50
+ - @nifrajs/core@5.1.0
51
+ - @nifrajs/i18n@5.1.0
52
+ - @nifrajs/image@5.1.0
53
+
54
+ ## 5.0.0
55
+
56
+ ### Minor Changes
57
+
58
+ - fc0284a: Add opt-in server components across React, Vue, Svelte, Solid and Preact. Closed schemas project public props, owner GET admission and read capabilities govern every refresh, and private responses bypass shared caches. React uses native Flight; Vue, Svelte, Solid and Preact preserve native island state and owned ranges.
59
+
60
+ Add coordinated Vite builds and development generations, guarded component references, generated Bun/Node/Deno/Workers/Pages/Vercel outputs, typed outlets, router and server-function refresh integration, and release certification for browsers, packaged consumers, resource limits and performance. Existing applications retain their default rendering pipeline.
61
+
62
+ Reuse registered Web middleware continuations across requests while retaining request-local state.
63
+
64
+ ### Patch Changes
65
+
66
+ - Updated dependencies [7169820]
67
+ - Updated dependencies [22e3df7]
68
+ - Updated dependencies [a0a02f4]
69
+ - Updated dependencies [0aed425]
70
+ - Updated dependencies [eaf030b]
71
+ - Updated dependencies [7de0397]
72
+ - Updated dependencies [674c658]
73
+ - Updated dependencies [de27557]
74
+ - Updated dependencies [2173644]
75
+ - Updated dependencies [4d894b0]
76
+ - Updated dependencies [61901cd]
77
+ - Updated dependencies [5e366e2]
78
+ - Updated dependencies [2938a0d]
79
+ - Updated dependencies [dc8439c]
80
+ - Updated dependencies [b19cb37]
81
+ - Updated dependencies [4330e40]
82
+ - Updated dependencies [4199ff3]
83
+ - Updated dependencies [90d9ee6]
84
+ - Updated dependencies [364a234]
85
+ - Updated dependencies [c06cba5]
86
+ - Updated dependencies [f4d3852]
87
+ - Updated dependencies [4a82ae0]
88
+ - Updated dependencies [0ff867a]
89
+ - Updated dependencies [e7391bf]
90
+ - Updated dependencies [0b80a59]
91
+ - Updated dependencies [c64db12]
92
+ - Updated dependencies [3753104]
93
+ - Updated dependencies [4b3b1fa]
94
+ - Updated dependencies [3562557]
95
+ - Updated dependencies [53087f3]
96
+ - Updated dependencies [2def6c7]
97
+ - Updated dependencies [b5b5bc3]
98
+ - Updated dependencies [22e1777]
99
+ - Updated dependencies [3a483cd]
100
+ - Updated dependencies [35f52c7]
101
+ - Updated dependencies [343cb81]
102
+ - Updated dependencies [412a9ee]
103
+ - Updated dependencies [65883e5]
104
+ - Updated dependencies [0e20529]
105
+ - Updated dependencies [d91fd44]
106
+ - Updated dependencies [f7e36ec]
107
+ - Updated dependencies [262e46d]
108
+ - Updated dependencies [b13e071]
109
+ - Updated dependencies [944623d]
110
+ - Updated dependencies [6d9869d]
111
+ - Updated dependencies [077657f]
112
+ - Updated dependencies [b9cc68c]
113
+ - Updated dependencies [de2cc1d]
114
+ - Updated dependencies [a5963cb]
115
+ - Updated dependencies [05aac0e]
116
+ - Updated dependencies [1a6616f]
117
+ - Updated dependencies [ecd738e]
118
+ - Updated dependencies [7775c2b]
119
+ - Updated dependencies [05587e0]
120
+ - Updated dependencies [c75818d]
121
+ - Updated dependencies [3db36b3]
122
+ - Updated dependencies [7775c2b]
123
+ - Updated dependencies [09c47c5]
124
+ - Updated dependencies [326caee]
125
+ - Updated dependencies [f8e0ed5]
126
+ - Updated dependencies [a588c18]
127
+ - Updated dependencies [1cdcfe5]
128
+ - Updated dependencies [e195fe8]
129
+ - Updated dependencies [846e2f6]
130
+ - Updated dependencies [55f4fdb]
131
+ - Updated dependencies [7775c2b]
132
+ - Updated dependencies [d3e79c4]
133
+ - Updated dependencies [d922f65]
134
+ - Updated dependencies [aadd9fc]
135
+ - Updated dependencies [ea16748]
136
+ - Updated dependencies [a1eda0a]
137
+ - Updated dependencies [fc0284a]
138
+ - Updated dependencies [5b22f4d]
139
+ - Updated dependencies [1b94593]
140
+ - Updated dependencies [9f58295]
141
+ - Updated dependencies [2f7299a]
142
+ - Updated dependencies [29e62a0]
143
+ - Updated dependencies [c613e7d]
144
+ - Updated dependencies [5975163]
145
+ - Updated dependencies [b9608ac]
146
+ - Updated dependencies [7a165de]
147
+ - Updated dependencies [4064f36]
148
+ - Updated dependencies [d8f11a5]
149
+ - Updated dependencies [e9e02cf]
150
+ - Updated dependencies [b70b18a]
151
+ - Updated dependencies [6f42b38]
152
+ - Updated dependencies [870227d]
153
+ - Updated dependencies [48aad8c]
154
+ - Updated dependencies [2fbb4f0]
155
+ - Updated dependencies [1d66252]
156
+ - Updated dependencies [a6a6834]
157
+ - Updated dependencies [166a6ff]
158
+ - Updated dependencies [79be5af]
159
+ - Updated dependencies [41050ed]
160
+ - Updated dependencies [fc837cb]
161
+ - Updated dependencies [bf6eeb0]
162
+ - Updated dependencies [5a3c27a]
163
+ - Updated dependencies [15a3a66]
164
+ - Updated dependencies [b80ce77]
165
+ - Updated dependencies [9b971fc]
166
+ - Updated dependencies [2577fc7]
167
+ - Updated dependencies [9b1acc9]
168
+ - Updated dependencies [391ab3c]
169
+ - Updated dependencies [3bf7d6b]
170
+ - Updated dependencies [7775c2b]
171
+ - Updated dependencies [ab1935b]
172
+ - Updated dependencies [740109f]
173
+ - Updated dependencies [e0a25fe]
174
+ - Updated dependencies [3630b6e]
175
+ - Updated dependencies [1290c6d]
176
+ - Updated dependencies [d0a7d58]
177
+ - @nifrajs/web@5.0.0
178
+ - @nifrajs/core@5.0.0
179
+ - @nifrajs/i18n@5.0.0
180
+ - @nifrajs/image@5.0.0
181
+
182
+ ## 4.0.2
183
+
184
+ ### Patch Changes
185
+
186
+ - Updated dependencies [fcd6a5b]
187
+ - @nifrajs/web@4.0.2
188
+ - @nifrajs/core@4.0.2
189
+ - @nifrajs/i18n@4.0.2
190
+ - @nifrajs/image@4.0.2
191
+
192
+ ## 4.0.1
193
+
194
+ ### Patch Changes
195
+
196
+ - Updated dependencies [bbb2b27]
197
+ - Updated dependencies [2957acd]
198
+ - Updated dependencies [4bd292a]
199
+ - Updated dependencies [55e813b]
200
+ - @nifrajs/web@4.0.1
201
+ - @nifrajs/core@4.0.1
202
+ - @nifrajs/i18n@4.0.1
203
+ - @nifrajs/image@4.0.1
204
+
205
+ ## 4.0.0
206
+
207
+ ### Minor Changes
208
+
209
+ - f70f99b: feat(i18n): selectordinal, fallback catalogs, typed nested catalogs and the locale cookie
210
+
211
+ The formatter supports `selectordinal` (`{n, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}`).
212
+ Inside a `plural` or `selectordinal` case, `#` is now the number in the locale's own format
213
+ (`1,000 items`, `1.000 Artikel`) instead of its plain digits - the output changes for counts of 1,000
214
+ and more and for fractions.
215
+
216
+ `createFormatter(locale, messages, options)` takes `fallback` catalogs, tried in order for a key the
217
+ catalog lacks (`locales.chain()` gives the order); `onMissing(key, locale)`, called once per key when
218
+ no catalog has it; and `timeZone` and `numberingSystem` defaults for `d()`, `n()` and `#`. An invalid
219
+ locale, time zone or numbering system throws at creation. Formatters are cached per catalog and
220
+ options, with bounded caches, so per-request values cannot grow memory.
221
+
222
+ Catalogs may nest: values are messages, lists or blocks, read with dotted keys (`t("home.title")`); a
223
+ flat key that contains a dot is found first, so flat catalogs work unchanged. `get(key)` returns a list
224
+ or block whole. Lookups read own properties only, and an argument named like an `Object.prototype`
225
+ member renders empty unless passed. Declaring `interface Register { messages: typeof en }` on
226
+ `@nifrajs/i18n` types every `t()` and `get()` key, and other locales' catalogs (`Translation`,
227
+ `PartialMessages`) against that shape.
228
+
229
+ `localeCookie(name, locale, { maxAge })` returns the `document.cookie` string for a language switcher,
230
+ byte-identical to the `Set-Cookie` `localeDetector({ persist: true })` writes.
231
+
232
+ Every adapter's `<I18nProvider>` takes `fallback`, `onMissing`, `timeZone` and `numberingSystem`, and
233
+ its `messages` prop is checked against the registered catalog type.
234
+
235
+ - 49f106f: feat(i18n): rich text from catalog messages without HTML
236
+
237
+ `rich(formatter, key, tags, vars)` from the new `@nifrajs/i18n/rich` entry formats a message like
238
+ `t()` and turns its `<name>…</name>` and `<name/>` tags into calls to `tags[name]`, returning the
239
+ message as text and whatever the handlers returned. Tags are bare names (no attributes); a tag
240
+ without an own handler keeps its content as text, an unclosed or stray marker stays literal, and
241
+ interpolated values and `#` are never read for tags. `renderRich(renderer, ...)` is the same for a
242
+ UI framework.
243
+
244
+ React, Preact, Solid and Vue export `rich(t, key, tags, vars)` from `/i18n`, returning one node
245
+ (each handler receives its tag's content as one node, and `<br/>` renders a `<br>` unless `br` is
246
+ given). Svelte exports `<Rich key tags vars>`, which takes one snippet per tag, and `rich()` for the
247
+ parts array. `t()` is unchanged.
248
+
249
+ - 0f6babe: feat(web): a route can export a `handle`, and `useMatches()` reports the rendered chain on every adapter.
250
+
251
+ ```tsx
252
+ // routes/orgs/[org]/_layout.tsx
253
+ import { useMatches } from "@nifrajs/web-react/router";
254
+
255
+ export const handle = { crumb: "Organization" };
256
+
257
+ export default function OrgLayout({
258
+ children,
259
+ }: {
260
+ children: React.ReactNode;
261
+ }) {
262
+ const crumbs = useMatches().flatMap(
263
+ (m) => (m.handle as { crumb?: string } | undefined)?.crumb ?? []
264
+ );
265
+ // ...
266
+ }
267
+ ```
268
+
269
+ `useMatches()` lists the layouts being rendered, outermost first, then the page, each as
270
+ `{ id, pathname, params, data, handle }`: the route file without its extension, the part of the URL
271
+ it covers (never the query), the params it declares, its loader data (`null` without a loader), and
272
+ its `handle` export. The server render and the browser report the same list, so a breadcrumb trail
273
+ hydrates with no mismatch. `handle` is read from the module on each side and never serialized.
274
+
275
+ While a `_loading` page is up it takes the page's place; a nested `_404` reports the layouts it
276
+ renders inside; an `_error` page the server renders reports an empty list. The hook ships from each
277
+ adapter's `/router` entry: an array on React and Preact, a `Ref` on Vue, an accessor on Solid and
278
+ Svelte. An app that never calls it does not bundle it.
279
+
280
+ The Preact, Solid, Vue and Svelte client mounts now pass `params` and `path` to the rendered chain,
281
+ as the server render already did. `UIMatch` and `MatchChain` are exported from `@nifrajs/web`.
282
+
283
+ ### Patch Changes
284
+
285
+ - ee19d29: fix(web-react, web-preact, web-vue, web-solid, web-svelte): a layout keeps its loader data in the
286
+ browser. The mounted router rendered every layout with `data: null` - on the first paint, where the
287
+ server had rendered the layout with its data and the client render no longer matched it, and after
288
+ each client navigation. It now hands each layout the data the router holds for it. On Solid a layout
289
+ also follows a same-route update, so new layout data after a revalidation or a param change arrives
290
+ without the layout remounting.
291
+ - 64e7a42: fix(web): pages that `defer()` keep working under a nonce Content-Security-Policy.
292
+ React, Solid and Preact stream inline scripts to reveal a Suspense boundary that resolves after the
293
+ shell, and none of them carried the document's nonce, so a nonce CSP blocked them and the boundary
294
+ stayed on its fallback. `RenderAdapter.renderToStream` now receives `{ nonce }` as an optional third
295
+ argument (`RenderStreamOptions`), and every adapter that streams scripts applies it: React and Solid
296
+ through their own `nonce` option, Preact on the one island-runtime script it streams. A script the
297
+ app renders itself never inherits the nonce. Vue, Svelte and the vanilla adapter stream no scripts.
298
+ - Updated dependencies [dde125b]
299
+ - Updated dependencies [22e2af8]
300
+ - Updated dependencies [72b62fa]
301
+ - Updated dependencies [aa44e93]
302
+ - Updated dependencies [4a3ee60]
303
+ - Updated dependencies [963694f]
304
+ - Updated dependencies [aad6297]
305
+ - Updated dependencies [dad0d41]
306
+ - Updated dependencies [538adc2]
307
+ - Updated dependencies [f47edd1]
308
+ - Updated dependencies [df9530a]
309
+ - Updated dependencies [3e6973f]
310
+ - Updated dependencies [25e8edf]
311
+ - Updated dependencies [2b5e5fc]
312
+ - Updated dependencies [3b090de]
313
+ - Updated dependencies [da7d792]
314
+ - Updated dependencies [612a296]
315
+ - Updated dependencies [fb14dfa]
316
+ - Updated dependencies [8ae97f6]
317
+ - Updated dependencies [4af6f39]
318
+ - Updated dependencies [ca8b50d]
319
+ - Updated dependencies [b00a889]
320
+ - Updated dependencies [b53d64f]
321
+ - Updated dependencies [66fd712]
322
+ - Updated dependencies [9c3d524]
323
+ - Updated dependencies [738e7a1]
324
+ - Updated dependencies [4801cac]
325
+ - Updated dependencies [1b2d53a]
326
+ - Updated dependencies [25fe13d]
327
+ - Updated dependencies [0852290]
328
+ - Updated dependencies [0589dbe]
329
+ - Updated dependencies [2e2d8c0]
330
+ - Updated dependencies [856f5ce]
331
+ - Updated dependencies [18aa5aa]
332
+ - Updated dependencies [cfd86b3]
333
+ - Updated dependencies [8ff96c9]
334
+ - Updated dependencies [4c46199]
335
+ - Updated dependencies [eef4932]
336
+ - Updated dependencies [6de8686]
337
+ - Updated dependencies [86e2d0f]
338
+ - Updated dependencies [a0cfffa]
339
+ - Updated dependencies [d7892ea]
340
+ - Updated dependencies [93e5e7f]
341
+ - Updated dependencies [214d674]
342
+ - Updated dependencies [4936309]
343
+ - Updated dependencies [ff5a779]
344
+ - Updated dependencies [772249a]
345
+ - Updated dependencies [f70f99b]
346
+ - Updated dependencies [49f106f]
347
+ - Updated dependencies [4936309]
348
+ - Updated dependencies [def0172]
349
+ - Updated dependencies [fc2f019]
350
+ - Updated dependencies [a734fba]
351
+ - Updated dependencies [0e9b167]
352
+ - Updated dependencies [bbdc5a1]
353
+ - Updated dependencies [10bc446]
354
+ - Updated dependencies [e8270d9]
355
+ - Updated dependencies [d942c33]
356
+ - Updated dependencies [ef28ef9]
357
+ - Updated dependencies [ff4a062]
358
+ - Updated dependencies [43ba944]
359
+ - Updated dependencies [46c741a]
360
+ - Updated dependencies [7bfa25e]
361
+ - Updated dependencies [216fe27]
362
+ - Updated dependencies [4936309]
363
+ - Updated dependencies [6e257a6]
364
+ - Updated dependencies [4a03d30]
365
+ - Updated dependencies [8e30090]
366
+ - Updated dependencies [28f3aaf]
367
+ - Updated dependencies [6d20355]
368
+ - Updated dependencies [08250bf]
369
+ - Updated dependencies [6907cbe]
370
+ - Updated dependencies [4b8d8de]
371
+ - Updated dependencies [ba5dd1c]
372
+ - Updated dependencies [d50f73e]
373
+ - Updated dependencies [031c33d]
374
+ - Updated dependencies [8fa902c]
375
+ - Updated dependencies [085e852]
376
+ - Updated dependencies [0dac7ec]
377
+ - Updated dependencies [b64c3ee]
378
+ - Updated dependencies [dc2d4d3]
379
+ - Updated dependencies [bda9637]
380
+ - Updated dependencies [81c720e]
381
+ - Updated dependencies [ed60b23]
382
+ - Updated dependencies [0150ed4]
383
+ - Updated dependencies [ae815ab]
384
+ - Updated dependencies [ea2ee87]
385
+ - Updated dependencies [3442e1c]
386
+ - Updated dependencies [85d636b]
387
+ - Updated dependencies [6c978a1]
388
+ - Updated dependencies [bfe29b6]
389
+ - Updated dependencies [8b99424]
390
+ - Updated dependencies [87783f0]
391
+ - Updated dependencies [135aba4]
392
+ - Updated dependencies [47b0d65]
393
+ - Updated dependencies [e32268d]
394
+ - Updated dependencies [432fec3]
395
+ - Updated dependencies [0e14068]
396
+ - Updated dependencies [8f1b780]
397
+ - Updated dependencies [dcc9ff6]
398
+ - Updated dependencies [5a63dd4]
399
+ - Updated dependencies [c49882f]
400
+ - Updated dependencies [dfd19d8]
401
+ - Updated dependencies [28e091f]
402
+ - Updated dependencies [046e79d]
403
+ - Updated dependencies [6393b1e]
404
+ - Updated dependencies [a158b74]
405
+ - Updated dependencies [ee19d29]
406
+ - Updated dependencies [293d3c8]
407
+ - Updated dependencies [7e1e1c3]
408
+ - Updated dependencies [0cd5f6e]
409
+ - Updated dependencies [2245bee]
410
+ - Updated dependencies [feeec4a]
411
+ - Updated dependencies [3eb6339]
412
+ - Updated dependencies [f784c32]
413
+ - Updated dependencies [e3b2b97]
414
+ - Updated dependencies [5934b5d]
415
+ - Updated dependencies [03a3729]
416
+ - Updated dependencies [bd11269]
417
+ - Updated dependencies [579d9a9]
418
+ - Updated dependencies [3554ad8]
419
+ - Updated dependencies [64e7a42]
420
+ - Updated dependencies [ee19d29]
421
+ - Updated dependencies [d6f806f]
422
+ - Updated dependencies [38cf032]
423
+ - Updated dependencies [b94e5cb]
424
+ - Updated dependencies [0f6babe]
425
+ - Updated dependencies [00f18bf]
426
+ - Updated dependencies [a84f546]
427
+ - Updated dependencies [669b6a2]
428
+ - Updated dependencies [ff25d68]
429
+ - @nifrajs/core@4.0.0
430
+ - @nifrajs/web@4.0.0
431
+ - @nifrajs/i18n@4.0.0
432
+ - @nifrajs/image@4.0.0
433
+
434
+ ## 3.5.0
435
+
436
+ ### Patch Changes
437
+
438
+ - Updated dependencies [6046984]
439
+ - Updated dependencies [ac27343]
440
+ - Updated dependencies [d5b7c22]
441
+ - @nifrajs/core@3.5.0
442
+ - @nifrajs/web@3.5.0
443
+ - @nifrajs/image@3.5.0
444
+ - @nifrajs/i18n@3.5.0
445
+
446
+ ## 3.4.0
447
+
448
+ ### Patch Changes
449
+
450
+ - 8d23613: Add the opt-in `@nifrajs/webmcp` package: typed WebMCP registration, core-backed receipts, deterministic predictive-UI reconciliation, and host-independent conformance checks. Also tighten agent execution cancellation cleanup so aborted local work cannot leak into later turns.
451
+ - Updated dependencies [8d23613]
452
+ - Updated dependencies [8d23613]
453
+ - Updated dependencies
454
+ - Updated dependencies [719d82e]
455
+ - @nifrajs/web@3.4.0
456
+ - @nifrajs/core@3.4.0
457
+ - @nifrajs/i18n@3.4.0
458
+ - @nifrajs/image@3.4.0
459
+
460
+ ## 3.3.0
461
+
462
+ ### Patch Changes
463
+
464
+ - @nifrajs/core@3.3.0
465
+ - @nifrajs/i18n@3.3.0
466
+ - @nifrajs/image@3.3.0
467
+ - @nifrajs/web@3.3.0
468
+
469
+ ## 3.2.0
470
+
471
+ ### Patch Changes
472
+
473
+ - Updated dependencies [652201a]
474
+ - Updated dependencies [3aefb12]
475
+ - Updated dependencies [8b58d1f]
476
+ - Updated dependencies [c4ed8f7]
477
+ - Updated dependencies [25305bb]
478
+ - Updated dependencies [095c320]
479
+ - Updated dependencies [7504864]
480
+ - Updated dependencies [e88c23a]
481
+ - Updated dependencies [c39712e]
482
+ - Updated dependencies [9010fd3]
483
+ - Updated dependencies [7551709]
484
+ - Updated dependencies [ea2356e]
485
+ - Updated dependencies [a816b87]
486
+ - @nifrajs/web@3.2.0
487
+ - @nifrajs/core@3.2.0
488
+ - @nifrajs/image@3.2.0
489
+ - @nifrajs/i18n@3.2.0
490
+
491
+ ## 3.1.0
492
+
493
+ ### Patch Changes
494
+
495
+ - 8b136ee: Framework bindings now share consistent query and fetcher idle behavior while keeping mounted-router state isolated per adapter.
496
+ - Updated dependencies [5b78473]
497
+ - Updated dependencies [1400f6c]
498
+ - Updated dependencies [8b136ee]
499
+ - Updated dependencies [a7db515]
500
+ - @nifrajs/core@3.1.0
501
+ - @nifrajs/i18n@3.1.0
502
+ - @nifrajs/web@3.1.0
503
+ - @nifrajs/image@3.1.0
504
+
505
+ ## 3.0.0
506
+
507
+ ### Patch Changes
508
+
509
+ - 86a555b: The roadmap contract surfaces are now shipped across the public packages: shared island triggers,
510
+ typed content indexes and joins, client loader/action hooks, and unified static, dynamic, and
511
+ intercepting boundary modes. WebSocket routes also support opt-in synchronous outbound validation
512
+ through `sendSchema` + `validateSend`; invalid or asynchronous outbound frames fail closed while the
513
+ default remains type-level only.
514
+ - Updated dependencies [f3d2a35]
515
+ - Updated dependencies [6e43c15]
516
+ - Updated dependencies [293a7fe]
517
+ - Updated dependencies [485ae60]
518
+ - Updated dependencies [627b0ba]
519
+ - Updated dependencies [f0fd370]
520
+ - Updated dependencies [004deee]
521
+ - Updated dependencies [86a555b]
522
+ - Updated dependencies [8c5f4cf]
523
+ - Updated dependencies [f0fd370]
524
+ - Updated dependencies [381bbf3]
525
+ - Updated dependencies [36801ae]
526
+ - Updated dependencies [9acadba]
527
+ - Updated dependencies [99fc683]
528
+ - Updated dependencies [73d894d]
529
+ - @nifrajs/core@3.0.0
530
+ - @nifrajs/web@3.0.0
531
+ - @nifrajs/i18n@3.0.0
532
+ - @nifrajs/image@3.0.0
533
+
534
+ ## 2.14.1
535
+
536
+ ### Patch Changes
537
+
538
+ - Updated dependencies [bf93902]
539
+ - @nifrajs/core@2.14.1
540
+ - @nifrajs/i18n@2.14.1
541
+ - @nifrajs/web@2.14.1
542
+ - @nifrajs/image@2.14.1
543
+
544
+ ## 2.14.0
545
+
546
+ ### Patch Changes
547
+
548
+ - Updated dependencies [489c6b6]
549
+ - Updated dependencies [701961a]
550
+ - Updated dependencies [62e22e2]
551
+ - Updated dependencies [62e22e2]
552
+ - Updated dependencies [62133bf]
553
+ - Updated dependencies [8dffdf4]
554
+ - Updated dependencies [489c6b6]
555
+ - @nifrajs/web@2.14.0
556
+ - @nifrajs/core@2.14.0
557
+ - @nifrajs/i18n@2.14.0
558
+ - @nifrajs/image@2.14.0
559
+
560
+ ## 2.13.0
561
+
562
+ ### Patch Changes
563
+
564
+ - Updated dependencies [e0b2dd6]
565
+ - Updated dependencies [7535ce1]
566
+ - Updated dependencies [1704308]
567
+ - Updated dependencies [6510fdc]
568
+ - @nifrajs/core@2.13.0
569
+ - @nifrajs/web@2.13.0
570
+ - @nifrajs/i18n@2.13.0
571
+ - @nifrajs/image@2.13.0
572
+
573
+ ## 2.12.1
574
+
575
+ ### Patch Changes
576
+
577
+ - Updated dependencies [fba30c7]
578
+ - @nifrajs/core@2.12.1
579
+ - @nifrajs/i18n@2.12.1
580
+ - @nifrajs/web@2.12.1
581
+ - @nifrajs/image@2.12.1
582
+
583
+ ## 2.12.0
584
+
585
+ ### Patch Changes
586
+
587
+ - Updated dependencies [df100d3]
588
+ - Updated dependencies [0efacea]
589
+ - Updated dependencies [cd1732c]
590
+ - Updated dependencies [df100d3]
591
+ - Updated dependencies [9a9346e]
592
+ - Updated dependencies [b5f47c0]
593
+ - Updated dependencies [fc33c0f]
594
+ - Updated dependencies [fa51aba]
595
+ - Updated dependencies [c4e8bb0]
596
+ - Updated dependencies [11d1658]
597
+ - Updated dependencies [33ee9ff]
598
+ - Updated dependencies [ceda72d]
599
+ - Updated dependencies [2c004ca]
600
+ - Updated dependencies [5f71c23]
601
+ - Updated dependencies [3788b36]
602
+ - Updated dependencies [0863ef0]
603
+ - Updated dependencies [ae5338f]
604
+ - Updated dependencies [8847825]
605
+ - Updated dependencies [9a9346e]
606
+ - Updated dependencies [4c2123d]
607
+ - Updated dependencies [5e4e31a]
608
+ - Updated dependencies [24f1787]
609
+ - Updated dependencies [9a9346e]
610
+ - Updated dependencies [b045f9e]
611
+ - Updated dependencies [df07059]
612
+ - Updated dependencies [9a9346e]
613
+ - Updated dependencies [9a9346e]
614
+ - Updated dependencies [dbc0b79]
615
+ - Updated dependencies [bd5c624]
616
+ - Updated dependencies [a5d3f5b]
617
+ - Updated dependencies [00819c5]
618
+ - Updated dependencies [e2bdd4a]
619
+ - Updated dependencies [e2d1939]
620
+ - Updated dependencies [e83e6eb]
621
+ - Updated dependencies [64d25db]
622
+ - Updated dependencies [c55f7a3]
623
+ - Updated dependencies [f8b0097]
624
+ - @nifrajs/core@2.12.0
625
+ - @nifrajs/i18n@2.12.0
626
+ - @nifrajs/web@2.12.0
627
+ - @nifrajs/image@2.12.0
628
+
629
+ ## 2.11.0
630
+
631
+ ### Patch Changes
632
+
633
+ - Updated dependencies [ed5e91c]
634
+ - Updated dependencies [30f5ea3]
635
+ - Updated dependencies [c29e0d0]
636
+ - @nifrajs/web@2.11.0
637
+ - @nifrajs/core@2.11.0
638
+ - @nifrajs/i18n@2.11.0
639
+ - @nifrajs/image@2.11.0
640
+
641
+ ## 2.10.0
642
+
643
+ ### Patch Changes
644
+
645
+ - Updated dependencies [5263c4e]
646
+ - Updated dependencies [15bffdd]
647
+ - Updated dependencies [15bffdd]
648
+ - Updated dependencies [15bffdd]
649
+ - @nifrajs/web@2.10.0
650
+ - @nifrajs/core@2.10.0
651
+ - @nifrajs/image@2.10.0
652
+ - @nifrajs/i18n@2.10.0
653
+
654
+ ## 2.9.1
655
+
656
+ ### Patch Changes
657
+
658
+ - Updated dependencies [01e36fb]
659
+ - @nifrajs/core@2.9.1
660
+ - @nifrajs/web@2.9.1
661
+ - @nifrajs/i18n@2.9.1
662
+ - @nifrajs/image@2.9.1
663
+
664
+ ## 2.9.0
665
+
666
+ ### Patch Changes
667
+
668
+ - Updated dependencies [e05e56d]
669
+ - @nifrajs/core@2.9.0
670
+ - @nifrajs/web@2.9.0
671
+ - @nifrajs/i18n@2.9.0
672
+ - @nifrajs/image@2.9.0
673
+
674
+ ## 2.8.2
675
+
676
+ ### Patch Changes
677
+
678
+ - Updated dependencies [f7d68e8]
679
+ - @nifrajs/core@2.8.2
680
+ - @nifrajs/image@2.8.2
681
+ - @nifrajs/web@2.8.2
682
+ - @nifrajs/i18n@2.8.2
683
+
684
+ ## 2.8.1
685
+
686
+ ### Patch Changes
687
+
688
+ - Updated dependencies [78d66a4]
689
+ - Updated dependencies [93fdc89]
690
+ - @nifrajs/core@2.8.1
691
+ - @nifrajs/web@2.8.1
692
+ - @nifrajs/i18n@2.8.1
693
+ - @nifrajs/image@2.8.1
694
+
695
+ ## 2.8.0
696
+
697
+ ### Patch Changes
698
+
699
+ - Updated dependencies [118e4a5]
700
+ - @nifrajs/web@2.8.0
701
+ - @nifrajs/core@2.8.0
702
+ - @nifrajs/i18n@2.8.0
703
+ - @nifrajs/image@2.8.0
704
+
705
+ ## 2.7.1
706
+
707
+ ### Patch Changes
708
+
709
+ - Updated dependencies [52c89e0]
710
+ - @nifrajs/core@2.7.1
711
+ - @nifrajs/web@2.7.1
712
+ - @nifrajs/i18n@2.7.1
713
+ - @nifrajs/image@2.7.1
714
+
715
+ ## 2.7.0
716
+
717
+ ### Patch Changes
718
+
719
+ - @nifrajs/core@2.7.0
720
+ - @nifrajs/i18n@2.7.0
721
+ - @nifrajs/image@2.7.0
722
+ - @nifrajs/web@2.7.0
723
+
724
+ ## 2.6.1
725
+
726
+ ### Patch Changes
727
+
728
+ - 80419f5: Non-hydrated pages (`hydrate: false`) omit the adapter's hydration bootstrap from `<head>` - with no client takeover, scripts like Solid's hydration registry were dead bytes on a static document. The Preact adapter's `renderToString` also returns synchronously once its renderer module is loaded (first call still resolves it lazily), so buffered renders take the synchronous fast path on every subsequent request.
729
+ - Updated dependencies [5840c98]
730
+ - Updated dependencies [80419f5]
731
+ - @nifrajs/core@2.6.1
732
+ - @nifrajs/web@2.6.1
733
+ - @nifrajs/i18n@2.6.1
734
+ - @nifrajs/image@2.6.1
735
+
736
+ ## 2.6.0
737
+
738
+ ### Patch Changes
739
+
740
+ - Updated dependencies [e6349e5]
741
+ - Updated dependencies [08fe221]
742
+ - Updated dependencies [8383063]
743
+ - @nifrajs/web@2.6.0
744
+ - @nifrajs/core@2.6.0
745
+ - @nifrajs/i18n@2.6.0
746
+ - @nifrajs/image@2.6.0
747
+
748
+ ## 2.5.0
749
+
750
+ ### Patch Changes
751
+
752
+ - 02d9aa8: Routing hooks now SSR-render correctly on the dev server. In dev, the adapter is imported by Bun while route modules load through Vite's SSR runner, so the router module could be evaluated twice in one process - two context objects, and `useSearch`/`useParams`/`useLocation` read a context the render never provided. The result was hooks SSR-rendering their empty defaults (`useSearch()` gave `{}`) while the same request's loader saw the validated values; hydration then papered over it on the client, so it surfaced as "the search schema doesn't work in dev". The router context in every adapter is now a `globalThis` singleton (keyed by `Symbol.for`), so both evaluations share the one context React/Vue/Solid/Preact matches providers to readers by. The Vite dev server also mirrors its client `resolve.conditions` into `ssr.resolve.{conditions,externalConditions}`, so dev SSR resolves the same `bun`-conditioned source files Bun does instead of a package's `dist` artifact - which nothing in the dev loop rebuilds, and whose staleness previously 500'd inside framework code.
753
+ - Updated dependencies [02d9aa8]
754
+ - @nifrajs/web@2.5.0
755
+ - @nifrajs/core@2.5.0
756
+ - @nifrajs/i18n@2.5.0
757
+ - @nifrajs/image@2.5.0
758
+
759
+ ## 2.4.0
760
+
761
+ ### Patch Changes
762
+
763
+ - Updated dependencies [1c2bf5a]
764
+ - Updated dependencies [138bfba]
765
+ - Updated dependencies [23e6eb1]
766
+ - @nifrajs/web@2.4.0
767
+ - @nifrajs/core@2.4.0
768
+ - @nifrajs/i18n@2.4.0
769
+ - @nifrajs/image@2.4.0
770
+
771
+ ## 2.3.0
772
+
773
+ ### Minor Changes
774
+
775
+ - 2d9beaa: Preact, Vue, Solid and Svelte gain `useNavigate` and `useBlocker`, from `@nifrajs/web-<framework>/router`.
776
+
777
+ `useNavigate` returns a programmatic navigate (a path pushes or replaces; a number is a history delta),
778
+ matching the React adapter. `useBlocker` is the unsaved-changes guard - pass a boolean or a
779
+ `({ currentLocation, nextLocation }) => boolean` predicate and get back a `{ state, proceed, reset }`
780
+ value in each framework's own reactive shape (a Vue ref, a Solid accessor, a Svelte store, a plain value
781
+ in Preact). When a navigation is intercepted, `state` becomes `"blocked"`; show your own confirmation and
782
+ call `proceed()` or `reset()`. It also arms the native "Leave site?" prompt on tab close and reload.
783
+
784
+ ```svelte
785
+ <script>
786
+ import { useBlocker } from "@nifrajs/web-svelte/router"
787
+ let dirty = false
788
+ const blocker = useBlocker(() => dirty)
789
+ </script>
790
+
791
+ {#if $blocker.state === "blocked"}
792
+ <dialog open>
793
+ <button on:click={$blocker.reset}>Keep editing</button>
794
+ <button on:click={$blocker.proceed}>Discard</button>
795
+ </dialog>
796
+ {/if}
797
+ ```
798
+
799
+ In Vue, Solid and Svelte the hook is created once, so pass a function to track a changing flag
800
+ (`useBlocker(() => dirty)`); a bare boolean is captured as-is. The guarding itself already ran on every
801
+ adapter at the browser layer - this exposes it as a reactive hook.
802
+
803
+ - c823915: Typed, validated search params: a route declares a `searchSchema` and both its loader and its component read the parsed, validated query.
804
+
805
+ Export a Standard Schema as `searchSchema` from a route. The loader's `ctx.search` becomes the parsed URL query validated against it (typed via `LoaderArgs<typeof app, Env, typeof searchSchema>`), and the component reads the same value with `useSearch<typeof searchSchema>()`. Invalid or hostile input fails closed to the schema's defaults (never a 500); without a `searchSchema`, both are the raw parsed query. Validation runs at match time and the value is derived identically on the server and on client navigation, so a component never parses `window.location.search` by hand and the query it renders hydrates with no mismatch.
806
+
807
+ ```tsx
808
+ export const searchSchema = v.object({
809
+ page: v.optional(v.fallback(v.number(), 1), 1),
810
+ });
811
+
812
+ export async function loader({
813
+ search,
814
+ api,
815
+ }: LoaderArgs<typeof backend, unknown, typeof searchSchema>) {
816
+ return { rows: await api.reports.list(search).get() }; // search.page is a number
817
+ }
818
+
819
+ export default function Reports({ data }) {
820
+ const { page } = useSearch<typeof searchSchema>(); // page: number, SSR-correct
821
+ return <Pager page={page} />;
822
+ }
823
+ ```
824
+
825
+ A `_layout` can declare its own `searchSchema` for keys shared across a section (`?org`, `?theme`); the route's effective search merges the layout chain's schemas with the page's, page-wins on a conflict, so both the layout and the page read their validated slice from one object.
826
+
827
+ A route can also list `searchClientKeys` - search keys that are purely client-side UI (`?tab`, a client-side `?sort`, `?modal`). When a client navigation changes only those keys, the URL updates (so `useSearch` re-renders) without re-running the loader; any other key change revalidates as before, so data is never stale.
828
+
829
+ `useSearch` ships on every adapter - React (a value), Preact (a value), Vue (a `Ref`), Solid (an `Accessor`), and Svelte (an accessor), each in that framework's own shape.
830
+
831
+ `navigate` gains an object form on every adapter: `navigate({ to, search, replace })` serializes `search` onto `to` (no hand-built query strings). Run `nifra sync-routes` to generate `nifra-routes.d.ts` (each static route mapped to its schema output) and include it in your tsconfig, and `search` is typed against the target route's `searchSchema` - a wrong shape for a known route is a compile error, while an unmapped path takes a loose `search`. Regenerated from the route files, so a stale shape becomes a `tsc` error. The string-path and history-delta forms are unchanged.
832
+
833
+ ```ts
834
+ navigate({ to: "/reports", search: { page: 2 } }); // search typed against /reports's schema
835
+ ```
836
+
837
+ - 62a8d03: Add `useServerFn` - a server function's pending, data and error state - to all five adapters.
838
+
839
+ ```tsx
840
+ const addTodo = useServerFn(fns.addTodo)
841
+ <button disabled={addTodo.pending} onClick={() => addTodo.call({ text }).catch(() => {})}>add</button>
842
+ ```
843
+
844
+ Calling a server function never needed a binding: the client stub is `(input) => Promise<Output>`.
845
+ This adds only the state a component wants around it.
846
+
847
+ The state machine is `@nifrajs/web`'s `createServerFnStore`, shared by every adapter, so "is it
848
+ pending" has one answer rather than five that drift. Each binding contributes just its subscription
849
+ primitive: `useSyncExternalStore` (React, Preact), a signal (Solid), a `shallowRef` (Vue), a `readable`
850
+ (Svelte).
851
+
852
+ Two behaviours worth knowing:
853
+
854
+ - **The last call wins.** A response that is no longer the newest is discarded rather than written, so
855
+ a slow first call landing after a fast second cannot overwrite fresh data with stale.
856
+ - **`call` still rejects.** The error is recorded for rendering AND the promise rejects, so `await`
857
+ behaves normally. A caller that only renders from state should attach `.catch(() => {})`, as with
858
+ `useFetcher`'s `submit`.
859
+
860
+ `data` is kept while the next call is in flight, so a rendered list does not blank on every refetch.
861
+
862
+ ### Patch Changes
863
+
864
+ - ea0a27f: A server function has one type per half, so both the server call and the hook argument are honest.
865
+
866
+ ```ts
867
+ export type ClientServerFn<Input, Output> = (
868
+ input: Input
869
+ ) => MaybePromise<Output>;
870
+ export type ServerFnReference<Input, Output> =
871
+ | ServerFn<Input, Output>
872
+ | ClientServerFn<Input, Output>;
873
+ ```
874
+
875
+ One type could not describe both halves. `ServerFn` is the SERVER declaration and takes `(input,
876
+ context)`; the client imports a generated stub that takes one argument. Widening the single type so a
877
+ one-argument call compiled made a direct server call type-check while handing the declaration
878
+ `undefined` for a context its implementation requires - a runtime failure the compiler had just been
879
+ told to allow.
880
+
881
+ Now the two are separate and `useServerFn` accepts either through `ServerFnReference`, which is the
882
+ one place the two halves legitimately meet. Calling a declaration from your own server code needs the
883
+ context, and omitting it is a compile error again.
884
+
885
+ - Updated dependencies [6f5b3ad]
886
+ - Updated dependencies [85b354d]
887
+ - Updated dependencies [7293a1c]
888
+ - Updated dependencies [8514caa]
889
+ - Updated dependencies [ea0a27f]
890
+ - Updated dependencies [ea0a27f]
891
+ - Updated dependencies [45b0733]
892
+ - Updated dependencies [c42d777]
893
+ - Updated dependencies [ea0a27f]
894
+ - Updated dependencies [b271164]
895
+ - Updated dependencies [8c77d47]
896
+ - Updated dependencies [ea0a27f]
897
+ - Updated dependencies [ea0a27f]
898
+ - Updated dependencies [d190b1c]
899
+ - Updated dependencies [a4ecca9]
900
+ - Updated dependencies [de8d992]
901
+ - Updated dependencies [a92104e]
902
+ - Updated dependencies [5fe332a]
903
+ - Updated dependencies [c823915]
904
+ - Updated dependencies [d2840ac]
905
+ - Updated dependencies [62a8d03]
906
+ - Updated dependencies [dcacfe7]
907
+ - Updated dependencies [28704d7]
908
+ - Updated dependencies [ea0a27f]
909
+ - Updated dependencies [0c2de22]
910
+ - @nifrajs/core@2.3.0
911
+ - @nifrajs/web@2.3.0
912
+ - @nifrajs/i18n@2.3.0
913
+ - @nifrajs/image@2.3.0
914
+
915
+ ## 2.2.0
916
+
917
+ ### Minor Changes
918
+
919
+ - 1394641: Layout loaders: request data in the component that wraps every page.
920
+
921
+ `routes/_layout.tsx` rendered, but a `loader` it exported never ran, so nothing request-derived could
922
+ reach a layout - host, session, locale, feature flags, tenant. An app hit this and moved its host guard
923
+ out of the component tree into the server entry, where it could not be typechecked with the rest of the
924
+ app. That is the real cost: the gap pushed security-relevant code to the one place nifra's typed-boundary
925
+ promise does not reach. Remix, React Router and SvelteKit all support this; nifra was alone in not.
926
+
927
+ ```tsx
928
+ // routes/orgs/[org]/_layout.tsx
929
+ export const gate = true // optional; see below
930
+ export async function loader({ params, req }) {
931
+ return { org: await findOrg(params.org) } // params is { org } - nothing deeper
932
+ }
933
+ export default function Layout({ data, children }) { … }
934
+ ```
935
+
936
+ **Scoped, not global.** A layout owns the URL prefix it wraps, so it receives only the params inside
937
+ that prefix and its loader is skipped on a navigation that did not change them. Navigating
938
+ `/orgs/acme/a` → `/orgs/acme/b` does not re-run the org layout's loader. Scope is derived at build time
939
+ per `(route, layout)` pair, because one layout can own different params on different expanded patterns:
940
+ `[[lang]]/docs/_layout` owns nothing on `/docs/:slug` and `{lang}` on `/:lang/docs/:slug`. Layouts are
941
+ not router nodes and did not become any - the router is untouched.
942
+
943
+ **Execution order is declared, and this matters for security.** By default a layout loader runs in
944
+ parallel with the page's, which is right for data and wrong for a guard: a page loader running
945
+ concurrently with a guard has already queried by the time the guard says no. `export const gate = true`
946
+ makes a layout blocking - nothing beneath it runs until it resolves, and nothing beneath a rejected gate
947
+ runs at all. **A layout loader without `gate: true` is not an authorization boundary.** Gates also run on
948
+ the data-only request, so a client navigation cannot bypass one by sending the data header, and a gate is
949
+ never skipped by the retention hint.
950
+
951
+ A layout may throw `notFound()` / `gone()` / `redirect()`. Its errors resolve to the `_error` boundary at
952
+ or above its OWN segment, never one below it - rendering there would wrap the boundary in the very layout
953
+ whose loader just failed.
954
+
955
+ Every adapter passes each layout its own data. A layout with no loader receives `null`, and an app where
956
+ no layout has a loader emits byte-identical HTML and unchanged props.
957
+
958
+ The data-mode response becomes a versioned envelope when a chain carries layout data. It is recognised
959
+ by structure, and the bare pre-envelope shape is still accepted - a prerendered `_data.json` is a static
960
+ file that outlives the deploy that wrote it.
961
+
962
+ ### Patch Changes
963
+
964
+ - Updated dependencies [39b1670]
965
+ - Updated dependencies [d428f52]
966
+ - Updated dependencies [135d0c6]
967
+ - Updated dependencies [1394641]
968
+ - Updated dependencies [e713cab]
969
+ - Updated dependencies [a4645e2]
970
+ - Updated dependencies [a7d740a]
971
+ - Updated dependencies [6e996a1]
972
+ - Updated dependencies [15ad6ca]
973
+ - Updated dependencies [6aa0aac]
974
+ - Updated dependencies [1857d39]
975
+ - Updated dependencies [6ba3173]
976
+ - Updated dependencies [ca71a2e]
977
+ - Updated dependencies [0fc215b]
978
+ - Updated dependencies [2ff661f]
979
+ - Updated dependencies [a1327a4]
980
+ - Updated dependencies [2500705]
981
+ - @nifrajs/web@2.2.0
982
+ - @nifrajs/i18n@2.2.0
983
+ - @nifrajs/image@2.2.0
984
+
985
+ ## 2.1.0
986
+
987
+ ### Patch Changes
988
+
989
+ - Updated dependencies [bd294bb]
990
+ - @nifrajs/web@2.1.0
991
+ - @nifrajs/i18n@2.1.0
992
+ - @nifrajs/image@2.1.0
993
+
994
+ ## 2.0.0
995
+
996
+ ### Patch Changes
997
+
998
+ - Updated dependencies [ade0c7a]
999
+ - Updated dependencies [d91a45b]
1000
+ - Updated dependencies [d91a45b]
1001
+ - Updated dependencies [e97a92f]
1002
+ - Updated dependencies [e8e49d1]
1003
+ - Updated dependencies [a7d34e5]
1004
+ - @nifrajs/web@2.0.0
1005
+ - @nifrajs/i18n@2.0.0
1006
+ - @nifrajs/image@2.0.0
1007
+
1008
+ ## 1.13.0
1009
+
1010
+ ### Patch Changes
1011
+
1012
+ - Updated dependencies [5b6127a]
1013
+ - @nifrajs/web@1.13.0
1014
+ - @nifrajs/i18n@1.13.0
1015
+ - @nifrajs/image@1.13.0
1016
+
1017
+ ## 1.12.0
1018
+
1019
+ ### Patch Changes
1020
+
1021
+ - @nifrajs/web@1.12.0
1022
+ - @nifrajs/i18n@1.12.0
1023
+ - @nifrajs/image@1.12.0
1024
+
1025
+ ## 1.11.0
1026
+
1027
+ ### Patch Changes
1028
+
1029
+ - Updated dependencies [5638ada]
1030
+ - @nifrajs/web@1.11.0
1031
+ - @nifrajs/i18n@1.11.0
1032
+ - @nifrajs/image@1.11.0
1033
+
1034
+ ## 1.10.0
1035
+
1036
+ ### Patch Changes
1037
+
1038
+ - @nifrajs/web@1.10.0
1039
+ - @nifrajs/i18n@1.10.0
1040
+ - @nifrajs/image@1.10.0
1041
+
1042
+ ## 1.9.1
1043
+
1044
+ ### Patch Changes
1045
+
1046
+ - Updated dependencies [3eb27ae]
1047
+ - @nifrajs/web@1.9.1
1048
+ - @nifrajs/i18n@1.9.1
1049
+ - @nifrajs/image@1.9.1
1050
+
1051
+ ## 1.9.0
1052
+
1053
+ ### Patch Changes
1054
+
1055
+ - Updated dependencies [0e1b4cc]
1056
+ - Updated dependencies [6b67833]
1057
+ - @nifrajs/web@1.9.0
1058
+ - @nifrajs/i18n@1.9.0
1059
+ - @nifrajs/image@1.9.0
1060
+
1061
+ ## 1.8.0
1062
+
1063
+ ### Patch Changes
1064
+
1065
+ - Updated dependencies [1ffd48b]
1066
+ - @nifrajs/web@1.8.0
1067
+ - @nifrajs/i18n@1.8.0
1068
+ - @nifrajs/image@1.8.0
1069
+
1070
+ ## 1.7.0
1071
+
1072
+ ### Patch Changes
1073
+
1074
+ - Updated dependencies [9f23e90]
1075
+ - @nifrajs/web@1.7.0
1076
+ - @nifrajs/i18n@1.7.0
1077
+ - @nifrajs/image@1.7.0
1078
+
1079
+ ## 1.6.0
1080
+
1081
+ ### Patch Changes
1082
+
1083
+ - @nifrajs/i18n@1.6.0
1084
+ - @nifrajs/image@1.6.0
1085
+ - @nifrajs/web@1.6.0
1086
+
1087
+ ## 1.5.0
1088
+
1089
+ ### Patch Changes
1090
+
1091
+ - @nifrajs/web@1.5.0
1092
+ - @nifrajs/i18n@1.5.0
1093
+ - @nifrajs/image@1.5.0
1094
+
1095
+ ## 1.4.0
1096
+
1097
+ ### Patch Changes
1098
+
1099
+ - Updated dependencies [4d25970]
1100
+ - @nifrajs/web@1.4.0
1101
+ - @nifrajs/i18n@1.4.0
1102
+ - @nifrajs/image@1.4.0
1103
+
1104
+ ## 1.3.1
1105
+
1106
+ ### Patch Changes
1107
+
1108
+ - @nifrajs/i18n@1.3.1
1109
+ - @nifrajs/image@1.3.1
1110
+ - @nifrajs/web@1.3.1
1111
+
1112
+ ## 1.3.0
1113
+
1114
+ ### Patch Changes
1115
+
1116
+ - Updated dependencies [4a4b1c4]
1117
+ - @nifrajs/web@1.3.0
1118
+ - @nifrajs/i18n@1.3.0
1119
+ - @nifrajs/image@1.3.0
1120
+
1121
+ ## 1.2.2
1122
+
1123
+ ### Patch Changes
1124
+
1125
+ - @nifrajs/i18n@1.2.2
1126
+ - @nifrajs/image@1.2.2
1127
+ - @nifrajs/web@1.2.2
1128
+
1129
+ ## 1.2.1
1130
+
1131
+ ### Patch Changes
1132
+
1133
+ - Updated dependencies [c3ebd73]
1134
+ - @nifrajs/web@1.2.1
1135
+ - @nifrajs/i18n@1.2.1
1136
+ - @nifrajs/image@1.2.1
1137
+
1138
+ ## 1.2.0
1139
+
1140
+ ### Patch Changes
1141
+
1142
+ - @nifrajs/web@1.2.0
1143
+ - @nifrajs/i18n@1.2.0
1144
+ - @nifrajs/image@1.2.0
1145
+
1146
+ ## 1.1.0
1147
+
1148
+ ### Patch Changes
1149
+
1150
+ - Updated dependencies [37d2383]
1151
+ - @nifrajs/web@1.1.0
1152
+ - @nifrajs/i18n@1.1.0
1153
+ - @nifrajs/image@1.1.0
1154
+
1155
+ ## 1.0.0
1156
+
1157
+ ### Patch Changes
1158
+
1159
+ - Updated dependencies [f1f0e18]
1160
+ - @nifrajs/web@1.0.0
1161
+ - @nifrajs/i18n@1.0.0
1162
+ - @nifrajs/image@1.0.0
1163
+
1164
+ ## 1.0.0-beta.4
1165
+
1166
+ ### Patch Changes
1167
+
1168
+ - @nifrajs/i18n@1.0.0-beta.4
1169
+ - @nifrajs/image@1.0.0-beta.4
1170
+ - @nifrajs/web@1.0.0-beta.4
1171
+
1172
+ ## 1.0.0-beta.3
1173
+
1174
+ ### Patch Changes
1175
+
1176
+ - @nifrajs/i18n@1.0.0-beta.3
1177
+ - @nifrajs/image@1.0.0-beta.3
1178
+ - @nifrajs/web@1.0.0-beta.3
1179
+
1180
+ ## 0.1.0-beta.2
1181
+
1182
+ ### Patch Changes
1183
+
1184
+ - Updated dependencies [5018546]
1185
+ - @nifrajs/web@0.1.0-beta.2
1186
+ - @nifrajs/i18n@0.1.0-beta.2
1187
+ - @nifrajs/image@0.1.0-beta.2
package/dist/i18n.d.ts CHANGED
@@ -8,7 +8,9 @@
8
8
  import { type Formatter, type FormatterOptions, type MessageKey, type RegisteredMessages, type Translation } from "@nifrajs/i18n";
9
9
  import { type ComponentChildren, type VNode } from "preact";
10
10
  export interface I18nProviderProps extends FormatterOptions {
11
- readonly locale: string;
11
+ /** The locale to format in. Default: the page's locale tag, in an app that registers its locale
12
+ * routing. */
13
+ readonly locale?: string;
12
14
  /** The catalog for `locale`, checked against the registered catalog type when one is declared. */
13
15
  readonly messages: Translation;
14
16
  readonly children?: ComponentChildren;
@@ -20,6 +22,12 @@ export interface I18nProviderProps extends FormatterOptions {
20
22
  export declare function I18nProvider(props: I18nProviderProps): VNode;
21
23
  /** Read the current {@link Formatter} (`{ locale, t, get, n, d }`). Throws if no `<I18nProvider>` is above. */
22
24
  export declare function useT(): Formatter;
25
+ /**
26
+ * The page's locale key (`ur`, the URL segment), in an app that registers its locale routing (`i18n`
27
+ * in `backend/framework.ts`): from the request on the server, from the URL after each navigation.
28
+ * Without one, the nearest `<I18nProvider>`'s locale; with neither, it throws.
29
+ */
30
+ export declare function useLocale(): string;
23
31
  /** Tag handlers for {@link rich}, by tag name: each receives its tag's content as one node. */
24
32
  export type RichTags = Readonly<Record<string, (content: ComponentChildren) => ComponentChildren>>;
25
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,KAAK,iBAAiB,EAA0C,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAenG,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CACtC;AAED;;;sFAGsF;AACtF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAO5D;AAED,+GAA+G;AAC/G,wBAAgB,IAAI,IAAI,SAAS,CAMhC;AAED,+FAA+F;AAC/F,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,iBAAiB,CAAC,CAAC,CAAA;AAalG;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,kBAAkB,EACxD,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EACvB,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAClB,IAAI,CAAC,EAAE,QAAQ,EACf,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,iBAAiB,CAEnB"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,KAAK,iBAAiB,EAA0C,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAgBnG,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD;kBACc;IACd,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CACtC;AAED;;;sFAGsF;AACtF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAc5D;AAED,+GAA+G;AAC/G,wBAAgB,IAAI,IAAI,SAAS,CAMhC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAUlC;AAED,+FAA+F;AAC/F,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,iBAAiB,CAAC,CAAC,CAAA;AAalG;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,kBAAkB,EACxD,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EACvB,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAClB,IAAI,CAAC,EAAE,QAAQ,EACf,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,iBAAiB,CAEnB"}
package/dist/i18n.js CHANGED
@@ -7,8 +7,10 @@
7
7
  */
8
8
  import { createFormatter, } from "@nifrajs/i18n";
9
9
  import { renderRich } from "@nifrajs/i18n/rich";
10
+ import { localeOfProps } from "@nifrajs/web/client";
10
11
  import { createContext, createElement, Fragment } from "preact";
11
12
  import { useContext, useMemo } from "preact/hooks";
13
+ import { RenderPropsContext } from "./router.js";
12
14
  const I18nContext = createContext(null);
13
15
  // Preact's `createElement` overloads don't reconcile a typed `Context.Provider` (required `value`)
14
16
  // under `exactOptionalPropertyTypes` - overload resolution drops `value` and infers the props as `{}`.
@@ -20,7 +22,12 @@ const createProvider = createElement;
20
22
  * on those props, so switching locale rebuilds it and re-renders consumers; formatters are cached by
21
23
  * catalog identity, so an inline `fallback={[en]}` still yields the same instance. */
22
24
  export function I18nProvider(props) {
23
- const { locale, messages, fallback, onMissing, timeZone, numberingSystem } = props;
25
+ const { messages, fallback, onMissing, timeZone, numberingSystem } = props;
26
+ const page = localeOfProps(useContext(RenderPropsContext));
27
+ const locale = props.locale ?? page?.lang;
28
+ if (locale === undefined) {
29
+ throw new Error("[nifra/web-preact] <I18nProvider> needs a `locale`, or an app that registers its locale routing (export `i18n` from backend/framework.ts)");
30
+ }
24
31
  const formatter = useMemo(() => createFormatter(locale, messages, { fallback, onMissing, timeZone, numberingSystem }), [locale, messages, fallback, onMissing, timeZone, numberingSystem]);
25
32
  return createProvider(I18nContext.Provider, { value: formatter }, props.children);
26
33
  }
@@ -32,6 +39,20 @@ export function useT() {
32
39
  }
33
40
  return formatter;
34
41
  }
42
+ /**
43
+ * The page's locale key (`ur`, the URL segment), in an app that registers its locale routing (`i18n`
44
+ * in `backend/framework.ts`): from the request on the server, from the URL after each navigation.
45
+ * Without one, the nearest `<I18nProvider>`'s locale; with neither, it throws.
46
+ */
47
+ export function useLocale() {
48
+ const page = localeOfProps(useContext(RenderPropsContext));
49
+ const formatter = useContext(I18nContext);
50
+ const locale = page?.key ?? formatter?.locale;
51
+ if (locale === undefined) {
52
+ throw new Error("[nifra/web-preact] useLocale() needs an app that registers its locale routing (export `i18n` from backend/framework.ts), or an <I18nProvider> above it");
53
+ }
54
+ return locale;
55
+ }
35
56
  const join = (chunks) => chunks.length === 0
36
57
  ? null
37
58
  : chunks.length === 1
package/dist/i18n.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,eAAe,GAMhB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAsC,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAA0B,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAc,MAAM,QAAQ,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAElD,MAAM,WAAW,GAAG,aAAa,CAAmB,IAAI,CAAC,CAAA;AAEzD,mGAAmG;AACnG,uGAAuG;AACvG,uGAAuG;AACvG,6FAA6F;AAC7F,MAAM,cAAc,GAAG,aAIb,CAAA;AASV;;;sFAGsF;AACtF,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,CAAA;IAClF,MAAM,SAAS,GAAG,OAAO,CACvB,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,EAC3F,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CACnE,CAAA;IACD,OAAO,cAAc,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AACnF,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,IAAI;IAClB,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAKD,MAAM,IAAI,GAAG,CAAC,MAAqC,EAAqB,EAAE,CACxE,MAAM,CAAC,MAAM,KAAK,CAAC;IACjB,CAAC,CAAC,IAAI;IACN,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QACnB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACX,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,CAAA;AAChD,MAAM,WAAW,GAAoC;IACnD,IAAI;IACJ,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC;CAC3C,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,SAAuB,EACvB,GAAkB,EAClB,IAAe,EACf,IAAwC;IAExC,OAAO,UAAU,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAC5D,CAAC"}
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,eAAe,GAMhB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAsC,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAA0B,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAc,MAAM,QAAQ,CAAA;AACnG,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEhD,MAAM,WAAW,GAAG,aAAa,CAAmB,IAAI,CAAC,CAAA;AAEzD,mGAAmG;AACnG,uGAAuG;AACvG,uGAAuG;AACvG,6FAA6F;AAC7F,MAAM,cAAc,GAAG,aAIb,CAAA;AAWV;;;sFAGsF;AACtF,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,KAAK,CAAA;IAC1E,MAAM,IAAI,GAAG,aAAa,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,EAAE,IAAI,CAAA;IACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,2IAA2I,CAC5I,CAAA;IACH,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CACvB,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,EAC3F,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC,CACnE,CAAA;IACD,OAAO,cAAc,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AACnF,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,IAAI;IAClB,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,aAAa,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAC1D,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,MAAM,GAAG,IAAI,EAAE,GAAG,IAAI,SAAS,EAAE,MAAM,CAAA;IAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,wJAAwJ,CACzJ,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAKD,MAAM,IAAI,GAAG,CAAC,MAAqC,EAAqB,EAAE,CACxE,MAAM,CAAC,MAAM,KAAK,CAAC;IACjB,CAAC,CAAC,IAAI;IACN,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QACnB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACX,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,CAAA;AAChD,MAAM,WAAW,GAAoC;IACnD,IAAI;IACJ,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC;CAC3C,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,SAAuB,EACvB,GAAkB,EAClB,IAAe,EACf,IAAwC;IAExC,OAAO,UAAU,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAC5D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/web-preact",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Preact render adapter for @nifrajs/web - streaming SSR + hydration (no build plugin).",
5
5
  "keywords": [
6
6
  "bun",
@@ -89,7 +89,8 @@
89
89
  },
90
90
  "files": [
91
91
  "dist",
92
- "src"
92
+ "src",
93
+ "CHANGELOG.md"
93
94
  ],
94
95
  "sideEffects": false,
95
96
  "scripts": {
@@ -100,10 +101,10 @@
100
101
  "access": "public"
101
102
  },
102
103
  "dependencies": {
103
- "@nifrajs/core": "5.1.0",
104
- "@nifrajs/i18n": "5.1.0",
105
- "@nifrajs/image": "5.1.0",
106
- "@nifrajs/web": "5.1.0",
104
+ "@nifrajs/core": "5.2.0",
105
+ "@nifrajs/i18n": "5.2.0",
106
+ "@nifrajs/image": "5.2.0",
107
+ "@nifrajs/web": "5.2.0",
107
108
  "preact-render-to-string": "^6.5.0"
108
109
  },
109
110
  "peerDependencies": {
package/src/i18n.ts CHANGED
@@ -14,8 +14,10 @@ import {
14
14
  type Translation,
15
15
  } from "@nifrajs/i18n"
16
16
  import { type RichChunks, type RichRenderer, renderRich } from "@nifrajs/i18n/rich"
17
+ import { localeOfProps } from "@nifrajs/web/client"
17
18
  import { type ComponentChildren, createContext, createElement, Fragment, type VNode } from "preact"
18
19
  import { useContext, useMemo } from "preact/hooks"
20
+ import { RenderPropsContext } from "./router.ts"
19
21
 
20
22
  const I18nContext = createContext<Formatter | null>(null)
21
23
 
@@ -30,7 +32,9 @@ const createProvider = createElement as (
30
32
  ) => VNode
31
33
 
32
34
  export interface I18nProviderProps extends FormatterOptions {
33
- readonly locale: string
35
+ /** The locale to format in. Default: the page's locale tag, in an app that registers its locale
36
+ * routing. */
37
+ readonly locale?: string
34
38
  /** The catalog for `locale`, checked against the registered catalog type when one is declared. */
35
39
  readonly messages: Translation
36
40
  readonly children?: ComponentChildren
@@ -41,7 +45,14 @@ export interface I18nProviderProps extends FormatterOptions {
41
45
  * on those props, so switching locale rebuilds it and re-renders consumers; formatters are cached by
42
46
  * catalog identity, so an inline `fallback={[en]}` still yields the same instance. */
43
47
  export function I18nProvider(props: I18nProviderProps): VNode {
44
- const { locale, messages, fallback, onMissing, timeZone, numberingSystem } = props
48
+ const { messages, fallback, onMissing, timeZone, numberingSystem } = props
49
+ const page = localeOfProps(useContext(RenderPropsContext))
50
+ const locale = props.locale ?? page?.lang
51
+ if (locale === undefined) {
52
+ throw new Error(
53
+ "[nifra/web-preact] <I18nProvider> needs a `locale`, or an app that registers its locale routing (export `i18n` from backend/framework.ts)",
54
+ )
55
+ }
45
56
  const formatter = useMemo(
46
57
  () => createFormatter(locale, messages, { fallback, onMissing, timeZone, numberingSystem }),
47
58
  [locale, messages, fallback, onMissing, timeZone, numberingSystem],
@@ -58,6 +69,23 @@ export function useT(): Formatter {
58
69
  return formatter
59
70
  }
60
71
 
72
+ /**
73
+ * The page's locale key (`ur`, the URL segment), in an app that registers its locale routing (`i18n`
74
+ * in `backend/framework.ts`): from the request on the server, from the URL after each navigation.
75
+ * Without one, the nearest `<I18nProvider>`'s locale; with neither, it throws.
76
+ */
77
+ export function useLocale(): string {
78
+ const page = localeOfProps(useContext(RenderPropsContext))
79
+ const formatter = useContext(I18nContext)
80
+ const locale = page?.key ?? formatter?.locale
81
+ if (locale === undefined) {
82
+ throw new Error(
83
+ "[nifra/web-preact] useLocale() needs an app that registers its locale routing (export `i18n` from backend/framework.ts), or an <I18nProvider> above it",
84
+ )
85
+ }
86
+ return locale
87
+ }
88
+
61
89
  /** Tag handlers for {@link rich}, by tag name: each receives its tag's content as one node. */
62
90
  export type RichTags = Readonly<Record<string, (content: ComponentChildren) => ComponentChildren>>
63
91