intor 2.3.28 → 2.3.30

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 (172) hide show
  1. package/dist/core/src/core/messages/load-remote-messages/fetch-remote-resource.js +1 -1
  2. package/dist/core/src/core/messages/load-remote-messages/load-remote-messages.js +2 -2
  3. package/dist/core/src/routing/pathname/canonicalize-pathname.js +11 -7
  4. package/dist/core/src/server/helpers/get-translator.js +2 -2
  5. package/dist/core/src/server/intor/intor.js +3 -8
  6. package/dist/core/src/server/messages/load-messages.js +2 -1
  7. package/dist/core/src/server/translator/init-translator.js +9 -3
  8. package/dist/express/src/core/messages/load-remote-messages/fetch-remote-resource.js +1 -1
  9. package/dist/express/src/core/messages/load-remote-messages/load-remote-messages.js +2 -2
  10. package/dist/express/src/routing/pathname/canonicalize-pathname.js +11 -7
  11. package/dist/express/src/server/helpers/get-translator.js +2 -2
  12. package/dist/express/src/server/messages/load-messages.js +2 -1
  13. package/dist/express/src/server/translator/init-translator.js +9 -3
  14. package/dist/next/export/next/index.js +0 -1
  15. package/dist/next/export/next/server/index.js +0 -1
  16. package/dist/next/src/adapters/next/navigation/link.js +11 -10
  17. package/dist/next/src/adapters/next/navigation/use-router.js +14 -20
  18. package/dist/next/src/adapters/next/server/get-locale.js +4 -1
  19. package/dist/next/src/adapters/next/server/intor.js +1 -1
  20. package/dist/next/src/client/shared/navigation/execute-navigation.js +50 -0
  21. package/dist/next/src/client/shared/utils/build-cookie-string.js +30 -0
  22. package/dist/next/src/client/shared/utils/locale/set-locale-cookie.js +15 -0
  23. package/dist/next/src/core/messages/load-remote-messages/fetch-remote-resource.js +1 -1
  24. package/dist/next/src/core/messages/load-remote-messages/load-remote-messages.js +2 -2
  25. package/dist/next/src/routing/pathname/canonicalize-pathname.js +11 -7
  26. package/dist/next/src/server/helpers/get-translator.js +2 -2
  27. package/dist/next/src/server/intor/intor.js +3 -8
  28. package/dist/next/src/server/messages/load-messages.js +2 -1
  29. package/dist/next/src/server/translator/init-translator.js +9 -3
  30. package/dist/react/export/react/index.js +0 -2
  31. package/dist/react/src/client/shared/messages/create-refetch-messages.js +1 -0
  32. package/dist/react/src/core/messages/load-remote-messages/fetch-remote-resource.js +1 -1
  33. package/dist/react/src/core/messages/load-remote-messages/load-remote-messages.js +2 -2
  34. package/dist/svelte/export/svelte/index.js +4 -2
  35. package/dist/svelte/src/client/shared/messages/create-refetch-messages.js +1 -0
  36. package/dist/svelte/src/client/svelte/{store → provider}/create-intor-store.js +7 -14
  37. package/dist/svelte/src/client/svelte/provider/intor-provider.svelte +7 -0
  38. package/dist/svelte/src/client/svelte/provider/use-intor-context.js +11 -0
  39. package/dist/svelte/src/client/svelte/render/create-svelte-renderer.js +5 -6
  40. package/dist/svelte/src/client/svelte/translator/create-t-rich.js +23 -0
  41. package/dist/svelte/src/client/svelte/translator/use-translator.js +32 -0
  42. package/dist/svelte/src/core/messages/load-remote-messages/fetch-remote-resource.js +1 -1
  43. package/dist/svelte/src/core/messages/load-remote-messages/load-remote-messages.js +2 -2
  44. package/dist/svelte-kit/export/svelte-kit/index.js +1 -0
  45. package/dist/svelte-kit/export/svelte-kit/server/index.js +2 -0
  46. package/dist/svelte-kit/src/adapters/svelte-kit/navigation/use-navigation.js +36 -0
  47. package/dist/svelte-kit/src/adapters/svelte-kit/server/create-intor-handle.js +58 -0
  48. package/dist/svelte-kit/src/adapters/svelte-kit/server/intor.js +24 -0
  49. package/dist/svelte-kit/src/client/shared/navigation/execute-navigation.js +49 -0
  50. package/dist/svelte-kit/src/client/shared/utils/build-cookie-string.js +30 -0
  51. package/dist/svelte-kit/src/client/shared/utils/locale/set-locale-cookie.js +15 -0
  52. package/dist/svelte-kit/src/core/constants/headers.js +6 -0
  53. package/dist/svelte-kit/src/core/error/intor-error.js +9 -0
  54. package/dist/svelte-kit/src/core/logger/get-logger.js +39 -0
  55. package/dist/svelte-kit/src/core/logger/global-logger-pool.js +8 -0
  56. package/dist/svelte-kit/src/core/messages/load-remote-messages/collect-remote-resources.js +25 -0
  57. package/dist/svelte-kit/src/core/messages/load-remote-messages/fetch-remote-resource.js +47 -0
  58. package/dist/svelte-kit/src/core/messages/load-remote-messages/load-remote-messages.js +93 -0
  59. package/dist/svelte-kit/src/core/messages/load-remote-messages/resolve-remote-resources.js +24 -0
  60. package/dist/svelte-kit/src/core/messages/merge-messages.js +33 -0
  61. package/dist/svelte-kit/src/core/messages/utils/is-valid-messages.js +44 -0
  62. package/dist/svelte-kit/src/core/messages/utils/nest-object-from-path.js +21 -0
  63. package/dist/svelte-kit/src/core/utils/deep-merge.js +47 -0
  64. package/dist/svelte-kit/src/core/utils/normalizers/normalize-cache-key.js +45 -0
  65. package/dist/svelte-kit/src/core/utils/normalizers/normalize-locale.js +59 -0
  66. package/dist/svelte-kit/src/core/utils/normalizers/normalize-query.js +25 -0
  67. package/dist/svelte-kit/src/core/utils/resolve-loader-options.js +34 -0
  68. package/dist/{react → svelte-kit}/src/policies/shoud-full-reload.js +1 -1
  69. package/dist/svelte-kit/src/policies/should-sync-locale.js +8 -0
  70. package/dist/svelte-kit/src/routing/inbound/resolve-inbound.js +46 -0
  71. package/dist/svelte-kit/src/routing/inbound/resolve-locale/resolve-locale.js +33 -0
  72. package/dist/svelte-kit/src/routing/inbound/resolve-pathname/resolve-pathname.js +42 -0
  73. package/dist/svelte-kit/src/routing/inbound/resolve-pathname/strategies/all.js +28 -0
  74. package/dist/svelte-kit/src/routing/inbound/resolve-pathname/strategies/except-default.js +29 -0
  75. package/dist/svelte-kit/src/routing/inbound/resolve-pathname/strategies/none.js +8 -0
  76. package/dist/svelte-kit/src/routing/locale/get-locale-from-accept-language.js +38 -0
  77. package/dist/svelte-kit/src/routing/locale/get-locale-from-host.js +32 -0
  78. package/dist/svelte-kit/src/routing/locale/get-locale-from-pathname.js +46 -0
  79. package/dist/svelte-kit/src/routing/locale/get-locale-from-query.js +29 -0
  80. package/dist/{react → svelte-kit}/src/routing/pathname/canonicalize-pathname.js +11 -7
  81. package/dist/svelte-kit/src/server/intor/intor.js +31 -0
  82. package/dist/svelte-kit/src/server/messages/load-local-messages/cache/messages-pool.js +11 -0
  83. package/dist/svelte-kit/src/server/messages/load-local-messages/load-local-messages.js +107 -0
  84. package/dist/svelte-kit/src/server/messages/load-local-messages/read-locale-messages/collect-file-entries/collect-file-entries.js +90 -0
  85. package/dist/svelte-kit/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/parse-file-entries.js +102 -0
  86. package/dist/svelte-kit/src/server/messages/load-local-messages/read-locale-messages/parse-file-entries/utils/json-reader.js +12 -0
  87. package/dist/svelte-kit/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +42 -0
  88. package/dist/svelte-kit/src/server/messages/load-messages.js +77 -0
  89. package/dist/svelte-kit/src/server/translator/create-translator.js +40 -0
  90. package/dist/svelte-kit/src/server/translator/init-translator.js +42 -0
  91. package/dist/types/export/next/index.d.ts +1 -1
  92. package/dist/types/export/next/server/index.d.ts +1 -1
  93. package/dist/types/export/react/index.d.ts +2 -2
  94. package/dist/types/export/svelte/index.d.ts +3 -1
  95. package/dist/types/export/svelte-kit/index.d.ts +1 -0
  96. package/dist/types/export/svelte-kit/server/index.d.ts +1 -0
  97. package/dist/types/src/adapters/express/global.d.ts +5 -8
  98. package/dist/types/src/adapters/next/navigation/index.d.ts +0 -1
  99. package/dist/types/src/adapters/next/server/index.d.ts +0 -1
  100. package/dist/types/src/adapters/svelte-kit/navigation/index.d.ts +1 -0
  101. package/dist/types/src/adapters/svelte-kit/navigation/use-navigation.d.ts +15 -0
  102. package/dist/types/src/adapters/svelte-kit/server/create-intor-handle.d.ts +12 -0
  103. package/dist/types/src/adapters/svelte-kit/server/index.d.ts +2 -0
  104. package/dist/types/src/adapters/svelte-kit/server/intor.d.ts +16 -0
  105. package/dist/types/src/client/index.d.ts +1 -0
  106. package/dist/types/src/client/react/index.d.ts +0 -1
  107. package/dist/types/src/client/shared/navigation/execute-navigation.d.ts +19 -0
  108. package/dist/types/src/client/shared/navigation/index.d.ts +1 -0
  109. package/dist/types/src/client/svelte/index.d.ts +2 -2
  110. package/dist/types/src/client/svelte/provider/create-intor-store.d.ts +3 -0
  111. package/dist/types/src/client/svelte/provider/index.d.ts +3 -0
  112. package/dist/types/src/client/svelte/provider/types.d.ts +18 -0
  113. package/dist/types/src/client/svelte/provider/use-intor-context.d.ts +2 -0
  114. package/dist/types/src/client/svelte/render/types.d.ts +7 -13
  115. package/dist/types/src/client/svelte/translator/create-t-rich.d.ts +15 -0
  116. package/dist/types/src/client/svelte/translator/index.d.ts +1 -0
  117. package/dist/types/src/client/svelte/translator/translator-instance.d.ts +20 -0
  118. package/dist/types/src/client/svelte/translator/use-translator.d.ts +8 -0
  119. package/dist/types/src/core/index.d.ts +1 -1
  120. package/dist/types/src/core/messages/load-remote-messages/fetch-remote-resource.d.ts +3 -1
  121. package/dist/types/src/core/messages/load-remote-messages/load-remote-messages.d.ts +1 -1
  122. package/dist/types/src/core/messages/load-remote-messages/types.d.ts +2 -0
  123. package/dist/types/src/core/types/index.d.ts +1 -0
  124. package/dist/types/src/core/types/runtime-fetch.d.ts +1 -0
  125. package/dist/types/src/routing/inbound/index.d.ts +1 -0
  126. package/dist/types/src/routing/inbound/resolve-inbound.d.ts +1 -12
  127. package/dist/types/src/routing/inbound/types.d.ts +12 -0
  128. package/dist/types/src/routing/index.d.ts +1 -1
  129. package/dist/types/src/routing/pathname/canonicalize-pathname.d.ts +2 -0
  130. package/dist/types/src/server/helpers/get-translator.d.ts +2 -1
  131. package/dist/types/src/server/intor/intor.d.ts +4 -3
  132. package/dist/types/src/server/intor/types.d.ts +0 -2
  133. package/dist/types/src/server/messages/load-messages.d.ts +1 -1
  134. package/dist/types/src/server/messages/types.d.ts +2 -1
  135. package/dist/types/src/server/translator/init-translator.d.ts +2 -2
  136. package/dist/vue/src/client/shared/messages/create-refetch-messages.js +1 -0
  137. package/dist/vue/src/core/messages/load-remote-messages/fetch-remote-resource.js +1 -1
  138. package/dist/vue/src/core/messages/load-remote-messages/load-remote-messages.js +2 -2
  139. package/package.json +11 -1
  140. package/dist/next/src/adapters/next/navigation/use-pathname.js +0 -26
  141. package/dist/next/src/adapters/next/server/get-pathname.js +0 -28
  142. package/dist/react/src/client/react/navigation/use-execute-navigation.js +0 -41
  143. package/dist/react/src/client/react/navigation/use-resolve-navigation.js +0 -16
  144. package/dist/svelte/src/client/svelte/helpers/create-intor.js +0 -45
  145. package/dist/svelte/src/client/svelte/store/create-translator-bindings.js +0 -25
  146. package/dist/types/src/adapters/next/navigation/use-pathname.d.ts +0 -14
  147. package/dist/types/src/adapters/next/server/get-pathname.d.ts +0 -16
  148. package/dist/types/src/client/react/navigation/index.d.ts +0 -2
  149. package/dist/types/src/client/react/navigation/use-execute-navigation.d.ts +0 -5
  150. package/dist/types/src/client/react/navigation/use-resolve-navigation.d.ts +0 -5
  151. package/dist/types/src/client/svelte/helpers/create-intor.d.ts +0 -4
  152. package/dist/types/src/client/svelte/helpers/index.d.ts +0 -1
  153. package/dist/types/src/client/svelte/store/create-intor-store.d.ts +0 -2
  154. package/dist/types/src/client/svelte/store/create-translator-bindings.d.ts +0 -13
  155. package/dist/types/src/client/svelte/store/index.d.ts +0 -2
  156. package/dist/types/src/client/svelte/store/types.d.ts +0 -31
  157. /package/dist/{react → next}/src/policies/should-sync-locale.js +0 -0
  158. /package/dist/svelte/src/client/svelte/{store → provider}/effects/locale-effects.js +0 -0
  159. /package/dist/svelte/src/client/svelte/{store → provider}/effects/messages-effects.js +0 -0
  160. /package/dist/{react → svelte-kit}/src/core/constants/prefix-placeholder.js +0 -0
  161. /package/dist/{react → svelte-kit}/src/core/utils/normalizers/normalize-pathname.js +0 -0
  162. /package/dist/{react → svelte-kit}/src/routing/navigation/decide-strategy.js +0 -0
  163. /package/dist/{react → svelte-kit}/src/routing/navigation/derive-target.js +0 -0
  164. /package/dist/{react → svelte-kit}/src/routing/navigation/resolve-navigation.js +0 -0
  165. /package/dist/{react → svelte-kit}/src/routing/navigation/utils/derive-host-destination.js +0 -0
  166. /package/dist/{react → svelte-kit}/src/routing/navigation/utils/derive-query-destination.js +0 -0
  167. /package/dist/{react → svelte-kit}/src/routing/navigation/utils/is-external-destination.js +0 -0
  168. /package/dist/{react → svelte-kit}/src/routing/pathname/localize-pathname.js +0 -0
  169. /package/dist/{react → svelte-kit}/src/routing/pathname/materialize-pathname.js +0 -0
  170. /package/dist/{react → svelte-kit}/src/routing/pathname/standardize-pathname.js +0 -0
  171. /package/dist/types/src/client/svelte/{store → provider}/effects/locale-effects.d.ts +0 -0
  172. /package/dist/types/src/client/svelte/{store → provider}/effects/messages-effects.d.ts +0 -0
