@jsenv/navi 0.29.27 → 0.29.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,10 @@ between related actions. Parent/child relations are first-class — `.one`,
29
29
 
30
30
  **`Text`** and related components (`Title`, `Paragraph`, `Code`, `Caption`) handle typography consistently across the app.
31
31
 
32
+ ## Texts & i18n
33
+
34
+ `interpolateText`/`Interpolate` keep a sentence containing values readable as one string, and `createI18n` gives the app a single place holding its wording — worth it even with one language. `naviI18n` is where navi's own texts (validation messages, button labels, relative time) can be overridden or translated. See [docs/i18n.md](./docs/i18n.md).
35
+
32
36
  ## Icons
33
37
 
34
38
  Icons are a piece that is often missing or painful in web projects. The `Icon` component makes icons behave like text — they scale with font size, inherit color, and align naturally in any layout. No sizing hacks, no SVG wrangling.
@@ -167,17 +167,39 @@ const requestConfirmation = async ({ message, content, anchor }) => {
167
167
  };
168
168
 
169
169
  /**
170
- * Interpolates a template string, replacing [key] placeholders with values.
171
- * Values can be strings or JSX elements (when allowJsx is true).
172
- * Returns a plain string when all replacements are strings, or a Preact
173
- * fragment when JSX values are present and allowJsx is enabled.
170
+ * Interpolates a template string, replacing `[key]` placeholders with values.
171
+ *
172
+ * Usable on its own no i18n instance required whenever a sentence should
173
+ * stay readable as one string instead of being cut into JSX expressions or
174
+ * concatenations. `<Interpolate>` is the JSX form of this function, and
175
+ * `createI18n` runs every translation through it. See `docs/i18n.md`.
174
176
  *
175
177
  * `[]` was chosen as the placeholder delimiter (rather than `{}` or `{{}}`)
176
178
  * because it does not conflict with JSX syntax, JavaScript template literals,
177
179
  * or common punctuation in translated strings.
178
180
  *
179
- * Pass `allowJsx: true` to enable VNode replacements (used by <Interpolate>).
180
- * Without it, all values are coerced to strings.
181
+ * @param {string} template
182
+ * e.g. `"Hello [name], you have [count] messages"`. A non-string is returned
183
+ * untouched, as is any template when `replacements` is missing.
184
+ * @param {object} [replacements]
185
+ * Values keyed by placeholder name. A key can be:
186
+ * - a direct name — `[name]` ← `{ name: "Alice" }`
187
+ * - a dot-path — `[item.label]` ← `{ item: { label: "Book" } }` (a literal
188
+ * `"item.label"` key wins over the path)
189
+ *
190
+ * A value that is a function is called at that point, so an expensive or
191
+ * lazily-known replacement is only computed when the placeholder is actually
192
+ * present in this language's template.
193
+ *
194
+ * A placeholder with no matching value is left in the output as-is
195
+ * (`"[name]"`), making the gap visible rather than silently empty.
196
+ * @param {object} [options]
197
+ * @param {boolean} [options.allowJsx=false]
198
+ * Allow VNode replacements (what `<Interpolate>` passes). Without it, a VNode
199
+ * value warns and is coerced to a string.
200
+ * @returns {string|import("preact").VNode}
201
+ * A plain string when every replacement is a string, a Preact fragment when
202
+ * at least one VNode was interpolated with `allowJsx`.
181
203
  */
