intor 2.3.14 → 2.3.16

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 (195) hide show
  1. package/dist/core/export/index.js +1 -1
  2. package/dist/core/src/config/resolvers/resolve-fallback-locales.js +1 -1
  3. package/dist/core/src/core/messages/utils/is-valid-messages.js +23 -15
  4. package/dist/core/src/routing/pathname/{get-unprefixed-pathname.js → canonicalize-pathname.js} +3 -3
  5. package/dist/core/src/routing/pathname/localize-pathname.js +15 -15
  6. package/dist/{next/src/routing/pathname/locale-prefix-pathname.js → core/src/routing/pathname/materialize-pathname.js} +6 -5
  7. package/dist/core/src/routing/pathname/standardize-pathname.js +9 -9
  8. package/dist/core/src/server/helpers/get-translator.js +2 -2
  9. package/dist/core/src/server/helpers/local-messages-from-url.js +1 -1
  10. package/dist/core/src/server/intor/intor.js +2 -2
  11. package/dist/core/src/server/messages/load-local-messages/load-local-messages.js +2 -2
  12. package/dist/core/src/server/messages/load-local-messages/read-locale-messages/collect-file-entries/collect-file-entries.js +6 -5
  13. package/dist/core/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/parse-file-entries.js +14 -5
  14. package/dist/core/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +3 -3
  15. package/dist/core/src/server/messages/load-messages.js +4 -4
  16. package/dist/core/src/server/runtime/create-intor-runtime.js +12 -7
  17. package/dist/express/src/adapters/express/helpers/get-translator.js +7 -3
  18. package/dist/express/src/adapters/express/middleware/create-intor.js +6 -7
  19. package/dist/express/src/core/messages/utils/is-valid-messages.js +23 -15
  20. package/dist/express/src/routing/inbound/resolve-inbound.js +5 -5
  21. package/dist/express/src/routing/inbound/resolve-locale/resolve-locale.js +13 -6
  22. package/dist/express/src/routing/inbound/resolve-pathname/resolve-pathname.js +1 -1
  23. package/dist/express/src/routing/locale/get-locale-from-accept-language.js +7 -16
  24. package/dist/express/src/routing/locale/get-locale-from-host.js +13 -14
  25. package/dist/express/src/routing/locale/get-locale-from-pathname.js +4 -13
  26. package/dist/express/src/routing/locale/get-locale-from-query.js +10 -16
  27. package/dist/{next/src/routing/pathname/get-unprefixed-pathname.js → express/src/routing/pathname/canonicalize-pathname.js} +3 -3
  28. package/dist/express/src/routing/pathname/localize-pathname.js +15 -15
  29. package/dist/{core/src/routing/pathname/locale-prefix-pathname.js → express/src/routing/pathname/materialize-pathname.js} +6 -5
  30. package/dist/express/src/routing/pathname/standardize-pathname.js +9 -9
  31. package/dist/express/src/server/helpers/get-translator.js +2 -2
  32. package/dist/express/src/server/messages/load-local-messages/load-local-messages.js +2 -2
  33. package/dist/express/src/server/messages/load-local-messages/read-locale-messages/collect-file-entries/collect-file-entries.js +6 -5
  34. package/dist/express/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/parse-file-entries.js +14 -5
  35. package/dist/express/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +3 -3
  36. package/dist/express/src/server/messages/load-messages.js +4 -4
  37. package/dist/express/src/server/runtime/create-intor-runtime.js +12 -7
  38. package/dist/next/src/adapters/next/navigation/use-pathname.js +4 -4
  39. package/dist/next/src/adapters/next/proxy/intor-proxy.js +2 -2
  40. package/dist/next/src/adapters/next/server/get-pathname.js +4 -4
  41. package/dist/next/src/adapters/next/server/get-translator.js +7 -3
  42. package/dist/next/src/adapters/next/server/intor.js +3 -3
  43. package/dist/next/src/core/messages/utils/is-valid-messages.js +23 -15
  44. package/dist/next/src/routing/inbound/resolve-inbound.js +5 -5
  45. package/dist/next/src/routing/inbound/resolve-locale/resolve-locale.js +13 -6
  46. package/dist/next/src/routing/inbound/resolve-pathname/resolve-pathname.js +1 -1
  47. package/dist/next/src/routing/locale/get-locale-from-accept-language.js +7 -16
  48. package/dist/next/src/routing/locale/get-locale-from-host.js +13 -14
  49. package/dist/next/src/routing/locale/get-locale-from-pathname.js +4 -13
  50. package/dist/next/src/routing/locale/get-locale-from-query.js +10 -16
  51. package/dist/next/src/routing/navigation/derive-target.js +5 -5
  52. package/dist/next/src/routing/navigation/utils/derive-host-destination.js +1 -1
  53. package/dist/next/src/routing/navigation/utils/derive-query-destination.js +1 -1
  54. package/dist/{express/src/routing/pathname/get-unprefixed-pathname.js → next/src/routing/pathname/canonicalize-pathname.js} +3 -3
  55. package/dist/next/src/routing/pathname/localize-pathname.js +15 -15
  56. package/dist/{react/src/routing/pathname/locale-prefix-pathname.js → next/src/routing/pathname/materialize-pathname.js} +6 -5
  57. package/dist/next/src/routing/pathname/standardize-pathname.js +9 -9
  58. package/dist/next/src/server/helpers/get-translator.js +2 -2
  59. package/dist/next/src/server/intor/intor.js +2 -2
  60. package/dist/next/src/server/messages/load-local-messages/load-local-messages.js +2 -2
  61. package/dist/next/src/server/messages/load-local-messages/read-locale-messages/collect-file-entries/collect-file-entries.js +6 -5
  62. package/dist/next/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/parse-file-entries.js +14 -5
  63. package/dist/next/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +3 -3
  64. package/dist/next/src/server/messages/load-messages.js +4 -4
  65. package/dist/next/src/server/runtime/create-intor-runtime.js +12 -7
  66. package/dist/react/export/react/index.js +1 -1
  67. package/dist/react/src/client/react/navigation/use-execute-navigation.js +3 -3
  68. package/dist/react/src/client/react/provider/effects/use-locale-effects.js +5 -5
  69. package/dist/react/src/client/react/render/create-react-renderer.js +5 -0
  70. package/dist/react/src/client/react/translator/create-t-rich.js +2 -2
  71. package/dist/react/src/client/react/translator/{t.js → trans.js} +4 -4
  72. package/dist/react/src/client/react/translator/use-translator.js +0 -4
  73. package/dist/react/src/client/shared/helpers/get-client-locale.js +2 -2
  74. package/dist/react/src/client/shared/utils/locale/detect-browser-locale.js +1 -1
  75. package/dist/{vue/src/client/shared/utils/locale/get-locale-cookie-browser.js → react/src/client/shared/utils/locale/get-locale-from-cookie.js} +3 -3
  76. package/dist/react/src/client/shared/utils/locale/set-document-locale.js +2 -2
  77. package/dist/{svelte/src/client/shared/utils/locale/set-locale-cookie-browser.js → react/src/client/shared/utils/locale/set-locale-cookie.js} +4 -4
  78. package/dist/react/src/core/messages/utils/is-valid-messages.js +23 -15
  79. package/dist/react/src/routing/navigation/derive-target.js +5 -5
  80. package/dist/react/src/routing/navigation/utils/derive-host-destination.js +1 -1
  81. package/dist/react/src/routing/navigation/utils/derive-query-destination.js +1 -1
  82. package/dist/react/src/routing/pathname/{get-unprefixed-pathname.js → canonicalize-pathname.js} +3 -3
  83. package/dist/react/src/routing/pathname/localize-pathname.js +15 -15
  84. package/dist/{express/src/routing/pathname/locale-prefix-pathname.js → react/src/routing/pathname/materialize-pathname.js} +6 -5
  85. package/dist/react/src/routing/pathname/standardize-pathname.js +9 -9
  86. package/dist/svelte/src/client/shared/helpers/get-client-locale.js +2 -2
  87. package/dist/svelte/src/client/shared/utils/locale/detect-browser-locale.js +1 -1
  88. package/dist/{react/src/client/shared/utils/locale/get-locale-cookie-browser.js → svelte/src/client/shared/utils/locale/get-locale-from-cookie.js} +3 -3
  89. package/dist/svelte/src/client/shared/utils/locale/set-document-locale.js +2 -2
  90. package/dist/{vue/src/client/shared/utils/locale/set-locale-cookie-browser.js → svelte/src/client/shared/utils/locale/set-locale-cookie.js} +4 -4
  91. package/dist/svelte/src/client/svelte/render/create-svelte-renderer.js +12 -0
  92. package/dist/svelte/src/client/svelte/runtime/create-intor.js +2 -12
  93. package/dist/svelte/src/client/svelte/runtime/effects/locale-effects.js +5 -5
  94. package/dist/svelte/src/core/messages/utils/is-valid-messages.js +23 -15
  95. package/dist/types/export/index.d.ts +2 -2
  96. package/dist/types/export/internal/index.d.ts +2 -1
  97. package/dist/types/export/react/index.d.ts +1 -1
  98. package/dist/types/export/server/index.d.ts +1 -1
  99. package/dist/types/export/vue/index.d.ts +1 -1
  100. package/dist/types/src/adapters/express/global.d.ts +4 -4
  101. package/dist/types/src/adapters/express/helpers/get-translator.d.ts +6 -6
  102. package/dist/types/src/adapters/express/middleware/create-intor.d.ts +1 -1
  103. package/dist/types/src/adapters/next/navigation/use-pathname.d.ts +3 -3
  104. package/dist/types/src/adapters/next/proxy/intor-proxy.d.ts +1 -1
  105. package/dist/types/src/adapters/next/server/get-pathname.d.ts +3 -3
  106. package/dist/types/src/adapters/next/server/get-translator.d.ts +6 -6
  107. package/dist/types/src/adapters/next/server/intor.d.ts +4 -4
  108. package/dist/types/src/client/react/helpers/use-runtime-state.d.ts +5 -3
  109. package/dist/types/src/client/react/index.d.ts +1 -1
  110. package/dist/types/src/client/react/provider/types.d.ts +10 -9
  111. package/dist/types/src/client/react/render/create-react-renderer.d.ts +1 -0
  112. package/dist/types/src/client/react/render/render-rich-message-react.d.ts +2 -1
  113. package/dist/types/src/client/react/render/types.d.ts +15 -10
  114. package/dist/types/src/client/react/translator/index.d.ts +1 -1
  115. package/dist/types/src/client/react/translator/trans.d.ts +27 -0
  116. package/dist/types/src/client/react/translator/translator-instance.d.ts +4 -4
  117. package/dist/types/src/client/react/translator/use-translator.d.ts +4 -4
  118. package/dist/types/src/client/shared/utils/index.d.ts +1 -1
  119. package/dist/types/src/client/shared/utils/locale/detect-browser-locale.d.ts +1 -1
  120. package/dist/types/src/client/shared/utils/locale/get-locale-from-cookie.d.ts +6 -0
  121. package/dist/types/src/client/shared/utils/locale/index.d.ts +2 -2
  122. package/dist/types/src/client/shared/utils/locale/set-document-locale.d.ts +2 -2
  123. package/dist/types/src/client/shared/utils/locale/set-locale-cookie.d.ts +7 -0
  124. package/dist/types/src/client/svelte/helpers/create-runtime-state.d.ts +7 -6
  125. package/dist/types/src/client/svelte/render/render-rich-message-svelte.d.ts +2 -1
  126. package/dist/types/src/client/svelte/runtime/create-intor.d.ts +1 -2
  127. package/dist/types/src/client/svelte/runtime/types.d.ts +19 -21
  128. package/dist/types/src/client/vue/helpers/use-runtime-state.d.ts +7 -6
  129. package/dist/types/src/client/vue/index.d.ts +1 -1
  130. package/dist/types/src/client/vue/provider/inject.d.ts +1 -2
  131. package/dist/types/src/client/vue/provider/resolver/resolve-runtime.d.ts +2 -2
  132. package/dist/types/src/client/vue/provider/types.d.ts +11 -11
  133. package/dist/types/src/client/vue/render/create-vue-renderer.d.ts +1 -0
  134. package/dist/types/src/client/vue/render/render-rich-message-vue.d.ts +2 -1
  135. package/dist/types/src/client/vue/render/types.d.ts +14 -6
  136. package/dist/types/src/client/vue/translator/index.d.ts +1 -1
  137. package/dist/types/src/client/vue/translator/{t.d.ts → trans.d.ts} +4 -5
  138. package/dist/types/src/client/vue/translator/translator-instance.d.ts +4 -4
  139. package/dist/types/src/client/vue/translator/use-translator.d.ts +5 -5
  140. package/dist/types/src/core/index.d.ts +2 -2
  141. package/dist/types/src/core/messages/index.d.ts +1 -1
  142. package/dist/types/src/core/messages/types.d.ts +14 -36
  143. package/dist/types/src/core/messages/utils/is-valid-messages.d.ts +5 -10
  144. package/dist/types/src/core/types/generated.d.ts +14 -5
  145. package/dist/types/src/core/types/index.d.ts +2 -3
  146. package/dist/types/src/core/types/translator-instance.d.ts +5 -19
  147. package/dist/types/src/routing/inbound/resolve-locale/resolve-locale.d.ts +4 -3
  148. package/dist/types/src/routing/locale/get-locale-from-accept-language.d.ts +5 -7
  149. package/dist/types/src/routing/locale/get-locale-from-host.d.ts +12 -8
  150. package/dist/types/src/routing/locale/get-locale-from-pathname.d.ts +4 -13
  151. package/dist/types/src/routing/locale/get-locale-from-query.d.ts +9 -10
  152. package/dist/types/src/routing/navigation/utils/derive-host-destination.d.ts +1 -1
  153. package/dist/types/src/routing/navigation/utils/derive-query-destination.d.ts +1 -1
  154. package/dist/types/src/routing/pathname/{get-unprefixed-pathname.d.ts → canonicalize-pathname.d.ts} +2 -2
  155. package/dist/types/src/routing/pathname/localize-pathname.d.ts +16 -13
  156. package/dist/types/src/routing/pathname/materialize-pathname.d.ts +17 -0
  157. package/dist/types/src/routing/pathname/standardize-pathname.d.ts +7 -6
  158. package/dist/types/src/server/helpers/get-translator.d.ts +6 -7
  159. package/dist/types/src/server/index.d.ts +1 -1
  160. package/dist/types/src/server/intor/index.d.ts +1 -1
  161. package/dist/types/src/server/intor/intor.d.ts +4 -4
  162. package/dist/types/src/server/intor/types.d.ts +5 -3
  163. package/dist/types/src/server/messages/load-local-messages/load-local-messages.d.ts +1 -1
  164. package/dist/types/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/parse-file-entries.d.ts +2 -2
  165. package/dist/types/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/types.d.ts +4 -3
  166. package/dist/types/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/utils/json-reader.d.ts +2 -2
  167. package/dist/types/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/utils/nest-object-from-path.d.ts +2 -2
  168. package/dist/types/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.d.ts +1 -1
  169. package/dist/types/src/server/messages/load-local-messages/read-locale-messages/types.d.ts +2 -2
  170. package/dist/types/src/server/messages/load-local-messages/types.d.ts +2 -2
  171. package/dist/types/src/server/messages/load-messages.d.ts +1 -1
  172. package/dist/types/src/server/messages/types.d.ts +2 -2
  173. package/dist/types/src/server/runtime/create-intor-runtime.d.ts +1 -2
  174. package/dist/types/src/server/runtime/types.d.ts +7 -13
  175. package/dist/types/src/server/translator/create-translator.d.ts +1 -1
  176. package/dist/types/src/server/translator/translator-instance.d.ts +3 -3
  177. package/dist/vue/export/vue/index.js +1 -1
  178. package/dist/vue/src/client/shared/helpers/get-client-locale.js +2 -2
  179. package/dist/vue/src/client/shared/utils/locale/detect-browser-locale.js +1 -1
  180. package/dist/{svelte/src/client/shared/utils/locale/get-locale-cookie-browser.js → vue/src/client/shared/utils/locale/get-locale-from-cookie.js} +3 -3
  181. package/dist/vue/src/client/shared/utils/locale/set-document-locale.js +2 -2
  182. package/dist/{react/src/client/shared/utils/locale/set-locale-cookie-browser.js → vue/src/client/shared/utils/locale/set-locale-cookie.js} +4 -4
  183. package/dist/vue/src/client/vue/provider/effects/use-locale-effects.js +5 -5
  184. package/dist/vue/src/client/vue/render/create-vue-renderer.js +5 -0
  185. package/dist/vue/src/client/vue/translator/{t.js → trans.js} +3 -3
  186. package/dist/vue/src/client/vue/translator/use-translator.js +1 -1
  187. package/dist/vue/src/core/messages/utils/is-valid-messages.js +23 -15
  188. package/package.json +2 -2
  189. package/dist/types/src/client/react/translator/t.d.ts +0 -27
  190. package/dist/types/src/client/shared/types/index.d.ts +0 -1
  191. package/dist/types/src/client/shared/types/runtime-state.d.ts +0 -13
  192. package/dist/types/src/client/shared/utils/locale/get-locale-cookie-browser.d.ts +0 -6
  193. package/dist/types/src/client/shared/utils/locale/set-locale-cookie-browser.d.ts +0 -7
  194. package/dist/types/src/core/types/bootstrap.d.ts +0 -13
  195. package/dist/types/src/routing/pathname/locale-prefix-pathname.d.ts +0 -16