@@ -12,7 +12,7 @@ import { isValidMessages } from '../utils/is-valid-messages.js';
12
12
  * - Validating the returned message structure
13
13
  * - Handling abort and network errors
14
14
  */
15
- async function fetchRemoteResource({ url, headers, signal, loggerOptions, }) {
15
+ async function fetchRemoteResource({ fetch, url, headers, signal, loggerOptions, }) {
16
16
  const baseLogger = getLogger(loggerOptions);
17
17
  const logger = baseLogger.child({ scope: "fetch-locale-messages" });
18
18
  try {
@@ -18,7 +18,7 @@ import { resolveRemoteResources } from './resolve-remote-resources.js';
18
18
  *
19
19
  * Network requests and response validation are delegated to lower-level utilities.
20
20
  */
21
- const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurrency, url: baseUrl, headers, signal, loggerOptions, }) => {
21
+ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurrency, fetch, url: baseUrl, headers, signal, loggerOptions, }) => {
22
22
  const baseLogger = getLogger(loggerOptions);
23
23
  const logger = baseLogger.child({ scope: "load-remote-messages" });
24
24
  // Abort early if the request has already been cancelled
@@ -49,7 +49,7 @@ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurr
49
49
  // -----------------------------------------------------------------
50
50
  // Fetch all message chunks in parallel
51
51
  // -----------------------------------------------------------------
52
- const fetchUrl = (url) => fetchRemoteResource({ url, headers, signal, loggerOptions });
52
+ const fetchUrl = (url) => fetchRemoteResource({ url, headers, signal, loggerOptions, fetch });
53
53
  const results = await Promise.all(resources.map(({ url }) => limit ? limit(() => fetchUrl(url)) : fetchUrl(url)));
54
54
  // Guard: no valid remote resources
55
55
  if (!results.some(Boolean))
@@ -1,3 +1,4 @@
1
+ import { PREFIX_PLACEHOLDER } from '../../core/constants/prefix-placeholder.js';
1
2
  import '../../core/error/intor-error.js';
2
3
  import { normalizePathname } from '../../core/utils/normalizers/normalize-pathname.js';
3
4
  import 'logry';
@@ -6,6 +7,8 @@ import 'p-limit';
6
7
  /**
7
8
  * Returns a canonical, locale-agnostic pathname.
8
9
  *
10
+ * Accepts `{locale}` as a locale placeholder segment.
11
+ *
9
12
  * @example
10
13
  * ```ts
11
14
  * // config.supportedLocales: ["en-US"]
@@ -18,9 +21,8 @@ import 'p-limit';
18
21
  function canonicalizePathname(rawPathname, config) {
19
22
  const { routing, supportedLocales } = config;
20
23
  const { basePath } = routing;
21
- // 1. Normalize pathname
22
24
  const normalizedPathname = normalizePathname(rawPathname);
23
- // 2. Strip basePath
25
+ // Strip basePath
24
26
  let prefixedPathname = normalizedPathname;
25
27
  if (basePath && normalizedPathname === basePath) {
26
28
  prefixedPathname = "/";
@@ -28,12 +30,14 @@ function canonicalizePathname(rawPathname, config) {
28
30
  else if (basePath && normalizedPathname.startsWith(basePath + "/")) {
29
31
  prefixedPathname = normalizedPathname.slice(basePath.length);
30
32
  }
31
- // 3. Detect locale segment
33
+ // Detect locale segment
32
34
  const firstSegment = prefixedPathname.split("/").find(Boolean);
33
- const locale = firstSegment && supportedLocales.includes(firstSegment)
34
- ? firstSegment
35
- : undefined;
36
- // 4. Strip locale segment
35
+ const locale = firstSegment === PREFIX_PLACEHOLDER
36
+ ? PREFIX_PLACEHOLDER
37
+ : firstSegment && supportedLocales.includes(firstSegment)
38
+ ? firstSegment
39
+ : undefined;
40
+ // Strip locale segment
37
41
  return locale
38
42
  ? prefixedPathname.slice(locale.length + 1) || "/"
39
43
  : prefixedPathname;
@@ -2,12 +2,12 @@ import { initTranslator } from '../translator/init-translator.js';
2
2
 
3
3
  // Implementation
4
4
  async function getTranslator(config, params) {
5
- const { readers, allowCacheWrite, preKey, handlers, plugins } = params;
6
- const locale = params.locale;
5
+ const { locale, readers, allowCacheWrite, fetch, preKey, handlers, plugins } = params;
7
6
  // Initialize a locale-bound translator snapshot with messages loaded
8
7
  const translator = await initTranslator(config, locale, {
9
8
  readers,
10
9
  allowCacheWrite,
10
+ fetch: fetch || globalThis.fetch,
11
11
  preKey,
12
12
  plugins,
13
13
  handlers,
@@ -9,21 +9,16 @@ import { initTranslator } from '../translator/init-translator.js';
9
9
  * Produces a server-side snapshot for SSR and
10
10
  * full-stack rendering environments.
11
11
  */
12
- async function intor(config, localeOrResolver, options) {
12
+ async function intor(config, locale, options) {
13
13
  const baseLogger = getLogger(config.logger);
14
14
  const logger = baseLogger.child({ scope: "intor" });
15
15
  logger.info("Start Intor initialization.");
16
- // Resolve locale
17
- const isLocaleFunction = typeof localeOrResolver === "function";
18
- const locale = isLocaleFunction
19
- ? await localeOrResolver(config)
20
- : localeOrResolver || config.defaultLocale;
21
- const source = typeof localeOrResolver === "function" ? "resolver" : "static";
22
- logger.debug(`Initial locale resolved as "${locale}" via "${source}".`);
16
+ logger.debug(`Initializing Intor with locale "${locale}".`);
23
17
  // Initialize a locale-bound translator snapshot with messages loaded
24
18
  const translator = await initTranslator(config, locale, {
25
19
  readers: options?.readers,
26
20
  allowCacheWrite: options?.allowCacheWrite,
21
+ fetch: options?.fetch || globalThis.fetch,
27
22
  });
28
23
  logger.info("Intor initialized.");
29
24
  return {
@@ -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, readers, allowCacheWrite = false, }) => {
20
+ const loadMessages = async ({ config, locale, readers, allowCacheWrite = false, fetch, }) => {
21
21
  const baseLogger = getLogger(config.logger);
22
22
  const logger = baseLogger.child({ scope: "load-messages" });
23
23
  // ---------------------------------------------------------------------------
@@ -61,6 +61,7 @@ const loadMessages = async ({ config, locale, readers, allowCacheWrite = false,
61
61
  fallbackLocales,
62
62
  namespaces,
63
63
  concurrency,
64
+ fetch,
64
65
  url: loader.url,
65
66
  headers: loader.headers,
66
67
  loggerOptions: config.logger,
@@ -14,13 +14,19 @@ import { createTranslator } from './create-translator.js';
14
14
  * - Returns an immutable translator snapshot.
15
15
  */
16
16
  async function initTranslator(config, locale, options) {
17
- const { readers, allowCacheWrite = false, preKey, handlers, plugins, } = options || {};
17
+ const { readers, allowCacheWrite = false, fetch, preKey, handlers, plugins, } = options;
18
18
  const loader = resolveLoaderOptions(config, "server");
19
19
  // Load messages
20
20
  let messages = {};
21
21
  if (loader) {
22
- messages =
23
- (await loadMessages({ config, locale, readers, allowCacheWrite })) || {};
22
+ const loaded = await loadMessages({
23
+ config,
24
+ locale,
25
+ readers,
26
+ allowCacheWrite,
27
+ fetch,
28
+ });
29
+ messages = loaded || {};
24
30
  }
25
31
  // Create immutable translator snapshot
26
32
  return createTranslator({
@@ -12,7 +12,7 @@ import { isValidMessages } from '../utils/is-valid-messages.js';
12
12
  * - Validating the returned message structure
13
13
  * - Handling abort and network errors
14
14
  */
15
- async function fetchRemoteResource({ url, headers, signal, loggerOptions, }) {
15
+ async function fetchRemoteResource({ fetch, url, headers, signal, loggerOptions, }) {
16
16
  const baseLogger = getLogger(loggerOptions);
17
17
  const logger = baseLogger.child({ scope: "fetch-locale-messages" });
18
18
  try {
@@ -18,7 +18,7 @@ import { resolveRemoteResources } from './resolve-remote-resources.js';
18
18
  *
19
19
  * Network requests and response validation are delegated to lower-level utilities.
20
20
  */
21
- const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurrency, url: baseUrl, headers, signal, loggerOptions, }) => {
21
+ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurrency, fetch, url: baseUrl, headers, signal, loggerOptions, }) => {
22
22
  const baseLogger = getLogger(loggerOptions);
23
23
  const logger = baseLogger.child({ scope: "load-remote-messages" });
24
24
  // Abort early if the request has already been cancelled
@@ -49,7 +49,7 @@ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurr
49
49
  // -----------------------------------------------------------------
50
50
  // Fetch all message chunks in parallel
51
51
  // -----------------------------------------------------------------
52
- const fetchUrl = (url) => fetchRemoteResource({ url, headers, signal, loggerOptions });
52
+ const fetchUrl = (url) => fetchRemoteResource({ url, headers, signal, loggerOptions, fetch });
53
53
  const results = await Promise.all(resources.map(({ url }) => limit ? limit(() => fetchUrl(url)) : fetchUrl(url)));
54
54
  // Guard: no valid remote resources
55
55
  if (!results.some(Boolean))
@@ -1,3 +1,4 @@
1
+ import { PREFIX_PLACEHOLDER } from '../../core/constants/prefix-placeholder.js';
1
2
  import '../../core/error/intor-error.js';
2
3
  import { normalizePathname } from '../../core/utils/normalizers/normalize-pathname.js';
3
4
  import 'logry';
@@ -6,6 +7,8 @@ import 'p-limit';
6
7
  /**
7
8
  * Returns a canonical, locale-agnostic pathname.
8
9
  *
10
+ * Accepts `{locale}` as a locale placeholder segment.
11
+ *
9
12
  * @example
10
13
  * ```ts
11
14
  * // config.supportedLocales: ["en-US"]
@@ -18,9 +21,8 @@ import 'p-limit';
18
21
  function canonicalizePathname(rawPathname, config) {
19
22
  const { routing, supportedLocales } = config;
20
23
  const { basePath } = routing;
21
- // 1. Normalize pathname
22
24
  const normalizedPathname = normalizePathname(rawPathname);
23
- // 2. Strip basePath
25
+ // Strip basePath
24
26
  let prefixedPathname = normalizedPathname;
25
27
  if (basePath && normalizedPathname === basePath) {
26
28
  prefixedPathname = "/";
@@ -28,12 +30,14 @@ function canonicalizePathname(rawPathname, config) {
28
30
  else if (basePath && normalizedPathname.startsWith(basePath + "/")) {
29
31
  prefixedPathname = normalizedPathname.slice(basePath.length);
30
32
  }
31
- // 3. Detect locale segment
33
+ // Detect locale segment
32
34
  const firstSegment = prefixedPathname.split("/").find(Boolean);
33
- const locale = firstSegment && supportedLocales.includes(firstSegment)
34
- ? firstSegment
35
- : undefined;
36
- // 4. Strip locale segment
35
+ const locale = firstSegment === PREFIX_PLACEHOLDER
36
+ ? PREFIX_PLACEHOLDER
37
+ : firstSegment && supportedLocales.includes(firstSegment)
38
+ ? firstSegment
39
+ : undefined;
40
+ // Strip locale segment
37
41
  return locale
38
42
  ? prefixedPathname.slice(locale.length + 1) || "/"
39
43
  : prefixedPathname;
@@ -2,12 +2,12 @@ import { initTranslator } from '../translator/init-translator.js';
2
2
 
3
3
  // Implementation
4
4
  async function getTranslator(config, params) {
5
- const { readers, allowCacheWrite, preKey, handlers, plugins } = params;
6
- const locale = params.locale;
5
+ const { locale, readers, allowCacheWrite, fetch, preKey, handlers, plugins } = params;
7
6
  // Initialize a locale-bound translator snapshot with messages loaded
8
7
  const translator = await initTranslator(config, locale, {
9
8
  readers,
10
9
  allowCacheWrite,
10
+ fetch: fetch || globalThis.fetch,
11
11
  preKey,
12
12
  plugins,
13
13
  handlers,
@@ -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, readers, allowCacheWrite = false, }) => {
20
+ const loadMessages = async ({ config, locale, readers, allowCacheWrite = false, fetch, }) => {
21
21
  const baseLogger = getLogger(config.logger);
22
22
  const logger = baseLogger.child({ scope: "load-messages" });
23
23
  // ---------------------------------------------------------------------------
@@ -61,6 +61,7 @@ const loadMessages = async ({ config, locale, readers, allowCacheWrite = false,
61
61
  fallbackLocales,
62
62
  namespaces,
63
63
  concurrency,
64
+ fetch,
64
65
  url: loader.url,
65
66
  headers: loader.headers,
66
67
  loggerOptions: config.logger,
@@ -14,13 +14,19 @@ import { createTranslator } from './create-translator.js';
14
14
  * - Returns an immutable translator snapshot.
15
15
  */
16
16
  async function initTranslator(config, locale, options) {
17
- const { readers, allowCacheWrite = false, preKey, handlers, plugins, } = options || {};
17
+ const { readers, allowCacheWrite = false, fetch, preKey, handlers, plugins, } = options;
18
18
  const loader = resolveLoaderOptions(config);
19
19
  // Load messages
20
20
  let messages = {};
21
21
  if (loader) {
22
- messages =
23
- (await loadMessages({ config, locale, readers, allowCacheWrite })) || {};
22
+ const loaded = await loadMessages({
23
+ config,
24
+ locale,
25
+ readers,
26
+ allowCacheWrite,
27
+ fetch,
28
+ });
29
+ messages = loaded || {};
24
30
  }
25
31
  // Create immutable translator snapshot
26
32
  return createTranslator({
@@ -1,4 +1,3 @@
1
1
  export { Link } from '../../src/adapters/next/navigation/link.js';
2
- export { usePathname } from '../../src/adapters/next/navigation/use-pathname.js';
3
2
  export { useRouter } from '../../src/adapters/next/navigation/use-router.js';
4
3
  export { redirect } from '../../src/adapters/next/navigation/redirect.js';
@@ -1,4 +1,3 @@
1
1
  export { intor } from '../../../src/adapters/next/server/intor.js';
2
2
  export { getLocale } from '../../../src/adapters/next/server/get-locale.js';
3
- export { getPathname } from '../../../src/adapters/next/server/get-pathname.js';
4
3
  export { getTranslator } from '../../../src/adapters/next/server/get-translator.js';
@@ -2,9 +2,14 @@
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { formatUrl } from 'next/dist/shared/lib/router/utils/format-url';
4
4
  import NextLink from 'next/link';
5
+ import { usePathname } from 'next/navigation';
5
6
  import 'react';
6
- import { useResolveNavigation, useExecuteNavigation } from 'intor/react';
7
- import { usePathname } from './use-pathname.js';
7
+ import { executeNavigation } from '../../../client/shared/navigation/execute-navigation.js';
8
+ import { useIntorContext } from 'intor/react';
9
+ import '../../../core/error/intor-error.js';
10
+ import 'logry';
11
+ import 'p-limit';
12
+ import { resolveNavigation } from '../../../routing/navigation/resolve-navigation.js';
8
13
 
9
14
  /**
10
15
  * Render a locale-aware link for the current execution context.
@@ -15,16 +20,12 @@ import { usePathname } from './use-pathname.js';
15
20
  * @platform Next.js
16
21
  */
17
22
  const Link = ({ href, locale, children, onClick, ...props }) => {
18
- const { pathname } = usePathname();
19
- const resolveNavigation = useResolveNavigation();
20
- const executeNavigation = useExecuteNavigation();
23
+ const { config, locale: currentLocale, setLocale } = useIntorContext();
24
+ const currentPathname = usePathname();
21
25
  // Normalize href into a string destination
22
26
  const rawDestination = typeof href === "string" ? href : href ? formatUrl(href) : undefined;
23
27
  // Resolve navigation result for this link
24
- const navigationResult = resolveNavigation(pathname, {
25
- destination: rawDestination,
26
- locale,
27
- });
28
+ const navigationResult = resolveNavigation(config, currentLocale, currentPathname, { destination: rawDestination, locale });
28
29
  // --------------------------------------------------
29
30
  // Execute navigation on user interaction
30
31
  // --------------------------------------------------
@@ -32,7 +33,7 @@ const Link = ({ href, locale, children, onClick, ...props }) => {
32
33
  onClick?.(e);
33
34
  if (e.defaultPrevented)
34
35
  return;
35
- executeNavigation(navigationResult, e);
36
+ executeNavigation(navigationResult, { config, currentLocale, setLocale }, e);
36
37
  };
37
38
  return (jsx(NextLink, { href: navigationResult.destination, onClick: handleClick, ...props, children: children }));
38
39
  };
@@ -1,6 +1,10 @@
1
- import { useRouter as useRouter$1 } from 'next/navigation';
2
- import { useResolveNavigation, useExecuteNavigation } from 'intor/react';
3
- import { usePathname } from './use-pathname.js';
1
+ import { usePathname, useRouter as useRouter$1 } from 'next/navigation';
2
+ import { executeNavigation } from '../../../client/shared/navigation/execute-navigation.js';
3
+ import { useIntorContext } from 'intor/react';
4
+ import '../../../core/error/intor-error.js';
5
+ import 'logry';
6
+ import 'p-limit';
7
+ import { resolveNavigation } from '../../../routing/navigation/resolve-navigation.js';
4
8
 
5
9
  /**
6
10
  * Locale-aware router hook for the current execution context.
@@ -11,40 +15,30 @@ import { usePathname } from './use-pathname.js';
11
15
  * @platform Next.js
12
16
  */
13
17
  const useRouter = () => {
18
+ const { config, locale: currentLocale, setLocale } = useIntorContext();
19
+ const currentPathname = usePathname();
14
20
  const { push: nextRouterPush, replace: nextRouterReplace, prefetch: nextRouterPrefetch, ...rest } = useRouter$1();
15
- const { pathname } = usePathname();
16
- const resolveNavigation = useResolveNavigation();
17
- const executeNavigation = useExecuteNavigation();
18
21
  // --------------------------------------------------
19
22
  // push
20
23
  // --------------------------------------------------
21
24
  const push = (href, options) => {
22
- const navigationResult = resolveNavigation(pathname, {
23
- destination: href,
24
- locale: options?.locale,
25
- });
26
- executeNavigation(navigationResult);
25
+ const navigationResult = resolveNavigation(config, currentLocale, currentPathname, { destination: href, locale: options?.locale });
26
+ executeNavigation(navigationResult, { config, currentLocale, setLocale });
27
27
  nextRouterPush(navigationResult.destination, options);
28
28
  };
29
29
  // --------------------------------------------------
30
30
  // replace
31
31
  // --------------------------------------------------
32
32
  const replace = (href, options) => {
33
- const navigationResult = resolveNavigation(pathname, {
34
- destination: href,
35
- locale: options?.locale,
36
- });
37
- executeNavigation(navigationResult);
33
+ const navigationResult = resolveNavigation(config, currentLocale, currentPathname, { destination: href, locale: options?.locale });
34
+ executeNavigation(navigationResult, { config, currentLocale, setLocale });
38
35
  nextRouterReplace(navigationResult.destination, options);
39
36
  };
40
37
  // --------------------------------------------------
41
38
  // prefetch
42
39
  // --------------------------------------------------
43
40
  const prefetch = (href, options) => {
44
- const { kind, destination } = resolveNavigation(pathname, {
45
- destination: href,
46
- locale: options?.locale,
47
- });
41
+ const { kind, destination } = resolveNavigation(config, currentLocale, currentPathname, { destination: href, locale: options?.locale });
48
42
  if (kind !== "client")
49
43
  return; // Prefetch only makes sense for client-side navigation
50
44
  nextRouterPrefetch(destination, options);
@@ -1,6 +1,7 @@
1
1
  import { headers, cookies } from 'next/headers';
2
2
  import { INTOR_HEADERS } from '../../../core/constants/headers.js';
3
3
  import '../../../core/error/intor-error.js';
4
+ import { normalizeLocale } from '../../../core/utils/normalizers/normalize-locale.js';
4
5
  import 'logry';
5
6
  import 'p-limit';
6
7
 
@@ -21,7 +22,9 @@ const getLocale = async (config) => {
21
22
  const cookieStore = await cookies();
22
23
  const cookieLocale = cookieStore.get(config.cookie.name)?.value;
23
24
  if (cookieLocale) {
24
- return cookieLocale;
25
+ const resolved = normalizeLocale(cookieLocale, config.supportedLocales);
26
+ if (resolved)
27
+ return resolved;
25
28
  }
26
29
  // Explicit default
27
30
  return config.defaultLocale;
@@ -15,7 +15,7 @@ import { getLocale } from './get-locale.js';
15
15
  * @platform Next.js
16
16
  */
17
17
  async function intor(config, options) {
18
- return await intor$1(config, getLocale, {
18
+ return await intor$1(config, await getLocale(config), {
19
19
  readers: options?.readers,
20
20
  allowCacheWrite: options?.allowCacheWrite ?? true,
21
21
  });
@@ -0,0 +1,50 @@
1
+ import '../../../core/error/intor-error.js';
2
+ import 'logry';
3
+ import 'p-limit';
4
+ import { shouldSyncLocale } from '../../../policies/should-sync-locale.js';
5
+ import { setLocaleCookie } from '../utils/locale/set-locale-cookie.js';
6
+
7
+ /**
8
+ * Executes a resolved navigation result.
9
+ *
10
+ * Applies all imperative side effects required to complete navigation,
11
+ * including locale synchronization, cookie persistence, and full reloads.
12
+ *
13
+ * This function must be called after `resolveNavigation`.
14
+ */
15
+ function executeNavigation(navigationResult, context, e) {
16
+ const { config, currentLocale, setLocale } = context;
17
+ const { cookie } = config;
18
+ const { destination, kind, locale } = navigationResult;
19
+ // ------------------------------------------------------
20
+ // External navigation: let browser handle it
21
+ // ------------------------------------------------------
22
+ if (kind === "external") {
23
+ return;
24
+ }
25
+ // ------------------------------------------------------
26
+ // Full reload: commit locale side effects, then perform document reload
27
+ // ------------------------------------------------------
28
+ if (kind === "reload") {
29
+ e?.preventDefault();
30
+ if (shouldSyncLocale(locale, currentLocale)) {
31
+ if (cookie.persist) {
32
+ setLocaleCookie(cookie, locale);
33
+ }
34
+ }
35
+ globalThis.location.href = destination;
36
+ return;
37
+ }
38
+ // ------------------------------------------------------
39
+ // Client-side navigation only
40
+ // ------------------------------------------------------
41
+ if (shouldSyncLocale(locale, currentLocale)) {
42
+ // Eagerly persist locale to avoid stale cookie during client-side navigation.
43
+ if (cookie.persist) {
44
+ setLocaleCookie(cookie, locale);
45
+ }
46
+ setLocale(locale);
47
+ }
48
+ }
49
+
50
+ export { executeNavigation };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Build a serialized cookie string.
3
+ */
4
+ const buildCookieString = (cookieOptions, value) => {
5
+ const { name, maxAge, path, domain, sameSite, secure } = cookieOptions;
6
+ // Cookie name and encoded value
7
+ const parts = [`${name}=${encodeURIComponent(value)}`];
8
+ // Add expiration and max-age if provided
9
+ if (maxAge) {
10
+ const expires = new Date(Date.now() + maxAge * 1000).toUTCString();
11
+ parts.push(`expires=${expires}`, `max-age=${maxAge}`);
12
+ }
13
+ // Set path (default to "/")
14
+ parts.push(`path=${path ?? "/"}`);
15
+ // Add domain if specified
16
+ if (domain) {
17
+ parts.push(`domain=${domain}`);
18
+ }
19
+ // Add SameSite policy (e.g., Lax, Strict)
20
+ if (sameSite) {
21
+ parts.push(`SameSite=${sameSite[0].toUpperCase()}${sameSite.slice(1).toLowerCase()}`);
22
+ }
23
+ // Add Secure flag if not explicitly disabled
24
+ if (secure !== false) {
25
+ parts.push(`Secure`);
26
+ }
27
+ return parts.join("; ");
28
+ };
29
+
30
+ export { buildCookieString };
@@ -0,0 +1,15 @@
1
+ import { buildCookieString } from '../build-cookie-string.js';
2
+
3
+ /**
4
+ * Persist locale to a cookie.
5
+ *
6
+ * This function relies on `document.cookie`.
7
+ */
8
+ const setLocaleCookie = (cookieOptions, locale) => {
9
+ if (typeof document === "undefined")
10
+ return;
11
+ // Build and apply the cookie string
12
+ document.cookie = buildCookieString(cookieOptions, locale);
13
+ };
14
+
15
+ export { setLocaleCookie };
@@ -12,7 +12,7 @@ import { isValidMessages } from '../utils/is-valid-messages.js';
12
12
  * - Validating the returned message structure
13
13
  * - Handling abort and network errors
14
14
  */
15
- async function fetchRemoteResource({ url, headers, signal, loggerOptions, }) {
15
+ async function fetchRemoteResource({ fetch, url, headers, signal, loggerOptions, }) {
16
16
  const baseLogger = getLogger(loggerOptions);
17
17
  const logger = baseLogger.child({ scope: "fetch-locale-messages" });
18
18
  try {
@@ -18,7 +18,7 @@ import { resolveRemoteResources } from './resolve-remote-resources.js';
18
18
  *
19
19
  * Network requests and response validation are delegated to lower-level utilities.
20
20
  */
21
- const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurrency, url: baseUrl, headers, signal, loggerOptions, }) => {
21
+ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurrency, fetch, url: baseUrl, headers, signal, loggerOptions, }) => {
22
22
  const baseLogger = getLogger(loggerOptions);
23
23
  const logger = baseLogger.child({ scope: "load-remote-messages" });
24
24
  // Abort early if the request has already been cancelled
@@ -49,7 +49,7 @@ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, concurr
49
49
  // -----------------------------------------------------------------
50
50
  // Fetch all message chunks in parallel
51
51
  // -----------------------------------------------------------------
52
- const fetchUrl = (url) => fetchRemoteResource({ url, headers, signal, loggerOptions });
52
+ const fetchUrl = (url) => fetchRemoteResource({ url, headers, signal, loggerOptions, fetch });
53
53
  const results = await Promise.all(resources.map(({ url }) => limit ? limit(() => fetchUrl(url)) : fetchUrl(url)));
54
54
  // Guard: no valid remote resources
55
55
  if (!results.some(Boolean))
@@ -1,3 +1,4 @@
1
+ import { PREFIX_PLACEHOLDER } from '../../core/constants/prefix-placeholder.js';
1
2
  import '../../core/error/intor-error.js';
2
3
  import { normalizePathname } from '../../core/utils/normalizers/normalize-pathname.js';
3
4
  import 'logry';
@@ -6,6 +7,8 @@ import 'p-limit';
6
7
  /**
7
8
  * Returns a canonical, locale-agnostic pathname.
8
9
  *
10
+ * Accepts `{locale}` as a locale placeholder segment.
11
+ *
9
12
  * @example
10
13
  * ```ts
11
14
  * // config.supportedLocales: ["en-US"]
@@ -18,9 +21,8 @@ import 'p-limit';
18
21
  function canonicalizePathname(rawPathname, config) {
19
22
  const { routing, supportedLocales } = config;
20
23
  const { basePath } = routing;
21
- // 1. Normalize pathname
22
24
  const normalizedPathname = normalizePathname(rawPathname);
23
- // 2. Strip basePath
25
+ // Strip basePath
24
26
  let prefixedPathname = normalizedPathname;
25
27
  if (basePath && normalizedPathname === basePath) {
26
28
  prefixedPathname = "/";
@@ -28,12 +30,14 @@ function canonicalizePathname(rawPathname, config) {
28
30
  else if (basePath && normalizedPathname.startsWith(basePath + "/")) {
29
31
  prefixedPathname = normalizedPathname.slice(basePath.length);
30
32
  }
31
- // 3. Detect locale segment
33
+ // Detect locale segment
32
34
  const firstSegment = prefixedPathname.split("/").find(Boolean);
33
- const locale = firstSegment && supportedLocales.includes(firstSegment)
34
- ? firstSegment
35
- : undefined;
36
- // 4. Strip locale segment
35
+ const locale = firstSegment === PREFIX_PLACEHOLDER
36
+ ? PREFIX_PLACEHOLDER
37
+ : firstSegment && supportedLocales.includes(firstSegment)
38
+ ? firstSegment
39
+ : undefined;
40
+ // Strip locale segment
37
41
  return locale
38
42
  ? prefixedPathname.slice(locale.length + 1) || "/"
39
43
  : prefixedPathname;
@@ -2,12 +2,12 @@ import { initTranslator } from '../translator/init-translator.js';
2
2
 
3
3
  // Implementation
4
4
  async function getTranslator(config, params) {
5
- const { readers, allowCacheWrite, preKey, handlers, plugins } = params;
6
- const locale = params.locale;
5
+ const { locale, readers, allowCacheWrite, fetch, preKey, handlers, plugins } = params;
7
6
  // Initialize a locale-bound translator snapshot with messages loaded
8
7
  const translator = await initTranslator(config, locale, {
9
8
  readers,
10
9
  allowCacheWrite,
10
+ fetch: fetch || globalThis.fetch,
11
11
  preKey,
12
12
  plugins,
13
13
  handlers,