182
204
  const interpolateText = (
183
205
  template,
@@ -288,8 +310,7 @@ if (typeof window !== "undefined") {
288
310
  * because that happens to be the browser's or the user's own preference.
289
311
  *
290
312
  * `null` (the default) means no restriction at all: every language the
291
- * browser/user prefers is allowed through, matching this module's previous,
292
- * unrestricted behavior.
313
+ * browser/user prefers is allowed through.
293
314
  */
294
315
  const supportedLanguagesSignal = signal(null);
295
316
 
@@ -376,30 +397,45 @@ const languagesSignal = computed(() => {
376
397
  });
377
398
 
378
399
  /**
379
- * Creates a lightweight i18n instance for translating text in the current locale.
400
+ * Creates a lightweight i18n instance: a central place where an app declares
401
+ * its texts once and reads them back translated into the active language.
402
+ *
403
+ * Worth using even in a single-language app — one registry beats strings
404
+ * scattered across components, and adding a second language later becomes a
405
+ * data change instead of a refactor. See `docs/i18n.md` for how to choose
406
+ * between the two key styles below and how this relates to `naviI18n`.
380
407
  *
381
408
  * @param {object} [options]
382
409
  * @param {string} [options.keyLang]
383
410
  * When set, each key also serves as its own translation for `keyLang`.
384
- * This allows writing keys directly in that language (typically English) so
385
- * only other languages need to be registered:
411
+ * This allows writing keys directly in that language (typically the language
412
+ * the app is written in) so only *other* languages need registering:
386
413
  *
387
414
  * ```js
388
415
  * const i18n = createI18n({ keyLang: "en" });
389
416
  * i18n.add("Hello [name]!", { fr: "Bonjour [name] !" });
390
- * i18n("Hello [name]!", { name: "Alice" }); // "Hello Alice!" (en — key is template)
391
- * i18n("Hello [name]!", { name: "Alice" }); // "Bonjour Alice !" (fr)
417
+ * i18n("Hello [name]!", { name: "Alice" }, { lang: "en" }); // "Hello Alice!"
418
+ * i18n("Hello [name]!", { name: "Alice" }, { lang: "fr" }); // "Bonjour Alice !"
392
419
  * ```
393
420
  *
394
- * Without `keyLang`, keys are opaque identifiers and all languages (including
395
- * the fallback) must be registered explicitly:
421
+ * `keyLang` only applies to keys passed to `add()`/`addAll()`; a key never
422
+ * registered stays opaque and comes back as-is.
423
+ *
424
+ * Without `keyLang`, keys are opaque identifiers and every language
425
+ * (including the one the app was written in) must be registered explicitly:
396
426
  *
397
427
  * ```js
398
428
  * const i18n = createI18n();
399
429
  * i18n.add("greeting", { en: "Hello [name]!", fr: "Bonjour [name] !" });
400
- * i18n("greeting", { name: "Alice" }); // "Hello Alice!" (en)
430
+ * i18n("greeting", { name: "Alice" }, { lang: "en" }); // "Hello Alice!"
401
431
  * ```
402
432
  *
433
+ * @param {string} [options.fallbackLang]
434
+ * Language consulted when the active language has no translation for a key
435
+ * — per key, not per language: a partially translated language falls through
436
+ * to `fallbackLang` only for the keys it is missing. Without it, a missing
437
+ * translation returns the key itself.
438
+ *
403
439
  * @param {string|string[]} [options.runtimeLang]
404
440
  * The active language (BCP 47 tag or ordered array of tags) — named
405
441
  * "runtime" rather than "system" because there is no actual access to the
@@ -413,7 +449,7 @@ const languagesSignal = computed(() => {
413
449
  *
414
450
  * ---
415
451
  *
416
- * ## Bulk registration
452
+ * ## Registration
417
453
  *
418
454
  * **`i18n.add(key, { lang: "translation" })`** — one key, multiple languages.
419
455
  *
@@ -422,18 +458,31 @@ const languagesSignal = computed(() => {
422
458
  * **`i18n.addLangKeys(lang, { key: "translation", ... })`** — full language pack
423
459
  * (useful when loading a JSON translation file).
424
460
  *
461
+ * All three accumulate: registering a key that already exists overwrites that
462
+ * one key and leaves the rest of the language untouched. This is what lets an
463
+ * app override a single built-in navi text without redeclaring the others.
464
+ *
425
465
  * A regional variant (e.g. `"fr-CA"`) automatically inherits all keys from its
426
466
  * parent (`"fr"`) that it does not explicitly override:
427
467
  * ```js
428
468
  * i18n.addLangKeys("fr", { hello: "Bonjour !" });
429
469
  * i18n.addLangKeys("fr-CA", { hello: "Allo !" }); // other "fr" keys inherited
430
470
  * ```
471
+ * Inheritance is resolved at registration time, so register the parent first.
431
472
  *
432
473
  * ---
433
474
  *
434
- * @returns {Function & { add, addAll, addLangKeys, format, languageMap }}
435
- * A callable function — `i18n(key, values?, { lang? })` — with the same
436
- * signature as `i18n.format()`. `format` is kept as an alias.
475
+ * ## Reading
476
+ *
477
+ * **`i18n(key, values?, { lang? })`** the translation for `key`, with
478
+ * `[placeholder]` occurrences replaced from `values` (see `interpolateText`).
479
+ * Returns `key` itself when nothing matches, so an untranslated string still
480
+ * renders something readable. `i18n.format` is an alias of this call.
481
+ *
482
+ * **`i18n.has(key, { lang? })`** — whether a translation genuinely exists,
483
+ * i.e. how to tell "no translation" apart from "translation equal to the key".
484
+ *
485
+ * @returns {Function & { add, addAll, addLangKeys, has, format, languageMap }}
437
486
  */
438
487
  const createI18n = ({ keyLang, fallbackLang, runtimeLang } = {}) => {
439
488
  const languageMap = new Map();
@@ -442,12 +491,10 @@ const createI18n = ({ keyLang, fallbackLang, runtimeLang } = {}) => {
442
491
  // resolve to, so it's what invalidates their own small caches.
443
492
  let languageMapVersion = 0;
444
493
 
445
- // Explicit runtimeLang stays fixed for this instance's lifetime (matches
446
- // the previous behavior exactly). Without one, re-read languagesSignal.value
447
- // fresh on every call instead of freezing it here via languagesSignal.peek()
448
- // once that would silently ignore setPreferredLanguage()/
449
- // setSupportedLanguages() (see lang_signal.js) for the rest of this
450
- // instance's life.
494
+ // Without an explicit runtimeLang, languagesSignal.value is re-read fresh on
495
+ // every call rather than frozen here via languagesSignal.peek() — freezing it
496
+ // would silently ignore setPreferredLanguage()/setSupportedLanguages() (see
497
+ // lang_signal.js) for the rest of this instance's life.
451
498
  const hasExplicitRuntimeLang = runtimeLang !== undefined;
452
499
 
453
500
  // matchBestLang does real work (a Map lookup per candidate, a possible
@@ -622,39 +669,49 @@ const matchBestLang = (lang, languageMap) => {
622
669
  };
623
670
 
624
671
  /**
625
- * The shared i18n instance for all @jsenv/navi components.
626
- *
627
- * Use `naviI18n.add(key, { lang: "translation" })` to register or override
628
- * any text used by navi components. The active language is read from
629
- * `languagesSignal` (see lang_signal.js combines the browser's own
630
- * `navigator.languages`, an optional `setPreferredLanguage()` user override,
631
- * and an optional `setSupportedLanguages()` app-wide allow-list), live on
632
- * every lookup.
633
- *
634
- * Built-in keys (can be overridden):
635
- * - `"time.less_than_minute"` e.g. "in less than a minute"
636
- * - `"time.ongoing"` — e.g. "Ongoing"
637
- * - `"time.tomorrow_at"` e.g. "[day] at [time]" ([day] and [time] are placeholders)
638
- * - `"time.midnight"` e.g. "midnight"
672
+ * The shared i18n instance holding every text @jsenv/navi components display
673
+ * on their own — validation messages, button labels, empty-list messages,
674
+ * relative time wording…
675
+ *
676
+ * It is navi's texts, not the application's: an app registers its own texts in
677
+ * its own `createI18n()` instance and reaches for `naviI18n` only to change
678
+ * what navi itself says, or to add a language navi does not ship. Keys here are
679
+ * opaque identifiers (`"list.empty"`), never the English sentence — the
680
+ * opposite of what an app is advised to do. `docs/i18n.md` explains why.
681
+ *
682
+ * The active language is read from `languagesSignal` (see lang_signal.js —
683
+ * combines the browser's own `navigator.languages`, an optional
684
+ * `setPreferredLanguage()` user override, and an optional
685
+ * `setSupportedLanguages()` app-wide allow-list), live on every lookup.
686
+ *
687
+ * Built-in key namespaces, all overridable — the registrations below are the
688
+ * exhaustive list, read them to find the exact key to override:
689
+ * - `"button.*"` — Clear, Reset, Send, Open, Close, Cancel, Confirm…
690
+ * - `"time.*"` — relative time wording, duration unit symbols, date field placeholders
691
+ * - `"spin.*"` — the ends of a steppable range
692
+ * - `"list.*"` — empty/no-match/failed-rows messages
693
+ * - `"badge_list.*"` — the "+[count] more" overflow badge
694
+ * - `"constraint.*"` — every field validation message
695
+ *
696
+ * Unit names get two derived keys, both optional: `<unit>__plural` and
697
+ * `<unit>__short`. `<Unit>`/`<Quantity>` fall back to the singular when the
698
+ * derived key is missing, and to `Intl.NumberFormat` when the unit itself is
699
+ * not registered at all — so only units Intl gets wrong need registering.
639
700
  *
640
701
  * @example
641
702
  * import { naviI18n } from "@jsenv/navi";
642
703
  *
643
- * // Register unit translations for Quantity:
644
- * naviI18n.add("minute", { en: "minute", fr: "minute" });
645
- * naviI18n.add("minute__plural", { en: "minutes", fr: "minutes" });
646
- *
647
- * // Register multiple keys at once:
648
- * naviI18n.addAll({
649
- * minute: { en: "minute", fr: "minute" },
650
- * minute__plural: { en: "minutes", fr: "minutes" },
651
- * });
652
- *
653
704
  * // Override a built-in text:
654
705
  * naviI18n.add("time.ongoing", { fr: "En cours…" });
655
706
  *
656
- * // Load a full language pack at once:
657
- * naviI18n.addLangKeys("fr", { minute: "minute", "minute__plural": "minutes" });
707
+ * // Teach navi a language it does not ship:
708
+ * naviI18n.addLangKeys("ja", { "list.empty": "項目がありません。" });
709
+ *
710
+ * // Register unit translations used by <Quantity>/<Unit>:
711
+ * naviI18n.addAll({
712
+ * ticket: { en: "ticket", fr: "billet" },
713
+ * ticket__plural: { en: "tickets", fr: "billets" },
714
+ * });
658
715
  */
659
716
  const naviI18n = createI18n();
660
717
 
@@ -30480,7 +30537,7 @@ const getParamScope = (params) => {
30480
30537
  };
30481
30538
 
30482
30539
  /*
30483
- * GET_PAGE: reading a resource one slice at a time, for a list that draws its
30540
+ * GET_RANGE: reading a resource one slice at a time, for a list that draws its
30484
30541
  * rows as it goes (`<List.Items itemsAction>`).
30485
30542
  *
30486
30543
  * It is on purpose not an action. An action keeps the one response it got and
@@ -30496,16 +30553,16 @@ const getParamScope = (params) => {
30496
30553
  * list is holding is the one it was given.
30497
30554
  *
30498
30555
  * The reader is a function, so a list feeds on it the way it feeds on any other
30499
- * source: `itemsAction={GAME.GET_PAGE.bindParams({ radar })}`.
30556
+ * source: `itemsAction={GAME.GET_RANGE.bindParams({ radar })}`.
30500
30557
  */
30501
30558
 
30502
30559
 
30503
- const createPageReader = (
30560
+ const createRangeReader = (
30504
30561
  actionName,
30505
30562
  callback,
30506
30563
  { store, params: boundParams },
30507
30564
  ) => {
30508
- const readPage = async (range = {}) => {
30565
+ const readRange = async (range = {}) => {
30509
30566
  const { signal, ...rangeParams } = range;
30510
30567
  const paramsResolved = { ...resolveParams(boundParams), ...rangeParams };
30511
30568
  const result = await callback(paramsResolved, { signal });
@@ -30520,7 +30577,7 @@ const createPageReader = (
30520
30577
  const startAsked = rangeParams.start;
30521
30578
  if (startAsked === undefined || startAsked < 0) {
30522
30579
  throw new TypeError(
30523
- `${actionName} must say where the page lands (start), it was asked for ${describeRangeAsked(rangeParams)}.`,
30580
+ `${actionName} must say where the range lands (start), it was asked for ${describeRangeAsked(rangeParams)}.`,
30524
30581
  );
30525
30582
  }
30526
30583
  start = startAsked;
@@ -30530,19 +30587,19 @@ const createPageReader = (
30530
30587
  }
30531
30588
  return { items, start, count };
30532
30589
  };
30533
- Object.defineProperty(readPage, "name", { value: actionName });
30534
- readPage.isPageReader = true;
30535
- readPage.bindParams = (paramsToBind) => {
30536
- return createPageReader(actionName, callback, {
30590
+ Object.defineProperty(readRange, "name", { value: actionName });
30591
+ readRange.isRangeReader = true;
30592
+ readRange.bindParams = (paramsToBind) => {
30593
+ return createRangeReader(actionName, callback, {
30537
30594
  store,
30538
30595
  params: boundParams ? { ...boundParams, ...paramsToBind } : paramsToBind,
30539
30596
  });
30540
30597
  };
30541
- return readPage;
30598
+ return readRange;
30542
30599
  };
30543
30600
 
30544
30601
  // Params bound to a reader may be signals (the radar currently on screen); the
30545
- // value they hold when the page is asked for is the one the page is about.
30602
+ // value they hold when the range is asked for is the one the range is about.
30546
30603
  const resolveParams = (params) => {
30547
30604
  if (!params) {
30548
30605
  return {};
@@ -30600,18 +30657,18 @@ const debug$2 = (args) => {
30600
30657
  * - GET / POST / PUT / PATCH → the full item object, e.g. `{ id, name }`
30601
30658
  * - DELETE → the id or `{ id }` of the removed item
30602
30659
  * - GET_MANY / POST_MANY / … → an array of item objects
30603
- * - GET_PAGE → `{ items, start, count }`, one slice of the collection
30660
+ * - GET_RANGE → `{ items, start, count }`, one slice of the collection
30604
30661
  *
30605
- * `GET_PAGE` is a reader rather than an action: it keeps no value and takes no place in
30662
+ * `GET_RANGE` is a reader rather than an action: it keeps no value and takes no place in
30606
30663
  * the rerun graph, so a `<List.Items>` can feed on it slice by slice
30607
- * (`itemsAction={USER.GET_PAGE.bindParams({ team })}`).
30664
+ * (`itemsAction={USER.GET_RANGE.bindParams({ team })}`).
30608
30665
  *
30609
30666
  * A sub-resource of the backend (`/games/:id/candidates`) must be modelled with a
30610
30667
  * relationship method, never as an `op`/`type` discriminator dispatched inside one
30611
30668
  * verb's callback.
30612
30669
  *
30613
30670
  * @param {string} name - resource name, used in action names and error messages
30614
- * @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_PAGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
30671
+ * @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_RANGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
30615
30672
  * @param {string} [restCallbacks.idKey] - primary key property, defaults to `"id"` (or the first `uniqueKeys` entry)
30616
30673
  * @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`); a callback may return a different `id` to rename the item's primary key
30617
30674
  * @see docs/resource.md — relationships, callback return contracts, decision table
@@ -30635,7 +30692,7 @@ const resource = (
30635
30692
 
30636
30693
  GET,
30637
30694
  GET_MANY,
30638
- GET_PAGE,
30695
+ GET_RANGE,
30639
30696
  POST,
30640
30697
  POST_MANY,
30641
30698
  PUT,
@@ -30701,7 +30758,7 @@ const resource = (
30701
30758
  restCallbacks: {
30702
30759
  GET,
30703
30760
  GET_MANY,
30704
- GET_PAGE,
30761
+ GET_RANGE,
30705
30762
  POST,
30706
30763
  POST_MANY,
30707
30764
  PUT,
@@ -31783,11 +31840,11 @@ ${originalActionName} source location: ${locationInfo}`,
31783
31840
  if (restCallback === undefined) {
31784
31841
  continue;
31785
31842
  }
31786
- if (restCallbackKey === "GET_PAGE") {
31787
- // A page is read, never kept: no action, no place in the rerun graph
31788
- // (see resource_page_reader.js).
31789
- stateFacade.GET_PAGE = createPageReader(
31790
- `${name}.GET_PAGE`,
31843
+ if (restCallbackKey === "GET_RANGE") {
31844
+ // A range is read, never kept: no action, no place in the rerun graph
31845
+ // (see resource_range_reader.js).
31846
+ stateFacade.GET_RANGE = createRangeReader(
31847
+ `${name}.GET_RANGE`,
31791
31848
  restCallback,
31792
31849
  { store, params },
31793
31850
  );
@@ -54314,15 +54371,15 @@ const VISIBILITY_HIDDEN_STYLE = {
54314
54371
  * before it knows how many there are), `limit` (how many rows), and
54315
54372
  * `before`/`after`/`around` (the id of a row to count from, for a source
54316
54373
  * paginating by cursor). Answer with the rows (an array — that is all of
54317
- * them), or with a page the way a Content-Range does: `{ items, start, count }`
54374
+ * them), or with a range the way a Content-Range does: `{ items, start, count }`
54318
54375
  * — these rows, at this place, out of that many. May be async. The range also
54319
54376
  * carries a `signal`, aborted when the list stops wanting those rows (the
54320
54377
  * window has moved on) — pass it to fetch to call the request off.
54321
54378
  *
54322
- * A resource answers through its page reader:
54323
- * `itemsAction={GAME.GET_PAGE.bindParams({ radar })}` — the rows are upserted
54379
+ * A resource answers through its range reader:
54380
+ * `itemsAction={GAME.GET_RANGE.bindParams({ radar })}` — the rows are upserted
54324
54381
  * into the store on their way in, so the list draws store items rather than
54325
- * copies of the JSON. The list holds the pages, the store holds the objects
54382
+ * copies of the JSON. The list holds the slices, the store holds the objects
54326
54383
  * (see docs/resource.md).
54327
54384
  *
54328
54385
  * A collection held in memory answers synchronously: `itemsAction={() => rows}`.
@@ -54857,7 +54914,7 @@ const useItemStore = ({
54857
54914
  let result;
54858
54915
  try {
54859
54916
  if (typeof itemsAction !== "function") {
54860
- throw new TypeError(`itemsAction must be a function, received ${itemsAction}. A resource feeds a list through its page reader: itemsAction={RESOURCE.GET_PAGE.bindParams(...)} — its other actions keep one response and cannot answer a range.`);
54917
+ throw new TypeError(`itemsAction must be a function, received ${itemsAction}. A resource feeds a list through its range reader: itemsAction={RESOURCE.GET_RANGE.bindParams(...)} — its other actions keep one response and cannot answer a range.`);
54861
54918
  }
54862
54919
  result = itemsAction(range);
54863
54920
  } catch (e) {
@@ -65944,16 +66001,35 @@ const CodeBox = ({
65944
66001
  */
65945
66002
 
65946
66003
  /**
65947
- * Renders a template string with [key] placeholders replaced by props.
65948
- * Replacement values can be strings or JSX elements.
65949
- * Returns a plain string when all replacements are strings, a fragment otherwise.
66004
+ * Renders a template string with `[key]` placeholders replaced by props.
66005
+ * Every prop other than `children` is a replacement value; values can be
66006
+ * strings or JSX elements. Returns a plain string when all replacements are
66007
+ * strings, a fragment otherwise.
65950
66008
  *
65951
66009
  * Keeps the full sentence readable in one place and makes the string
65952
- * i18n-ready, since the template contains no JSX expressions.
66010
+ * i18n-ready, since the template contains no JSX expressions. `children` must
66011
+ * be a plain string for interpolation to happen.
66012
+ *
66013
+ * Placeholder resolution (dot-paths, function values, unmatched placeholders
66014
+ * left visible) is `interpolateText`'s — read its JSDoc for the details, and
66015
+ * `docs/i18n.md` for how this fits with `naviI18n`/`createI18n`.
65953
66016
  *
65954
66017
  * @example
65955
- * <Interpolate radiusKm={<Text bold>50 km</Text>} zoneName="votre zone">
65956
- * Données limitées à [radiusKm] autour de [zoneName].
66018
+ * <Interpolate radiusKm={<Text bold>50 km</Text>} zoneName="your area">
66019
+ * Data limited to [radiusKm] around [zoneName].
66020
+ * </Interpolate>
66021
+ *
66022
+ * @example
66023
+ * // translated template: the sentence comes from i18n, the JSX from here.
66024
+ * // With keyLang, the key is the sentence itself — same string as above, so
66025
+ * // moving a hardcoded <Interpolate> to i18n is wrapping it in a call.
66026
+ * const i18n = createI18n({ keyLang: "en" });
66027
+ * i18n.add("Data limited to [radiusKm] around [zoneName].", {
66028
+ * fr: "Données limitées à [radiusKm] autour de [zoneName].",
66029
+ * });
66030
+ *
66031
+ * <Interpolate radiusKm={<Text bold>50 km</Text>} zoneName="your area">
66032
+ * {i18n("Data limited to [radiusKm] around [zoneName].")}
65957
66033
  * </Interpolate>
65958
66034
  */
65959
66035
  const Interpolate = ({
@@ -67514,5 +67590,5 @@ const UserSvg = () => jsx("svg", {
67514
67590
  })
67515
67591
  });
67516
67592
 
67517
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
67593
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage, windowWidthSignal };
67518
67594
  //# sourceMappingURL=jsenv_navi.js.map