@@ -10,4 +10,4 @@ export { mergeMessages } from '../src/core/messages/merge-messages.js';
10
10
  export { defineIntorConfig } from '../src/config/define-intor-config.js';
11
11
  import '../src/config/constants/cookie.js';
12
12
  export { localizePathname } from '../src/routing/pathname/localize-pathname.js';
13
- export { Translator } from 'intor-translator';
13
+ export { Translator, tokenize } from 'intor-translator';
@@ -12,7 +12,7 @@ const resolveFallbackLocales = (config, id, supportedSet) => {
12
12
  if (!fallbackLocales || typeof fallbackLocales !== "object") {
13
13
  return {};
14
14
  }
15
- const logger = getLogger({ id }).child({
15
+ const logger = getLogger({ ...config.logger, id }).child({
16
16
  scope: "resolve-fallback-locales",
17
17
  });
18
18
  const resolvedMap = {};
@@ -3,15 +3,10 @@ function isPlainObject(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value);
4
4
  }
5
5
  /**
6
- * Check if a value is a valid **Messages** object.
6
+ * Check if a value is a valid MessageObject.
7
7
  *
8
- * - Uses an iterative approach to avoid stack overflow with deeply nested objects.
9
- *
10
- * @example
11
- * ```ts
12
- * isValidMessages({ en: { hello: "Hello" } }) // true
13
- * isValidMessages({ en: { count: 5 } }) // false
14
- * ```
8
+ * - Supports all MessageValue variants (primitive, array, object).
9
+ * - Uses an iterative approach to avoid stack overflow.
15
10
  */
16
11
  function isValidMessages(value) {
17
12
  if (!isPlainObject(value))
@@ -19,16 +14,29 @@ function isValidMessages(value) {
19
14
  const stack = [value];
20
15
  while (stack.length > 0) {
21
16
  const current = stack.pop();
22
- for (const v of Object.values(current)) {
23
- if (typeof v === "string")
24
- continue;
25
- if (isPlainObject(v)) {
26
- stack.push(v);
17
+ // primitives are always valid
18
+ if (current === null ||
19
+ typeof current === "string" ||
20
+ typeof current === "number" ||
21
+ typeof current === "boolean") {
22
+ continue;
23
+ }
24
+ // array → validate each item
25
+ if (Array.isArray(current)) {
26
+ for (const item of current) {
27
+ stack.push(item);
27
28
  }
28
- else {
29
- return false;
29
+ continue;
30
+ }
31
+ // object → validate each value
32
+ if (isPlainObject(current)) {
33
+ for (const v of Object.values(current)) {
34
+ stack.push(v);
30
35
  }
36
+ continue;
31
37
  }
38
+ // everything else is invalid
39
+ return false;
32
40
  }
33
41
  return true;
34
42
  }
@@ -10,11 +10,11 @@ import 'logry';
10
10
  * // config.supportedLocales: ["en-US"]
11
11
  * // config.routing.basePath: "/app"
12
12
  * // config.routing.prefix: "all"
13
- * getUnprefixedPathname(config, "/app/en-US/about" );
13
+ * canonicalizePathname("/app/en-US/about", config);
14
14
  * // => "/about"
15
15
  *```
16
16
  */
17
- function getUnprefixedPathname(config, rawPathname) {
17
+ function canonicalizePathname(rawPathname, config) {
18
18
  const { routing, supportedLocales } = config;
19
19
  const { basePath } = routing;
20
20
  // 1. Normalize pathname
@@ -38,4 +38,4 @@ function getUnprefixedPathname(config, rawPathname) {
38
38
  : prefixedPathname;
39
39
  }
40
40
 
41
- export { getUnprefixedPathname };
41
+ export { canonicalizePathname };
@@ -1,5 +1,5 @@
1
- import { getUnprefixedPathname } from './get-unprefixed-pathname.js';
2
- import { localePrefixPathname } from './locale-prefix-pathname.js';
1
+ import { canonicalizePathname } from './canonicalize-pathname.js';
2
+ import { materializePathname } from './materialize-pathname.js';
3
3
  import { standardizePathname } from './standardize-pathname.js';
4
4
 
5
5
  /**
@@ -11,25 +11,25 @@ import { standardizePathname } from './standardize-pathname.js';
11
11
  * // config.supportedLocales: ["en-US"]
12
12
  * // config.routing.basePath: "/app"
13
13
  * // config.routing.prefix: "all"
14
- * localePrefixPathname({ config, pathname: "/app/en-US/about", locale: "en-US" });
14
+ * localizePathname(config, "/app/en-US/about", "en-US");
15
15
  * // => {
16
+ * // pathname: '/app/en-US/about'
16
17
  * // unprefixedPathname: '/about',
17
- * // standardizedPathname: '/app/{locale}/about',
18
- * // localizedPathname: '/app/en-US/about'
18
+ * // templatedPathname: '/app/{locale}/about',
19
19
  * // }
20
20
  * ```
21
21
  */
22
- const localizePathname = (config, rawPathname, locale) => {
23
- // 1. Canonicalize: extract basePath and strip locale
24
- const unprefixedPathname = getUnprefixedPathname(config, rawPathname);
25
- // 2. Standardize: build a pathname with locale placeholder
26
- const standardizedPathname = standardizePathname(config, unprefixedPathname);
27
- // 3. Apply strategy: resolve locale prefix based on routing rules
28
- const pathname = localePrefixPathname(config, standardizedPathname, locale);
22
+ const localizePathname = (rawPathname, config, locale) => {
23
+ // 1. Canonicalize: normalize and remove routing-specific prefixes
24
+ const canonicalized = canonicalizePathname(rawPathname, config);
25
+ // 2. Standardize: convert to internal pathname shape with locale placeholder
26
+ const standardized = standardizePathname(canonicalized, config);
27
+ // 3. Materialize: apply routing rules to produce the final pathname
28
+ const materialized = materializePathname(standardized, config, locale);
29
29
  return {
30
- pathname,
31
- unprefixedPathname,
32
- standardizedPathname,
30
+ pathname: materialized,
31
+ unprefixedPathname: canonicalized,
32
+ templatedPathname: standardized,
33
33
  };
34
34
  };
35
35
 
@@ -4,20 +4,21 @@ import { normalizePathname } from '../../core/utils/normalizers/normalize-pathna
4
4
  import 'logry';
5
5
 
6
6
  /**
7
- * Applies the configured locale prefix behavior to a standardized pathname.
7
+ * Materializes a standardized pathname by applying
8
+ * the configured locale prefix behavior.
8
9
  *
9
10
  * @example
10
11
  * ```ts
11
12
  * // config.routing.localePrefix: "all"
12
- * localePrefixPathname({ config, pathname: "/app/{locale}/about", locale: "en-US" });
13
+ * materializePathname("/app/{locale}/about", config, "en-US");
13
14
  * // => /app/en-US/about
14
15
  *
15
16
  * // config.routing.localePrefix: "none"
16
- * localePrefixPathname({ config, pathname: "/app/{locale}/about", locale: "en-US" });
17
+ * materializePathname("/app/{locale}/about", config, "en-US");
17
18
  * // => /app/about
18
19
  * ```
19
20
  */
20
- const localePrefixPathname = (config, standardizedPathname, locale) => {
21
+ const materializePathname = (standardizedPathname, config, locale) => {
21
22
  const { localePrefix } = config.routing;
22
23
  if (localePrefix !== "none" && !locale) {
23
24
  throw new Error('No locale when using localePrefix "all", "except-default"');
@@ -36,4 +37,4 @@ const localePrefixPathname = (config, standardizedPathname, locale) => {
36
37
  return normalizePathname(standardizedPathname.replaceAll(`/${PREFIX_PLACEHOLDER}`, ""));
37
38
  };
38
39
 
39
- export { localePrefixPathname };
40
+ export { materializePathname };
@@ -4,24 +4,24 @@ import { normalizePathname } from '../../core/utils/normalizers/normalize-pathna
4
4
  import 'logry';
5
5
 
6
6
  /**
7
- * Standardizes a canonical pathname by applying the base path
8
- * and injecting the locale placeholder.
7
+ * Standardizes a canonical pathname into an internal routing template
8
+ * by applying the base path and injecting a locale placeholder.
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * // routing.basePath: "/app",
13
- * standardizePathname({ config, pathname: "/cms" });
14
- * // => "/app/{locale}/cms"
12
+ * // config.routing.basePath: "/app",
13
+ * // config.routing.prefix: "all"
14
+ * standardizePathname("/about", config);
15
+ * // => "/app/{locale}/about"
15
16
  * ```
16
17
  */
17
- const standardizePathname = (config, unprefixedPathname) => {
18
- const { routing } = config;
19
- const { basePath } = routing;
18
+ const standardizePathname = (canonicalizedPathname, config) => {
19
+ const { basePath } = config.routing;
20
20
  // Normalize each segment before join to avoid redundant slashes
21
21
  const parts = [
22
22
  normalizePathname(basePath),
23
23
  PREFIX_PLACEHOLDER,
24
- normalizePathname(unprefixedPathname),
24
+ normalizePathname(canonicalizedPathname),
25
25
  ];
26
26
  // Avoid double slashes between segments
27
27
  const standardizedPathname = parts.join("/").replaceAll(/\/{2,}/g, "/");
@@ -2,11 +2,11 @@ import { createIntorRuntime } from '../runtime/create-intor-runtime.js';
2
2
 
3
3
  // Implementation
4
4
  async function getTranslator(config, params) {
5
- const { readOptions, allowCacheWrite, preKey, handlers, plugins } = params;
5
+ const { readers, allowCacheWrite, preKey, handlers, plugins } = params;
6
6
  const locale = params.locale;
7
7
  // Create runtime (request-scoped, no cache write)
8
8
  const runtime = createIntorRuntime(config, {
9
- readOptions,
9
+ readers,
10
10
  allowCacheWrite,
11
11
  });
12
12
  // Ensure messages & create translator snapshot
@@ -43,7 +43,7 @@ async function loadMessagesFromUrl(url, options) {
43
43
  namespaces,
44
44
  fallbackLocales,
45
45
  concurrency: options?.concurrency,
46
- readOptions: options?.readOptions,
46
+ readers: options?.readers,
47
47
  pool: options?.pool,
48
48
  allowCacheWrite: options?.allowCacheWrite,
49
49
  loggerOptions: options?.loggerOptions || { id: "default" },
@@ -5,8 +5,8 @@ import { createIntorRuntime } from '../runtime/create-intor-runtime.js';
5
5
  /**
6
6
  * Initializes Intor for the current execution context.
7
7
  *
8
- * Provides server-side bootstrap logic for SSR-based
9
- * full-stack frameworks.
8
+ * Produces a server-side snapshot for SSR and
9
+ * full-stack rendering environments.
10
10
  */
11
11
  async function intor(config, localeOrResolver, options) {
12
12
  const baseLogger = getLogger(config.logger);
@@ -21,7 +21,7 @@ import { readLocaleMessages } from './read-locale-messages/read-locale-messages.
21
21
  *
22
22
  * File traversal, parsing, and validation are delegated to lower-level utilities.
23
23
  */
24
- const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, rootDir = "messages", concurrency = 10, readOptions, pool = getGlobalMessagesPool(), allowCacheWrite = false, loggerOptions, }) => {
24
+ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, rootDir = "messages", concurrency = 10, readers, pool = getGlobalMessagesPool(), allowCacheWrite = false, loggerOptions, }) => {
25
25
  const baseLogger = getLogger(loggerOptions);
26
26
  const logger = baseLogger.child({ scope: "load-local-messages" });
27
27
  const start = performance.now();
@@ -65,7 +65,7 @@ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, root
65
65
  namespaces,
66
66
  rootDir,
67
67
  limit,
68
- readOptions,
68
+ readers,
69
69
  loggerOptions,
70
70
  });
71
71
  // Stop at the first locale that yields non-empty messages
@@ -20,9 +20,10 @@ import { getLogger } from '../../../../../core/logger/get-logger.js';
20
20
  * }, ... ];
21
21
  * ```
22
22
  */
23
- async function collectFileEntries({ readdir = fs.readdir, namespaces, rootDir, limit, exts = [".json"], loggerOptions, }) {
23
+ async function collectFileEntries({ readdir = fs.readdir, namespaces, rootDir, limit, exts = ["json"], loggerOptions, }) {
24
24
  const baseLogger = getLogger(loggerOptions);
25
25
  const logger = baseLogger.child({ scope: "collect-file-entries" });
26
+ const supportedExts = new Set(["json", ...exts]);
26
27
  const fileEntries = [];
27
28
  // Recursive directory walk
28
29
  const walk = async (currentDir) => {
@@ -50,14 +51,14 @@ async function collectFileEntries({ readdir = fs.readdir, namespaces, rootDir, l
50
51
  return;
51
52
  }
52
53
  // Skip unsupported file extensions
53
- if (!exts.some((ext) => entry.name.endsWith(ext)))
54
+ const ext = path.extname(entry.name).slice(1); // "json", "yaml"
55
+ if (!ext || !supportedExts.has(ext))
54
56
  return;
55
57
  // ---------------------------------------------------------------------
56
58
  // Resolve file entry
57
59
  // ---------------------------------------------------------------------
58
60
  const relativePath = path.relative(rootDir, fullPath);
59
- const ext = path.extname(relativePath);
60
- const withoutExt = relativePath.slice(0, -ext.length);
61
+ const withoutExt = relativePath.slice(0, relativePath.length - (ext.length + 1));
61
62
  const segments = withoutExt.split(path.sep).filter(Boolean);
62
63
  const namespace = segments.at(0);
63
64
  if (!namespace)
@@ -72,7 +73,7 @@ async function collectFileEntries({ readdir = fs.readdir, namespaces, rootDir, l
72
73
  fullPath,
73
74
  relativePath,
74
75
  segments,
75
- basename: path.basename(entry.name, ext),
76
+ basename: path.basename(entry.name, `.${ext}`),
76
77
  });
77
78
  }));
78
79
  await Promise.all(tasks);
@@ -35,7 +35,7 @@ import { nestObjectFromPath } from './utils/nest-object-from-path.js';
35
35
  * }
36
36
  * ```
37
37
  */
38
- async function parseFileEntries({ fileEntries, limit, messagesReader, loggerOptions, }) {
38
+ async function parseFileEntries({ fileEntries, limit, readers, loggerOptions, }) {
39
39
  const baseLogger = getLogger(loggerOptions);
40
40
  const logger = baseLogger.child({ scope: "parse-file-entries" });
41
41
  // Read and parse all file entries
@@ -45,10 +45,19 @@ async function parseFileEntries({ fileEntries, limit, messagesReader, loggerOpti
45
45
  // -------------------------------------------------------------------
46
46
  // Read and validate file content
47
47
  // -------------------------------------------------------------------
48
- const ext = path.extname(fullPath);
49
- const raw = ext !== ".json" && messagesReader
50
- ? await messagesReader(fullPath)
51
- : await jsonReader(fullPath);
48
+ const ext = path.extname(fullPath).slice(1); // remove dot
49
+ let raw;
50
+ if (ext === "json") {
51
+ raw = await jsonReader(fullPath);
52
+ }
53
+ else {
54
+ const reader = readers?.[ext];
55
+ if (!reader) {
56
+ throw new Error(`No message reader registered for .${ext} files. ` +
57
+ `Please register a reader for the "${ext}" extension.`);
58
+ }
59
+ raw = await reader(fullPath);
60
+ }
52
61
  // Validate messages structure
53
62
  if (!isValidMessages(raw)) {
54
63
  throw new Error("Parsed content does not match expected Messages structure");
@@ -12,7 +12,7 @@ import { parseFileEntries } from './parse-file-entries/parse-file-entries.js';
12
12
  *
13
13
  * It does not perform validation or transformation itself.
14
14
  */
15
- const readLocaleMessages = async ({ locale, namespaces, rootDir = "messages", limit, readOptions: { exts, messagesReader } = {}, loggerOptions, }) => {
15
+ const readLocaleMessages = async ({ locale, namespaces, rootDir = "messages", limit, readers, loggerOptions, }) => {
16
16
  // ---------------------------------------------------------------------------
17
17
  // Collect message file entries for the locale
18
18
  // ---------------------------------------------------------------------------
@@ -20,7 +20,7 @@ const readLocaleMessages = async ({ locale, namespaces, rootDir = "messages", li
20
20
  namespaces,
21
21
  rootDir: path.resolve(rootDir, locale),
22
22
  limit,
23
- exts,
23
+ exts: Object.keys(readers || {}),
24
24
  loggerOptions,
25
25
  });
26
26
  // ---------------------------------------------------------------------------
@@ -29,7 +29,7 @@ const readLocaleMessages = async ({ locale, namespaces, rootDir = "messages", li
29
29
  const messages = await parseFileEntries({
30
30
  fileEntries,
31
31
  limit,
32
- messagesReader,
32
+ readers,
33
33
  loggerOptions,
34
34
  });
35
35
  // ---------------------------------------------------------------------------
@@ -17,7 +17,7 @@ import { loadLocalMessages } from './load-local-messages/load-local-messages.js'
17
17
  * Message traversal, parsing, fallback resolution, and caching logic
18
18
  * are delegated to the selected loader.
19
19
  */
20
- const loadMessages = async ({ config, locale, readOptions, allowCacheWrite = false, }) => {
20
+ const loadMessages = async ({ config, locale, readers, allowCacheWrite = false, }) => {
21
21
  const baseLogger = getLogger(config.logger);
22
22
  const logger = baseLogger.child({ scope: "load-messages" });
23
23
  // ---------------------------------------------------------------------------
@@ -35,8 +35,8 @@ const loadMessages = async ({ config, locale, readOptions, allowCacheWrite = fal
35
35
  loaderType: type,
36
36
  rootDir,
37
37
  locale,
38
- fallbackLocales,
39
- namespaces: namespaces && namespaces.length > 0 ? [...namespaces] : ["*"],
38
+ fallbackLocales: fallbackLocales.join(", "),
39
+ namespaces: namespaces && namespaces.length > 0 ? [...namespaces] : "*",
40
40
  });
41
41
  // ---------------------------------------------------------------------------
42
42
  // Dispatch to loader implementation
@@ -50,7 +50,7 @@ const loadMessages = async ({ config, locale, readOptions, allowCacheWrite = fal
50
50
  namespaces,
51
51
  rootDir,
52
52
  concurrency: loader.concurrency,
53
- readOptions,
53
+ readers,
54
54
  allowCacheWrite,
55
55
  loggerOptions: config.logger,
56
56
  });
@@ -1,4 +1,5 @@
1
1
  import { IntorError, IntorErrorCode } from '../../core/error/intor-error.js';
2
+ import { resolveLoaderOptions } from '../../core/utils/resolve-loader-options.js';
2
3
  import 'logry';
3
4
  import { loadMessages } from '../messages/load-messages.js';
4
5
  import { createTranslator } from '../translator/create-translator.js';
@@ -13,20 +14,24 @@ import { createTranslator } from '../translator/create-translator.js';
13
14
  * before a translator snapshot can be created.
14
15
  */
15
16
  function createIntorRuntime(config, options) {
17
+ const loader = resolveLoaderOptions(config, "server");
16
18
  // Locale that has completed the ensureMessages() phase
17
19
  let ensuredLocale;
18
20
  // Messages prepared during ensureMessages(); may be empty
19
21
  let ensuredMessages;
20
22
  return {
21
23
  async ensureMessages(locale) {
22
- const messages = await loadMessages({
23
- config,
24
- locale,
25
- readOptions: options?.readOptions,
26
- allowCacheWrite: options?.allowCacheWrite ?? false,
27
- });
24
+ let messages;
25
+ if (loader) {
26
+ messages = await loadMessages({
27
+ config,
28
+ locale,
29
+ readers: options?.readers,
30
+ allowCacheWrite: options?.allowCacheWrite || false,
31
+ });
32
+ }
28
33
  ensuredLocale = locale;
29
- ensuredMessages = messages;
34
+ ensuredMessages = messages || {};
30
35
  },
31
36
  translator(locale, options) {
32
37
  // Guard: translator requires ensureMessages() to be completed for this locale
@@ -8,14 +8,18 @@ import { getTranslator as getTranslator$1 } from '../../../server/helpers/get-tr
8
8
 
9
9
  // Implementation
10
10
  async function getTranslator(config, req, params) {
11
- const { preKey, handlers, plugins, readOptions } = params || {};
11
+ const { preKey, handlers, plugins, readers, allowCacheWrite } = params || {};
12
12
  return getTranslator$1(config, {
13
13
  locale: req.intor?.locale || config.defaultLocale,
14
14
  preKey,
15
15
  handlers,
16
16
  plugins,
17
- readOptions,
18
- allowCacheWrite: false,
17
+ readers,
18
+ allowCacheWrite,
19
+ // NOTE:
20
+ // The runtime implementation is intentionally erased.
21
+ // Type safety is guaranteed by public type contracts.
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
23
  });
20
24
  }
21
25
 
@@ -15,7 +15,7 @@ import { getTranslator } from '../../../server/helpers/get-translator.js';
15
15
  *
16
16
  * The resolved routing state is exposed via response headers.
17
17
  *
18
- * - Acts as the bootstrap entry where cache writes are permitted.
18
+ * - Permits cache writes during server execution.
19
19
  * - Convenience routing shortcuts are also bound to the request for downstream consumption.
20
20
  *
21
21
  * @platform Express
@@ -24,7 +24,7 @@ function createIntor(config, options) {
24
24
  return async function intorMiddleware(req, _res, next) {
25
25
  // locale from accept-language header
26
26
  const acceptLanguage = req.headers["accept-language"];
27
- const localeFromAcceptLanguage = getLocaleFromAcceptLanguage(config, acceptLanguage);
27
+ const localeFromAcceptLanguage = getLocaleFromAcceptLanguage(acceptLanguage, config.supportedLocales);
28
28
  // ----------------------------------------------------------
29
29
  // Resolve inbound routing decision (pure computation)
30
30
  // ----------------------------------------------------------
@@ -43,19 +43,18 @@ function createIntor(config, options) {
43
43
  // --------------------------------------------------
44
44
  // Bind inbound routing context
45
45
  // --------------------------------------------------
46
- const { t, hasKey } = (await getTranslator(config, {
46
+ const { hasKey, t } = (await getTranslator(config, {
47
47
  locale,
48
48
  handlers: options?.handlers,
49
49
  plugins: options?.plugins,
50
- readOptions: options?.readOptions,
50
+ readers: options?.readers,
51
51
  allowCacheWrite: true,
52
52
  }));
53
- req.intor = { locale, localeSource };
53
+ req.intor = { locale, localeSource, pathname };
54
54
  // DX shortcuts (optional)
55
55
  req.locale = locale;
56
- req.localeSource = localeSource;
57
- req.t = t;
58
56
  req.hasKey = hasKey;
57
+ req.t = t;
59
58
  return next();
60
59
  };
61
60
  }
@@ -3,15 +3,10 @@ function isPlainObject(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value);
4
4
  }
5
5
  /**
6
- * Check if a value is a valid **Messages** object.
6
+ * Check if a value is a valid MessageObject.
7
7
  *
8
- * - Uses an iterative approach to avoid stack overflow with deeply nested objects.
9
- *
10
- * @example
11
- * ```ts
12
- * isValidMessages({ en: { hello: "Hello" } }) // true
13
- * isValidMessages({ en: { count: 5 } }) // false
14
- * ```
8
+ * - Supports all MessageValue variants (primitive, array, object).
9
+ * - Uses an iterative approach to avoid stack overflow.
15
10
  */
16
11
  function isValidMessages(value) {
17
12
  if (!isPlainObject(value))
@@ -19,16 +14,29 @@ function isValidMessages(value) {
19
14
  const stack = [value];
20
15
  while (stack.length > 0) {
21
16
  const current = stack.pop();
22
- for (const v of Object.values(current)) {
23
- if (typeof v === "string")
24
- continue;
25
- if (isPlainObject(v)) {
26
- stack.push(v);
17
+ // primitives are always valid
18
+ if (current === null ||
19
+ typeof current === "string" ||
20
+ typeof current === "number" ||
21
+ typeof current === "boolean") {
22
+ continue;
23
+ }
24
+ // array → validate each item
25
+ if (Array.isArray(current)) {
26
+ for (const item of current) {
27
+ stack.push(item);
27
28
  }
28
- else {
29
- return false;
29
+ continue;
30
+ }
31
+ // object → validate each value
32
+ if (isPlainObject(current)) {
33
+ for (const v of Object.values(current)) {
34
+ stack.push(v);
30
35
  }
36
+ continue;
31
37
  }
38
+ // everything else is invalid
39
+ return false;
32
40
  }
33
41
  return true;
34
42
  }
@@ -1,5 +1,3 @@
1
- import '../../core/error/intor-error.js';
2
- import 'logry';
3
1
  import { getLocaleFromPathname } from '../locale/get-locale-from-pathname.js';
4
2
  import { getLocaleFromHost } from '../locale/get-locale-from-host.js';
5
3
  import { getLocaleFromQuery } from '../locale/get-locale-from-query.js';
@@ -20,11 +18,13 @@ async function resolveInbound(config, rawPathname, hasRedirected, localeInputs)
20
18
  // ------------------------------------------------------
21
19
  // Resolve locale from inbound inputs
22
20
  // ------------------------------------------------------
23
- const pathLocale = getLocaleFromPathname(config, rawPathname);
21
+ const pathLocale = getLocaleFromPathname(rawPathname, config);
24
22
  const { locale, localeSource } = resolveLocale(config, {
25
23
  path: { locale: pathLocale },
26
- host: { locale: getLocaleFromHost(config, host) },
27
- query: { locale: getLocaleFromQuery(config, query) },
24
+ host: { locale: getLocaleFromHost(host) },
25
+ query: {
26
+ locale: getLocaleFromQuery(query, config.routing.inbound.queryKey),
27
+ },
28
28
  cookie: { locale: cookie },
29
29
  detected: { locale: detected },
30
30
  });
@@ -1,23 +1,30 @@
1
+ import '../../../core/error/intor-error.js';
2
+ import { normalizeLocale } from '../../../core/utils/normalizers/normalize-locale.js';
3
+ import 'logry';
4
+
1
5
  /**
2
- * Resolves the active locale from inbound routing configuration.
6
+ * Resolve the active locale from inbound routing configuration.
3
7
  *
4
- * The first matching locale from the configured sources is used,
5
- * with the detected locale as a guaranteed fallback.
8
+ * Iterates through configured locale sources and returns the first
9
+ * normalized, supported locale. Falls back to the detected locale
10
+ * or the default locale if none match.
6
11
  */
7
12
  function resolveLocale(config, context) {
8
13
  const { localeSources } = config.routing.inbound;
9
14
  for (const source of localeSources) {
10
15
  const locale = context[source]?.locale;
11
- if (!locale)
16
+ const normalized = normalizeLocale(locale, config.supportedLocales);
17
+ if (!normalized)
12
18
  continue;
13
19
  return {
14
- locale,
20
+ locale: normalized,
15
21
  localeSource: source,
16
22
  };
17
23
  }
18
24
  // Fallback: detected is always available
19
25
  return {
20
- locale: context.detected.locale,
26
+ locale: normalizeLocale(context.detected.locale, config.supportedLocales) ||
27
+ config.defaultLocale,
21
28
  localeSource: "detected",
22
29
  };
23
30
  }
@@ -32,7 +32,7 @@ const resolvePathname = (config, rawPathname, context) => {
32
32
  return assertNever(localePrefix);
33
33
  }
34
34
  }
35
- const { pathname } = localizePathname(config, rawPathname, context.locale);
35
+ const { pathname } = localizePathname(rawPathname, config, context.locale);
36
36
  return {
37
37
  pathname,
38
38
  shouldRedirect: directive.type === "redirect",