rune-lab 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/dist/core/design-tokens/props.d.ts +52 -0
  3. package/dist/core/design-tokens/props.d.ts.map +1 -0
  4. package/dist/core/design-tokens/props.js +34 -0
  5. package/dist/core/exchange-rate/strategies.d.ts +52 -0
  6. package/dist/core/exchange-rate/strategies.d.ts.map +1 -0
  7. package/dist/core/exchange-rate/strategies.js +72 -0
  8. package/dist/core/index.d.ts +8 -3
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +8 -3
  11. package/dist/core/internal/message-resolver.d.ts +1 -1
  12. package/dist/core/internal/message-resolver.d.ts.map +1 -1
  13. package/dist/core/layout/types.d.ts +60 -0
  14. package/dist/core/layout/types.d.ts.map +1 -0
  15. package/dist/core/layout/types.js +4 -0
  16. package/dist/core/money/index.d.ts +1 -1
  17. package/dist/core/money/index.d.ts.map +1 -1
  18. package/dist/core/money/index.js +1 -1
  19. package/dist/core/money/money-primitive.d.ts +101 -0
  20. package/dist/core/money/money-primitive.d.ts.map +1 -0
  21. package/dist/core/money/money-primitive.js +161 -0
  22. package/dist/core/money/money.d.ts +74 -2
  23. package/dist/core/money/money.d.ts.map +1 -1
  24. package/dist/core/money/money.js +120 -2
  25. package/dist/core/shortcuts/types.d.ts +60 -0
  26. package/dist/core/shortcuts/types.d.ts.map +1 -0
  27. package/dist/core/shortcuts/types.js +4 -0
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +6 -4
  30. package/dist/state/api.svelte.js +2 -2
  31. package/dist/state/app.svelte.js +1 -1
  32. package/dist/state/auth/index.d.ts +2 -2
  33. package/dist/state/auth/index.js +1 -1
  34. package/dist/state/auth/session.svelte.d.ts +1 -1
  35. package/dist/state/auth/session.svelte.js +7 -5
  36. package/dist/state/auth/types.d.ts +1 -1
  37. package/dist/state/cart.svelte.d.ts +1 -1
  38. package/dist/state/cart.svelte.js +1 -1
  39. package/dist/state/commands.svelte.d.ts +7 -7
  40. package/dist/state/commands.svelte.js +1 -1
  41. package/dist/state/composables/useMoney.d.ts +19 -3
  42. package/dist/state/composables/useMoney.js +70 -6
  43. package/dist/state/composables/useMoneyFilter.d.ts +20 -0
  44. package/dist/state/composables/useMoneyFilter.js +81 -0
  45. package/dist/state/composables/usePersistence.d.ts +1 -1
  46. package/dist/state/composables/usePersistence.js +1 -1
  47. package/dist/state/composables/useRuneLab.d.ts +1 -1
  48. package/dist/state/composables/useRuneLab.js +2 -2
  49. package/dist/state/composables/useShortcuts.d.ts +33 -0
  50. package/dist/state/composables/useShortcuts.js +75 -0
  51. package/dist/state/context.d.ts +1 -0
  52. package/dist/state/context.js +1 -0
  53. package/dist/state/createConfigStore.svelte.d.ts +4 -31
  54. package/dist/state/createConfigStore.svelte.js +62 -51
  55. package/dist/state/currency.svelte.d.ts +13 -9
  56. package/dist/state/currency.svelte.js +26 -10
  57. package/dist/state/currency.test.d.ts +1 -0
  58. package/dist/state/currency.test.js +35 -0
  59. package/dist/state/exchange-rate.svelte.d.ts +43 -0
  60. package/dist/state/exchange-rate.svelte.js +145 -0
  61. package/dist/state/exchange-rate.test.d.ts +1 -0
  62. package/dist/state/exchange-rate.test.js +75 -0
  63. package/dist/state/index.d.ts +26 -19
  64. package/dist/state/index.js +25 -18
  65. package/dist/state/language.svelte.d.ts +3 -10
  66. package/dist/state/language.svelte.js +4 -5
  67. package/dist/state/layout.svelte.d.ts +1 -1
  68. package/dist/state/layout.svelte.js +4 -4
  69. package/dist/state/persistence/drivers.d.ts +1 -1
  70. package/dist/state/persistence/drivers.js +9 -7
  71. package/dist/state/persistence/drivers.test.d.ts +1 -0
  72. package/dist/state/persistence/drivers.test.js +79 -0
  73. package/dist/state/persistence/provider.d.ts +23 -0
  74. package/dist/state/persistence/provider.js +43 -0
  75. package/dist/state/persistence/provider.test.d.ts +1 -0
  76. package/dist/state/persistence/provider.test.js +51 -0
  77. package/dist/state/registry/index.d.ts +44 -0
  78. package/dist/state/registry/index.js +58 -0
  79. package/dist/state/registry/registry.test.d.ts +1 -0
  80. package/dist/state/registry/registry.test.js +112 -0
  81. package/dist/state/registry/types.d.ts +20 -0
  82. package/dist/state/registry/types.js +3 -0
  83. package/dist/state/shortcuts.svelte.js +4 -4
  84. package/dist/state/theme.svelte.d.ts +3 -10
  85. package/dist/state/theme.svelte.js +8 -8
  86. package/dist/state/toast-bridge.d.ts +1 -1
  87. package/dist/state/toast.svelte.js +1 -1
  88. package/dist/ui/components/ApiMonitor.svelte +2 -2
  89. package/dist/ui/components/Icon.svelte +1 -1
  90. package/dist/ui/components/RuneProvider.svelte +28 -8
  91. package/dist/ui/components/RuneProvider.svelte.d.ts +12 -5
  92. package/dist/ui/components/Toaster.svelte +1 -1
  93. package/dist/ui/components/money/MoneyDisplay.svelte +91 -18
  94. package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +15 -3
  95. package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +1 -1
  96. package/dist/ui/components/money/MoneyDisplay.svelte.test.js +45 -2
  97. package/dist/ui/components/money/MoneyInput.svelte +123 -42
  98. package/dist/ui/components/money/MoneyInput.svelte.d.ts +14 -5
  99. package/dist/ui/features/command-palette/CommandPalette.svelte +3 -3
  100. package/dist/ui/features/config/APP_CONFIGURATIONS.d.ts +29 -0
  101. package/dist/ui/features/config/APP_CONFIGURATIONS.js +38 -0
  102. package/dist/ui/features/config/CurrencySelector.svelte +10 -36
  103. package/dist/ui/features/config/LanguageSelector.svelte +10 -33
  104. package/dist/ui/features/config/ResourceSelector.svelte +92 -0
  105. package/dist/ui/features/config/ResourceSelector.svelte.d.ts +25 -0
  106. package/dist/ui/features/config/ThemeSelector.svelte +11 -34
  107. package/dist/ui/features/shortcuts/ShortcutBinder.svelte +17 -0
  108. package/dist/ui/features/shortcuts/ShortcutBinder.svelte.d.ts +7 -0
  109. package/dist/ui/features/shortcuts/ShortcutPalette.svelte +3 -3
  110. package/dist/ui/index.d.ts +5 -1
  111. package/dist/ui/index.js +8 -3
  112. package/dist/ui/layout/ConnectedNavigationPanel.svelte +7 -8
  113. package/dist/ui/layout/ConnectedNavigationPanel.svelte.d.ts +1 -1
  114. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte +5 -3
  115. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte.d.ts +1 -1
  116. package/dist/ui/layout/NavigationPanel.svelte +1 -1
  117. package/dist/ui/layout/NavigationPanel.svelte.d.ts +1 -1
  118. package/dist/ui/layout/WorkspaceLayout.svelte +9 -1
  119. package/dist/ui/layout/WorkspaceLayout.svelte.d.ts +7 -0
  120. package/dist/ui/layout/WorkspaceStrip.svelte +1 -1
  121. package/dist/ui/layout/WorkspaceStrip.svelte.d.ts +1 -1
  122. package/dist/ui/layout/connection-factory.d.ts +50 -0
  123. package/dist/ui/layout/connection-factory.js +58 -0
  124. package/dist/ui/layout/index.d.ts +2 -2
  125. package/dist/ui/layout/index.js +1 -1
  126. package/dist/ui/paraglide/README.md +53 -0
  127. package/dist/ui/paraglide/runtime.d.ts +105 -124
  128. package/dist/ui/paraglide/runtime.js +162 -127
  129. package/dist/ui/paraglide/server.d.ts +6 -17
  130. package/dist/ui/paraglide/server.js +11 -20
  131. package/dist/ui/primitives/DatePicker.svelte +1 -1
  132. package/package.json +8 -8
  133. package/dist/state/daisyui.d.ts +0 -4
@@ -20,7 +20,7 @@ export const baseLocale = "en";
20
20
  * throw new Error('Locale is not available');
21
21
  * }
22
22
  */
23
- export const locales = /** @type {const} */ (["es", "fr", "it", "pt", "en", "de", "ru", "hi", "ar", "zh", "ja", "ko", "vi"]);
23
+ export const locales = /** @type {const} */ (["es","fr","it","pt","en","de","ru","hi","ar","zh","ja","ko","vi"]);
24
24
  /** @type {string} */
25
25
  export const cookieName = "PARAGLIDE_LOCALE";
26
26
  /** @type {number} */
@@ -52,7 +52,7 @@ export const routeStrategies = [];
52
52
  /**
53
53
  * The used URL patterns.
54
54
  *
55
- * @type {Array<{ pattern: string, localized: Array<[Locale, string]> }> }
55
+ * @type {Array<{ pattern: string, localized: Array<[Locale, string]> }>}
56
56
  */
57
57
  export const urlPatterns = [
58
58
  {
@@ -115,11 +115,11 @@ export const urlPatterns = [
115
115
  ];
116
116
  /** @type {string | undefined} */
117
117
  let cachedRouteStrategyUrl;
118
- /** @type {{ match: string; strategy?: Array<string>; exclude?: boolean } | undefined} */
118
+ /** @type {{ match: string; strategy?: typeof strategy; exclude?: boolean } | undefined} */
119
119
  let cachedRouteStrategy;
120
120
  /**
121
121
  * @param {string | URL} url
122
- * @returns {{ match: string; strategy?: Array<string>; exclude?: boolean } | undefined}
122
+ * @returns {{ match: string; strategy?: typeof strategy; exclude?: boolean } | undefined}
123
123
  */
124
124
  function findMatchingRouteStrategy(url) {
125
125
  if (routeStrategies.length === 0) {
@@ -156,7 +156,6 @@ export function getStrategyForUrl(url) {
156
156
  if (routeStrategy &&
157
157
  routeStrategy.exclude !== true &&
158
158
  Array.isArray(routeStrategy.strategy)) {
159
- // @ts-ignore - runtime value is injected and validated by compiler types.
160
159
  return routeStrategy.strategy;
161
160
  }
162
161
  return strategy;
@@ -194,7 +193,6 @@ export const disableAsyncLocalStorage = false;
194
193
  export const experimentalMiddlewareLocaleSplitting = false;
195
194
  export const isServer = typeof window === 'undefined';
196
195
  /** @type {Locale | undefined} */
197
- // @ts-ignore - injected by bundlers at compile time
198
196
  export const experimentalStaticLocale = undefined;
199
197
  /**
200
198
  * Sets the server side async local storage.
@@ -216,16 +214,19 @@ const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = false;
216
214
  const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = true;
217
215
  const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = false;
218
216
 
219
- globalThis.__paraglide = {}
217
+ /** @type {any} */ (globalThis).__paraglide =
218
+ /** @type {any} */ (globalThis).__paraglide ?? {};
219
+ /** @type {any} */ (globalThis).__paraglide.ssr =
220
+ /** @type {any} */ (globalThis).__paraglide.ssr ?? {};
220
221
 
221
222
  /**
222
223
  * This is a fallback to get started with a custom
223
224
  * strategy and avoid type errors.
224
225
  *
225
226
  * The implementation is overwritten
226
- * by \`overwriteGetLocale()\` and \`defineSetLocale()\`.
227
+ * by `overwriteGetLocale()` and `defineSetLocale()`.
227
228
  *
228
- * @type {Locale|undefined}
229
+ * @type {Locale | undefined}
229
230
  */
230
231
  let _locale;
231
232
  let localeInitiallySet = false;
@@ -245,11 +246,11 @@ let localeInitiallySet = false;
245
246
  * console.log('Netherlands 🇳🇱');
246
247
  * }
247
248
  *
248
- * @type {() => Locale}
249
+ * @returns {Locale} The current locale.
249
250
  */
250
251
  export let getLocale = () => {
251
252
  if (experimentalStaticLocale !== undefined) {
252
- return assertIsLocale(experimentalStaticLocale);
253
+ return experimentalStaticLocale;
253
254
  }
254
255
  // if running in a server-side rendering context
255
256
  // retrieve the locale from the async local storage
@@ -283,7 +284,7 @@ export let getLocale = () => {
283
284
  */
284
285
  export function getLocaleForUrl(url) {
285
286
  if (experimentalStaticLocale !== undefined) {
286
- return assertIsLocale(experimentalStaticLocale);
287
+ return experimentalStaticLocale;
287
288
  }
288
289
  const strategyToUse = getStrategyForUrl(url);
289
290
  const resolved = resolveLocaleWithStrategies(strategyToUse, typeof url === "string" ? url : url.href);
@@ -337,11 +338,14 @@ function resolveLocaleWithStrategies(strategyToUse, urlForUrlStrategy) {
337
338
  // Can't await in sync function, skip async strategies
338
339
  continue;
339
340
  }
340
- locale = result;
341
+ if (result !== undefined) {
342
+ return assertIsLocale(result);
343
+ }
341
344
  }
342
345
  }
343
- if (locale !== undefined) {
344
- return assertIsLocale(locale);
346
+ const matchedLocale = toLocale(locale);
347
+ if (matchedLocale) {
348
+ return matchedLocale;
345
349
  }
346
350
  }
347
351
  return undefined;
@@ -359,7 +363,7 @@ function resolveLocaleWithStrategies(strategyToUse, urlForUrlStrategy) {
359
363
  * return Cookies.get('locale') ?? baseLocale
360
364
  * });
361
365
  *
362
- * @type {(fn: () => Locale) => void}
366
+ * @param {() => Locale} fn - The new implementation for `getLocale()`.
363
367
  */
364
368
  export const overwriteGetLocale = (fn) => {
365
369
  getLocale = fn;
@@ -414,7 +418,6 @@ export function getTextDirection(locale = getLocale()) {
414
418
  * Navigates to the localized URL, or reloads the current page
415
419
  *
416
420
  * @param {string} [newLocation] The new location
417
- * @return {undefined}
418
421
  */
419
422
  const navigateOrReload = (newLocation) => {
420
423
  if (newLocation) {
@@ -465,7 +468,7 @@ export let setLocale = (newLocale, options) => {
465
468
  catch {
466
469
  // do nothing, no locale has been set yet.
467
470
  }
468
- /** @type {Array<Promise<any>>} */
471
+ /** @type {Array<Promise<void>>} */
469
472
  const customSetLocalePromises = [];
470
473
  /** @type {string | undefined} */
471
474
  let newLocation = undefined;
@@ -550,7 +553,7 @@ export let setLocale = (newLocale, options) => {
550
553
  return;
551
554
  };
552
555
  /**
553
- * Overwrite the \`setLocale()\` function.
556
+ * Overwrite the `setLocale()` function.
554
557
  *
555
558
  * Use this function to overwrite how the locale is set. For example,
556
559
  * modify a cookie, env variable, or a user's preference.
@@ -564,7 +567,7 @@ export let setLocale = (newLocale, options) => {
564
567
  * @param {SetLocaleFn} fn
565
568
  */
566
569
  export const overwriteSetLocale = (fn) => {
567
- setLocale = /** @type {SetLocaleFn} */ (fn);
570
+ setLocale = fn;
568
571
  };
569
572
 
570
573
  /**
@@ -591,14 +594,32 @@ export let getUrlOrigin = () => {
591
594
  * Use this function in server environments to
592
595
  * define how the URL origin is resolved.
593
596
  *
594
- * @type {(fn: () => string) => void}
597
+ * @param {() => string} fn - The new implementation for `getUrlOrigin()`.
595
598
  */
596
599
  export let overwriteGetUrlOrigin = (fn) => {
597
600
  getUrlOrigin = fn;
598
601
  };
599
602
 
600
603
  /**
601
- * Check if something is an available locale.
604
+ * Coerces a locale-like string to the canonical locale value used by the runtime.
605
+ *
606
+ * @param {unknown} value
607
+ * @returns {Locale | undefined}
608
+ */
609
+ export function toLocale(value) {
610
+ if (typeof value !== "string") {
611
+ return undefined;
612
+ }
613
+ const lowerValue = value.toLowerCase();
614
+ for (const locale of locales) {
615
+ if (locale.toLowerCase() === lowerValue) {
616
+ return locale;
617
+ }
618
+ }
619
+ return undefined;
620
+ }
621
+ /**
622
+ * Check if something is an available locale with the canonical project casing.
602
623
  *
603
624
  * @example
604
625
  * if (isLocale(params.locale)) {
@@ -607,34 +628,26 @@ export let overwriteGetUrlOrigin = (fn) => {
607
628
  * setLocale('en');
608
629
  * }
609
630
  *
610
- * @param {any} locale
631
+ * Use `toLocale()` when you want case-insensitive matching and canonicalization.
632
+ *
633
+ * @param {unknown} locale
611
634
  * @returns {locale is Locale}
612
635
  */
613
636
  export function isLocale(locale) {
614
- if (typeof locale !== "string")
615
- return false;
616
- return !locale
617
- ? false
618
- : locales.some((item) => item.toLowerCase() === locale.toLowerCase());
637
+ return !!locale && locales.some((item) => item === locale);
619
638
  }
620
-
621
639
  /**
622
- * Asserts that the input is a locale.
640
+ * Asserts that the input can be normalized to a locale.
623
641
  *
624
- * @param {any} input - The input to check.
625
- * @returns {Locale} The input if it is a locale.
642
+ * @param {unknown} input - The input to check.
643
+ * @returns {Locale} The input normalized to a Locale.
626
644
  * @throws {Error} If the input is not a locale.
627
645
  */
628
646
  export function assertIsLocale(input) {
629
- if (typeof input !== "string") {
630
- throw new Error(`Invalid locale: ${input}. Expected a string.`);
631
- }
632
- const lowerInput = input.toLowerCase();
633
- const matchedLocale = locales.find((item) => item.toLowerCase() === lowerInput);
634
- if (!matchedLocale) {
635
- throw new Error(`Invalid locale: ${input}. Expected one of: ${locales.join(", ")}`);
636
- }
637
- return matchedLocale;
647
+ const locale = toLocale(input);
648
+ if (locale)
649
+ return locale;
650
+ throw new Error(`Invalid locale: ${input}. Expected one of: ${locales.join(", ")}`);
638
651
  }
639
652
 
640
653
  /**
@@ -653,7 +666,8 @@ export function assertIsLocale(input) {
653
666
  * @example
654
667
  * const locale = extractLocaleFromRequest(request);
655
668
  *
656
- * @type {(request: Request) => Locale}
669
+ * @param {Request} request
670
+ * @returns {Locale}
657
671
  */
658
672
  export const extractLocaleFromRequest = (request) => {
659
673
  return extractLocaleFromRequestWithStrategies(request, getStrategyForUrl(request.url));
@@ -697,13 +711,9 @@ export const extractLocaleFromRequestWithStrategies = (request, strategies) => {
697
711
  // Use extractLocaleFromRequestAsync for custom server strategies
698
712
  continue;
699
713
  }
700
- if (locale !== undefined) {
701
- if (!isLocale(locale)) {
702
- locale = undefined;
703
- }
704
- else {
705
- return assertIsLocale(locale);
706
- }
714
+ const matchedLocale = toLocale(locale);
715
+ if (matchedLocale) {
716
+ return matchedLocale;
707
717
  }
708
718
  }
709
719
  throw new Error("No locale found. There is an error in your strategy. Try adding 'baseLocale' as the very last strategy. Read more here https://inlang.com/m/gerre34r/library-inlang-paraglideJs/errors#no-locale-found");
@@ -736,7 +746,8 @@ export const extractLocaleFromRequestWithStrategies = (request, strategies) => {
736
746
  *
737
747
  * const locale = await extractLocaleFromRequestAsync(request);
738
748
  *
739
- * @type {(request: Request) => Promise<Locale>}
749
+ * @param {Request} request - The request object to extract the locale from.
750
+ * @returns {Promise<Locale>} The extracted locale.
740
751
  */
741
752
  export const extractLocaleFromRequestAsync = async (request) => {
742
753
  /** @type {string|undefined} */
@@ -751,14 +762,14 @@ export const extractLocaleFromRequestAsync = async (request) => {
751
762
  locale = await handler.getLocale(request);
752
763
  }
753
764
  // If we got a valid locale from this custom strategy, use it
754
- if (locale !== undefined && isLocale(locale)) {
755
- return assertIsLocale(locale);
765
+ const matchedLocale = toLocale(locale);
766
+ if (matchedLocale) {
767
+ return matchedLocale;
756
768
  }
757
769
  }
758
770
  }
759
771
  // If no custom strategy provided a valid locale, fall back to sync version
760
- locale = extractLocaleFromRequestWithStrategies(request, strategy);
761
- return assertIsLocale(locale);
772
+ return extractLocaleFromRequestWithStrategies(request, strategy);
762
773
  };
763
774
 
764
775
  /**
@@ -767,7 +778,7 @@ export const extractLocaleFromRequestAsync = async (request) => {
767
778
  * Will return undefined if the document is not available or if the cookie is not set.
768
779
  * The `document` object is not available in server-side rendering, so this function should not be called in that context.
769
780
  *
770
- * @returns {string | undefined}
781
+ * @returns {Locale | undefined}
771
782
  */
772
783
  export function extractLocaleFromCookie() {
773
784
  if (typeof document === "undefined" || !document.cookie) {
@@ -775,10 +786,7 @@ export function extractLocaleFromCookie() {
775
786
  }
776
787
  const match = document.cookie.match(new RegExp(`(^| )${cookieName}=([^;]+)`));
777
788
  const locale = match?.[2];
778
- if (isLocale(locale)) {
779
- return locale;
780
- }
781
- return undefined;
789
+ return toLocale(locale);
782
790
  }
783
791
 
784
792
  /**
@@ -790,9 +798,8 @@ export function extractLocaleFromCookie() {
790
798
  * @example
791
799
  * const locale = extractLocaleFromHeader(request);
792
800
  *
793
- * @type {(request: Request) => Locale}
794
801
  * @param {Request} request - The request object to extract the locale from.
795
- * @returns {string|undefined} The negotiated preferred language.
802
+ * @returns {Locale | undefined} The negotiated preferred language.
796
803
  */
797
804
  export function extractLocaleFromHeader(request) {
798
805
  const acceptLanguageHeader = request.headers.get("accept-language");
@@ -803,20 +810,22 @@ export function extractLocaleFromHeader(request) {
803
810
  .map((lang) => {
804
811
  const [tag, q = "1"] = lang.trim().split(";q=");
805
812
  // Get both the full tag and base language code
806
- const baseTag = tag?.split("-")[0]?.toLowerCase();
813
+ const baseTag = tag?.split("-")[0];
807
814
  return {
808
- fullTag: tag?.toLowerCase(),
815
+ fullTag: tag,
809
816
  baseTag,
810
817
  q: Number(q),
811
818
  };
812
819
  })
813
820
  .sort((a, b) => b.q - a.q);
814
821
  for (const lang of languages) {
815
- if (isLocale(lang.fullTag)) {
816
- return lang.fullTag;
822
+ const fullLocale = toLocale(lang.fullTag);
823
+ if (fullLocale) {
824
+ return fullLocale;
817
825
  }
818
- else if (isLocale(lang.baseTag)) {
819
- return lang.baseTag;
826
+ const baseLocale = toLocale(lang.baseTag);
827
+ if (baseLocale) {
828
+ return baseLocale;
820
829
  }
821
830
  }
822
831
  return undefined;
@@ -833,23 +842,24 @@ export function extractLocaleFromHeader(request) {
833
842
  * @example
834
843
  * const locale = extractLocaleFromNavigator();
835
844
  *
836
- * @type {() => Locale | undefined}
837
- * @returns {string | undefined}
845
+ * @returns {Locale | undefined}
838
846
  */
839
847
  export function extractLocaleFromNavigator() {
840
848
  if (!navigator?.languages?.length) {
841
849
  return undefined;
842
850
  }
843
851
  const languages = navigator.languages.map((lang) => ({
844
- fullTag: lang.toLowerCase(),
845
- baseTag: lang.split("-")[0]?.toLowerCase(),
852
+ fullTag: lang,
853
+ baseTag: lang.split("-")[0],
846
854
  }));
847
855
  for (const lang of languages) {
848
- if (isLocale(lang.fullTag)) {
849
- return lang.fullTag;
856
+ const fullLocale = toLocale(lang.fullTag);
857
+ if (fullLocale) {
858
+ return fullLocale;
850
859
  }
851
- else if (isLocale(lang.baseTag)) {
852
- return lang.baseTag;
860
+ const baseLocale = toLocale(lang.baseTag);
861
+ if (baseLocale) {
862
+ return baseLocale;
853
863
  }
854
864
  }
855
865
  return undefined;
@@ -868,6 +878,10 @@ let cachedLocale;
868
878
  /**
869
879
  * Extracts the locale from a given URL using native URLPattern.
870
880
  *
881
+ * The built-in default `/:locale/...` routing is case-insensitive because it
882
+ * canonicalizes the first path segment with `toLocale()`. Custom `urlPatterns`
883
+ * keep URLPattern's normal exact matching semantics for path segments.
884
+ *
871
885
  * @param {URL|string} url - The full URL from which to extract the locale.
872
886
  * @returns {Locale|undefined} The extracted locale, or undefined if no locale is found.
873
887
  */
@@ -876,6 +890,7 @@ export function extractLocaleFromUrl(url) {
876
890
  if (cachedUrl === urlString) {
877
891
  return cachedLocale;
878
892
  }
893
+ /** @type {Locale | undefined} */
879
894
  let result;
880
895
  if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
881
896
  result = defaultUrlPatternExtractLocale(url);
@@ -886,11 +901,7 @@ export function extractLocaleFromUrl(url) {
886
901
  for (const element of urlPatterns) {
887
902
  for (const [locale, localizedPattern] of element.localized) {
888
903
  const match = new URLPattern(localizedPattern, urlObj.href).exec(urlObj.href);
889
- if (!match) {
890
- continue;
891
- }
892
- // Check if the locale is valid
893
- if (assertIsLocale(locale)) {
904
+ if (match) {
894
905
  result = locale;
895
906
  break;
896
907
  }
@@ -906,20 +917,13 @@ export function extractLocaleFromUrl(url) {
906
917
  /**
907
918
  * https://github.com/opral/inlang-paraglide-js/issues/381
908
919
  *
909
- * @param {URL|string} url - The full URL from which to extract the locale.
910
- * @returns {Locale|undefined} The extracted locale, or undefined if no locale is found.
920
+ * @param {URL | string} url - The full URL from which to extract the locale.
921
+ * @returns {Locale | undefined} The extracted locale, or undefined if no locale is found.
911
922
  */
912
923
  function defaultUrlPatternExtractLocale(url) {
913
924
  const urlObj = new URL(url, "http://dummy.com");
914
925
  const pathSegments = urlObj.pathname.split("/").filter(Boolean);
915
- if (pathSegments.length > 0) {
916
- const potentialLocale = pathSegments[0];
917
- if (isLocale(potentialLocale)) {
918
- return potentialLocale;
919
- }
920
- }
921
- // everything else has to be the base locale
922
- return baseLocale;
926
+ return toLocale(pathSegments[0]) || baseLocale;
923
927
  }
924
928
 
925
929
  /**
@@ -962,15 +966,17 @@ function defaultUrlPatternExtractLocale(url) {
962
966
  * ```
963
967
  *
964
968
  * @param {string | URL} url - The URL to localize. If string, must be absolute.
965
- * @param {Object} [options] - Options for localization
966
- * @param {string} [options.locale] - Target locale. If not provided, uses getLocale()
969
+ * @param {object} [options] - Options for localization
970
+ * @param {Locale} [options.locale] - Target locale. If not provided, uses getLocale()
967
971
  * @returns {URL} The localized URL, always absolute
968
972
  */
969
973
  export function localizeUrl(url, options) {
974
+ const targetLocale = options?.locale
975
+ ? assertIsLocale(options?.locale)
976
+ : getLocale();
970
977
  if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
971
- return localizeUrlDefaultPattern(url, options);
978
+ return localizeUrlDefaultPattern(url, targetLocale);
972
979
  }
973
- const targetLocale = options?.locale ?? getLocale();
974
980
  const urlObj = typeof url === "string" ? new URL(url) : url;
975
981
  // Iterate over URL patterns
976
982
  for (const element of urlPatterns) {
@@ -1003,13 +1009,11 @@ export function localizeUrl(url, options) {
1003
1009
  * https://github.com/opral/inlang-paraglide-js/issues/381
1004
1010
  *
1005
1011
  * @param {string | URL} url
1006
- * @param {Object} [options]
1007
- * @param {string} [options.locale]
1012
+ * @param {Locale} locale
1008
1013
  * @returns {URL}
1009
1014
  */
1010
- function localizeUrlDefaultPattern(url, options) {
1015
+ function localizeUrlDefaultPattern(url, locale) {
1011
1016
  const urlObj = typeof url === "string" ? new URL(url, getUrlOrigin()) : new URL(url);
1012
- const locale = options?.locale ?? getLocale();
1013
1017
  const currentLocale = extractLocaleFromUrl(urlObj);
1014
1018
  // If current locale matches target locale, no change needed
1015
1019
  if (currentLocale === locale) {
@@ -1017,7 +1021,7 @@ function localizeUrlDefaultPattern(url, options) {
1017
1021
  }
1018
1022
  const pathSegments = urlObj.pathname.split("/").filter(Boolean);
1019
1023
  // If current path starts with a locale, remove it
1020
- if (pathSegments.length > 0 && isLocale(pathSegments[0])) {
1024
+ if (pathSegments.length > 0 && toLocale(pathSegments[0])) {
1021
1025
  pathSegments.shift();
1022
1026
  }
1023
1027
  // For base locale, don't add prefix
@@ -1105,7 +1109,7 @@ function deLocalizeUrlDefaultPattern(url) {
1105
1109
  const urlObj = typeof url === "string" ? new URL(url, getUrlOrigin()) : new URL(url);
1106
1110
  const pathSegments = urlObj.pathname.split("/").filter(Boolean);
1107
1111
  // If first segment is a locale, remove it
1108
- if (pathSegments.length > 0 && isLocale(pathSegments[0])) {
1112
+ if (pathSegments.length > 0 && toLocale(pathSegments[0])) {
1109
1113
  urlObj.pathname = "/" + pathSegments.slice(1).join("/");
1110
1114
  }
1111
1115
  return urlObj;
@@ -1218,7 +1222,8 @@ function fillPattern(pattern, values, origin) {
1218
1222
  * Aggregates named groups from various parts of the URLPattern match result.
1219
1223
  *
1220
1224
  *
1221
- * @type {(match: any) => Record<string, string | null | undefined>}
1225
+ * @param {any} match - The URLPattern match result object.
1226
+ * @returns {Record<string, string | null | undefined>} An object containing all named groups from the match.
1222
1227
  */
1223
1228
  export function aggregateGroups(match) {
1224
1229
  return {
@@ -1237,18 +1242,18 @@ export function aggregateGroups(match) {
1237
1242
  * @typedef {object} ShouldRedirectServerInput
1238
1243
  * @property {Request} request
1239
1244
  * @property {string | URL} [url]
1240
- * @property {ReturnType<typeof assertIsLocale>} [locale]
1245
+ * @property {Locale} [locale]
1241
1246
  *
1242
1247
  * @typedef {object} ShouldRedirectClientInput
1243
1248
  * @property {undefined} [request]
1244
1249
  * @property {string | URL} [url]
1245
- * @property {ReturnType<typeof assertIsLocale>} [locale]
1250
+ * @property {Locale} [locale]
1246
1251
  *
1247
1252
  * @typedef {ShouldRedirectServerInput | ShouldRedirectClientInput} ShouldRedirectInput
1248
1253
  *
1249
1254
  * @typedef {object} ShouldRedirectResult
1250
1255
  * @property {boolean} shouldRedirect - Indicates whether the consumer should perform a redirect.
1251
- * @property {ReturnType<typeof assertIsLocale>} locale - Locale resolved using the configured strategies.
1256
+ * @property {Locale} locale - Locale resolved using the configured strategies.
1252
1257
  * @property {URL | undefined} redirectUrl - Destination URL when a redirect is required.
1253
1258
  */
1254
1259
  /**
@@ -1289,7 +1294,7 @@ export function aggregateGroups(match) {
1289
1294
  */
1290
1295
  export async function shouldRedirect(input = {}) {
1291
1296
  const currentUrl = resolveUrl(input);
1292
- const locale = /** @type {ReturnType<typeof assertIsLocale>} */ (await resolveLocale(input, currentUrl));
1297
+ const locale = await resolveLocale(input, currentUrl);
1293
1298
  const strategy = getStrategyForUrl(currentUrl.href);
1294
1299
  if (isExcludedByRouteStrategy(currentUrl.href) || !strategy.includes("url")) {
1295
1300
  return { shouldRedirect: false, locale, redirectUrl: undefined };
@@ -1307,11 +1312,12 @@ export async function shouldRedirect(input = {}) {
1307
1312
  *
1308
1313
  * @param {ShouldRedirectInput} input
1309
1314
  * @param {URL} currentUrl
1310
- * @returns {Promise<ReturnType<typeof assertIsLocale>>}
1315
+ * @returns {Promise<Locale>}
1311
1316
  */
1312
1317
  async function resolveLocale(input, currentUrl) {
1313
- if (input.locale) {
1314
- return assertIsLocale(input.locale);
1318
+ const locale = toLocale(input.locale);
1319
+ if (locale) {
1320
+ return locale;
1315
1321
  }
1316
1322
  if (input.request) {
1317
1323
  return extractLocaleFromRequestAsync(input.request);
@@ -1390,8 +1396,8 @@ function normalizeUrl(url) {
1390
1396
  * which provides more precise control over URL handling.
1391
1397
  *
1392
1398
  * @param {string} href - The href to localize (can be relative or absolute)
1393
- * @param {Object} [options] - Options for localization
1394
- * @param {string} [options.locale] - Target locale. If not provided, uses `getLocale()`
1399
+ * @param {object} [options] - Options for localization
1400
+ * @param {Locale} [options.locale] - Target locale. If not provided, uses `getLocale()`
1395
1401
  * @returns {string} The localized href, relative if input was relative
1396
1402
  */
1397
1403
  export function localizeHref(href, options) {
@@ -1523,6 +1529,7 @@ export function trackMessageCall(safeModuleId, locale) {
1523
1529
  * The order follows each input URL with all its locale variants before moving to the next URL.
1524
1530
  */
1525
1531
  export function generateStaticLocalizedUrls(urls) {
1532
+ /** @type {Set<URL>} */
1526
1533
  const localizedUrls = new Set();
1527
1534
  // For default URL pattern, we can optimize the generation
1528
1535
  if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
@@ -1615,7 +1622,7 @@ export const customClientStrategies = new Map();
1615
1622
  /**
1616
1623
  * Checks if the given strategy is a custom strategy.
1617
1624
  *
1618
- * @param {any} strategy The name of the custom strategy to validate.
1625
+ * @param {unknown} strategy The name of the custom strategy to validate.
1619
1626
  * Must be a string that starts with "custom-" followed by alphanumeric characters, hyphens, or underscores.
1620
1627
  * @returns {boolean} Returns true if it is a custom strategy, false otherwise.
1621
1628
  */
@@ -1627,7 +1634,7 @@ export function isCustomStrategy(strategy) {
1627
1634
  *
1628
1635
  * @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#write-your-own-strategy
1629
1636
  *
1630
- * @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
1637
+ * @param {string} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
1631
1638
  * @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
1632
1639
  * the method getLocale.
1633
1640
  * @returns {void}
@@ -1643,7 +1650,7 @@ export function defineCustomServerStrategy(strategy, handler) {
1643
1650
  *
1644
1651
  * @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#write-your-own-strategy
1645
1652
  *
1646
- * @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
1653
+ * @param {string} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
1647
1654
  * @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
1648
1655
  * methods getLocale and setLocale.
1649
1656
  * @returns {void}
@@ -1656,26 +1663,25 @@ export function defineCustomClientStrategy(strategy, handler) {
1656
1663
  }
1657
1664
 
1658
1665
  // ------ TYPES ------
1659
-
1666
+ export {};
1660
1667
  /**
1661
1668
  * A locale that is available in the project.
1662
1669
  *
1663
1670
  * @example
1664
1671
  * setLocale(request.locale as Locale)
1665
1672
  *
1666
- * @typedef {(typeof locales)[number]} Locale
1673
+ * @typedef {typeof locales[number]} Locale
1667
1674
  */
1668
-
1669
1675
  /**
1670
1676
  * A branded type representing a localized string.
1671
1677
  *
1672
- * Message functions return this type instead of `string`, enabling TypeScript
1678
+ * Message functions return this type instead of \`string\`, enabling TypeScript
1673
1679
  * to distinguish translated strings from regular strings at compile time.
1674
1680
  * This allows you to enforce that only properly localized content is used
1675
1681
  * in your UI components.
1676
1682
  *
1677
- * Since `LocalizedString` is a branded subtype of `string`, it remains fully
1678
- * backward compatible—you can pass it anywhere a `string` is expected.
1683
+ * Since \`LocalizedString\` is a branded subtype of \`string\`, it remains fully
1684
+ * backward compatible—you can pass it anywhere a \`string\` is expected.
1679
1685
  *
1680
1686
  * @example
1681
1687
  * // Enforce localized strings in your components
@@ -1707,19 +1713,32 @@ export function defineCustomClientStrategy(strategy, handler) {
1707
1713
  *
1708
1714
  * @typedef {string & { readonly __brand: 'LocalizedString' }} LocalizedString
1709
1715
  */
1710
-
1716
+ /**
1717
+ * A single markup option passed to a tag instance.
1718
+ *
1719
+ * @typedef {{
1720
+ * name: string;
1721
+ * value: unknown;
1722
+ * }} MessageMarkupOption
1723
+ */
1724
+ /**
1725
+ * A single static markup attribute attached to a tag instance.
1726
+ *
1727
+ * @typedef {{
1728
+ * name: string;
1729
+ * value: string | true;
1730
+ * }} MessageMarkupAttribute
1731
+ */
1711
1732
  /**
1712
1733
  * Record of markup options for a tag instance.
1713
1734
  *
1714
1735
  * @typedef {Record<string, unknown>} MessageMarkupOptions
1715
1736
  */
1716
-
1717
1737
  /**
1718
1738
  * Record of markup attributes for a tag instance.
1719
1739
  *
1720
1740
  * @typedef {Record<string, string | true>} MessageMarkupAttributes
1721
1741
  */
1722
-
1723
1742
  /**
1724
1743
  * Type-level schema for a single markup tag.
1725
1744
  *
@@ -1729,19 +1748,17 @@ export function defineCustomClientStrategy(strategy, handler) {
1729
1748
  * children: boolean;
1730
1749
  * }} MessageMarkupTag
1731
1750
  */
1732
-
1733
1751
  /**
1734
1752
  * Type-level schema for all markup tags in a message.
1735
1753
  *
1736
1754
  * @typedef {Record<string, MessageMarkupTag>} MessageMarkupSchema
1737
1755
  */
1738
-
1739
1756
  /**
1740
1757
  * Type-only metadata attached to compiled message functions.
1741
1758
  *
1742
1759
  * @template Inputs
1743
1760
  * @template Options
1744
- * @template {MessageMarkupSchema} Markup
1761
+ * @template {MessageMarkupSchema} [Markup = MessageMarkupSchema]
1745
1762
  * @typedef {{
1746
1763
  * readonly __paraglide?: {
1747
1764
  * inputs: Inputs;
@@ -1750,7 +1767,6 @@ export function defineCustomClientStrategy(strategy, handler) {
1750
1767
  * };
1751
1768
  * }} MessageMetadata
1752
1769
  */
1753
-
1754
1770
  /**
1755
1771
  * A compiled, framework-neutral message part.
1756
1772
  *
@@ -1774,4 +1790,23 @@ export function defineCustomClientStrategy(strategy, handler) {
1774
1790
  * attributes: MessageMarkupAttributes;
1775
1791
  * }} MessagePart
1776
1792
  */
1777
-
1793
+ /**
1794
+ * A message function is a message for a specific locale.
1795
+ *
1796
+ * @example
1797
+ * m.hello({ name: 'world' })
1798
+ *
1799
+ * @typedef {(inputs?: Record<string, never>) => LocalizedString} MessageFunction
1800
+ */
1801
+ /**
1802
+ * A message bundle function that selects the message to be returned.
1803
+ *
1804
+ * Uses `getLocale()` under the hood to determine the locale with an option.
1805
+ *
1806
+ * @template {string} T
1807
+ *
1808
+ * @example
1809
+ * * m.hello({ name: 'world' }, { locale: "en" })
1810
+ *
1811
+ * @typedef {(params: Record<string, never>, options: { locale: T }) => LocalizedString} MessageBundleFunction
1812
+ */