intor 2.0.3 → 2.1.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.
@@ -5,6 +5,7 @@ import { LinkProps as LinkProps$1 } from 'next/link';
5
5
  import { Url } from 'next/dist/shared/lib/router/router';
6
6
  import * as next_dist_shared_lib_app_router_context_shared_runtime from 'next/dist/shared/lib/app-router-context.shared-runtime';
7
7
  import { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime';
8
+ import { RedirectType } from 'next/navigation';
8
9
 
9
10
  type CookieRawOptions = {
10
11
  /** Completely disable cookie usage (no read, no write, no lookup by name) - default: false */
@@ -143,60 +144,102 @@ type TranslateHandlersProviderProps = {
143
144
 
144
145
  declare const TranslateHandlersProvider: ({ children, handlers, }: TranslateHandlersProviderProps) => React.JSX.Element;
145
146
 
146
- type PreKey<M> = NodeKeys<UnionLocaleMessages<M>> & string;
147
- interface SharedMethod<M> {
147
+ declare const PREFIX_PLACEHOLDER = "{locale}";
148
+
149
+ type IfGen<Then, Else = never> = IntorGeneratedTypes extends void ? Else : Then;
150
+ type GenConfigKeys = IfGen<keyof IntorGeneratedTypes, string>;
151
+ type GenConfig<C extends GenConfigKeys = "__default__"> = IntorGeneratedTypes extends void ? {
152
+ Locales: string;
153
+ Messages: LocaleNamespaceMessages;
154
+ } : C extends keyof IntorGeneratedTypes ? {
155
+ Locales: IntorGeneratedTypes[C]["Locales"];
156
+ Messages: {
157
+ [K in IntorGeneratedTypes[C]["Locales"]]: IntorGeneratedTypes[C]["Messages"][typeof PREFIX_PLACEHOLDER];
158
+ };
159
+ } : never;
160
+ type GenMessages<C extends GenConfigKeys = "__default__"> = GenConfig<C>["Messages"];
161
+ type GenLocaleFallback = Locale;
162
+ type GenLocale<Config extends string = "__default__"> = Config extends keyof IntorGeneratedTypes ? IntorGeneratedTypes[Config]["Locales"] : GenLocaleFallback;
163
+
164
+ type PreKey<C extends GenConfigKeys = "__default__"> = NodeKeys<UnionLocaleMessages<GenMessages<C>>>;
165
+ interface TranslatorBaseProps<M> {
148
166
  messages: M;
149
167
  locale: LocaleKey<M>;
168
+ }
169
+ interface TranslatorClientProps<M> {
150
170
  isLoading: boolean;
151
171
  setLocale: (locale: LocaleKey<M>) => void;
152
172
  }
153
- type TranslatorMethods<M> = {
154
- hasKey: (key?: InferTranslatorKey<M> & string, targetLocale?: LocaleKey<M>) => boolean;
155
- t: (key?: InferTranslatorKey<M> & string, replacements?: Replacement | RichReplacement) => string;
156
- } & SharedMethod<M>;
157
- type ScopedTranslatorMethods<M, K extends string> = {
158
- hasKey: (key?: M extends void ? string : ScopedLeafKeys<M, K> & string, targetLocale?: LocaleKey<M>) => boolean;
159
- t: (key?: M extends void ? string : ScopedLeafKeys<M, K> & string, replacements?: Replacement | RichReplacement) => string;
160
- } & SharedMethod<M>;
161
-
162
- type IntorMessages = void;
163
- declare function useIntor<M = IntorMessages>(): TranslatorMethods<M>;
164
- declare function useIntor<M = IntorMessages, K extends PreKey<M> = PreKey<M>>(preKey: M extends void ? string : K): ScopedTranslatorMethods<M, K>;
173
+ type TranslatorInstance<M> = {
174
+ hasKey: (key?: IfGen<InferTranslatorKey<M>, string>, targetLocale?: LocaleKey<M> | undefined) => boolean;
175
+ t: <Result = string>(key?: IfGen<InferTranslatorKey<M>, string>, replacements?: Replacement | RichReplacement) => Result;
176
+ } & TranslatorBaseProps<M> & TranslatorClientProps<M>;
177
+ type ScopedTranslatorInstance<M, K extends string> = {
178
+ hasKey: (key?: IfGen<ScopedLeafKeys<M, K> & string, string>, targetLocale?: LocaleKey<M>) => boolean;
179
+ t: (key?: IfGen<ScopedLeafKeys<M, K> & string, string>, replacements?: Replacement | RichReplacement) => string;
180
+ } & TranslatorBaseProps<M> & TranslatorClientProps<M>;
181
+
182
+ /**
183
+ * React hook to access a ready-to-use translator instance in the client.
184
+ *
185
+ * - Provides `t`, `hasKey`, `messages`, `locale`, `isLoading` and `setLocale`.
186
+ * - Supports optional `preKey` to create a scoped translator for nested keys.
187
+ * - Can accept a generic type parameter `M` to strongly type your messages.
188
+ */
189
+ declare function useTranslator<C extends GenConfigKeys = "__default__">(): TranslatorInstance<GenMessages<C>>;
190
+ declare function useTranslator<C extends GenConfigKeys = "__default__", K extends PreKey<C> = PreKey<C>>(preKey: IfGen<K, string>): ScopedTranslatorInstance<GenMessages<C>, K>;
165
191
 
166
192
  interface LinkProps extends Omit<LinkProps$1, "href">, Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
167
193
  href?: Url;
168
- locale?: string;
194
+ locale?: GenLocale;
169
195
  }
170
196
  /**
171
- * Localized Link component wrapping Next.js Link.
197
+ * Localized Link component
172
198
  *
173
- * If loader type is "import", triggers a full page reload by setting window.location.href.
174
- * Otherwise, it updates the locale in context without a reload.
199
+ * - Wraps Next.js Link and handles locale switching.
200
+ * - Full reload occurs only if the locale changes and requires it; otherwise updates context.
175
201
  */
176
202
  declare const Link: ({ href, locale, children, onClick, ...props }: LinkProps) => React.JSX.Element;
177
203
 
178
204
  /**
179
- * Custom hook to get the localized pathname.
205
+ * usePathname hook
180
206
  *
181
- * It fetches the raw pathname from Next.js,
182
- * then prefixes it with the current locale
183
- * based on the app's configuration and locale context.
207
+ * Wraps Next.js usePathname and returns the current pathname prefixed with the active locale.
184
208
  */
185
209
  declare const usePathname: () => string;
186
210
 
211
+ /**
212
+ * useRouter hook.
213
+ *
214
+ * Wraps Next.js useRouter and provides push/replace methods that automatically switch locale.
215
+ */
187
216
  declare const useRouter: () => {
188
217
  back(): void;
189
218
  forward(): void;
190
219
  refresh(): void;
191
220
  prefetch(href: string, options?: next_dist_shared_lib_app_router_context_shared_runtime.PrefetchOptions): void;
192
221
  push: (href: string, options?: NavigateOptions & {
193
- locale?: Locale;
222
+ locale?: GenLocale;
194
223
  }) => void;
195
224
  replace: (href: string, options?: NavigateOptions & {
196
225
  locale?: Locale;
197
226
  }) => void;
198
227
  };
199
228
 
229
+ /**
230
+ * redirect utility.
231
+ *
232
+ * - Wraps Next.js redirect and applies locale-aware navigation.
233
+ * - Automatically prefixes the pathname with the correct locale.
234
+ * - External URLs are redirected directly without modification.
235
+ */
236
+ declare const redirect: ({ config, locale, url, type, }: {
237
+ config: IntorResolvedConfig;
238
+ locale?: GenLocale;
239
+ url: string;
240
+ type?: RedirectType | undefined;
241
+ }) => Promise<never>;
242
+
200
243
  declare const PATHNAME_HEADER_NAME = "x-intor-pathname";
201
244
 
202
- export { IntorProvider, type IntorProviderProps, Link, PATHNAME_HEADER_NAME, TranslateHandlersProvider, type TranslateHandlersProviderProps, useIntor, usePathname, useRouter };
245
+ export { IntorProvider, type IntorProviderProps, Link, PATHNAME_HEADER_NAME, TranslateHandlersProvider, type TranslateHandlersProviderProps, redirect, usePathname, useRouter, useTranslator };
@@ -5,6 +5,7 @@ import { LinkProps as LinkProps$1 } from 'next/link';
5
5
  import { Url } from 'next/dist/shared/lib/router/router';
6
6
  import * as next_dist_shared_lib_app_router_context_shared_runtime from 'next/dist/shared/lib/app-router-context.shared-runtime';
7
7
  import { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime';
8
+ import { RedirectType } from 'next/navigation';
8
9
 
9
10
  type CookieRawOptions = {
10
11
  /** Completely disable cookie usage (no read, no write, no lookup by name) - default: false */
@@ -143,60 +144,102 @@ type TranslateHandlersProviderProps = {
143
144
 
144
145
  declare const TranslateHandlersProvider: ({ children, handlers, }: TranslateHandlersProviderProps) => React.JSX.Element;
145
146
 
146
- type PreKey<M> = NodeKeys<UnionLocaleMessages<M>> & string;
147
- interface SharedMethod<M> {
147
+ declare const PREFIX_PLACEHOLDER = "{locale}";
148
+
149
+ type IfGen<Then, Else = never> = IntorGeneratedTypes extends void ? Else : Then;
150
+ type GenConfigKeys = IfGen<keyof IntorGeneratedTypes, string>;
151
+ type GenConfig<C extends GenConfigKeys = "__default__"> = IntorGeneratedTypes extends void ? {
152
+ Locales: string;
153
+ Messages: LocaleNamespaceMessages;
154
+ } : C extends keyof IntorGeneratedTypes ? {
155
+ Locales: IntorGeneratedTypes[C]["Locales"];
156
+ Messages: {
157
+ [K in IntorGeneratedTypes[C]["Locales"]]: IntorGeneratedTypes[C]["Messages"][typeof PREFIX_PLACEHOLDER];
158
+ };
159
+ } : never;
160
+ type GenMessages<C extends GenConfigKeys = "__default__"> = GenConfig<C>["Messages"];
161
+ type GenLocaleFallback = Locale;
162
+ type GenLocale<Config extends string = "__default__"> = Config extends keyof IntorGeneratedTypes ? IntorGeneratedTypes[Config]["Locales"] : GenLocaleFallback;
163
+
164
+ type PreKey<C extends GenConfigKeys = "__default__"> = NodeKeys<UnionLocaleMessages<GenMessages<C>>>;
165
+ interface TranslatorBaseProps<M> {
148
166
  messages: M;
149
167
  locale: LocaleKey<M>;
168
+ }
169
+ interface TranslatorClientProps<M> {
150
170
  isLoading: boolean;
151
171
  setLocale: (locale: LocaleKey<M>) => void;
152
172
  }
153
- type TranslatorMethods<M> = {
154
- hasKey: (key?: InferTranslatorKey<M> & string, targetLocale?: LocaleKey<M>) => boolean;
155
- t: (key?: InferTranslatorKey<M> & string, replacements?: Replacement | RichReplacement) => string;
156
- } & SharedMethod<M>;
157
- type ScopedTranslatorMethods<M, K extends string> = {
158
- hasKey: (key?: M extends void ? string : ScopedLeafKeys<M, K> & string, targetLocale?: LocaleKey<M>) => boolean;
159
- t: (key?: M extends void ? string : ScopedLeafKeys<M, K> & string, replacements?: Replacement | RichReplacement) => string;
160
- } & SharedMethod<M>;
161
-
162
- type IntorMessages = void;
163
- declare function useIntor<M = IntorMessages>(): TranslatorMethods<M>;
164
- declare function useIntor<M = IntorMessages, K extends PreKey<M> = PreKey<M>>(preKey: M extends void ? string : K): ScopedTranslatorMethods<M, K>;
173
+ type TranslatorInstance<M> = {
174
+ hasKey: (key?: IfGen<InferTranslatorKey<M>, string>, targetLocale?: LocaleKey<M> | undefined) => boolean;
175
+ t: <Result = string>(key?: IfGen<InferTranslatorKey<M>, string>, replacements?: Replacement | RichReplacement) => Result;
176
+ } & TranslatorBaseProps<M> & TranslatorClientProps<M>;
177
+ type ScopedTranslatorInstance<M, K extends string> = {
178
+ hasKey: (key?: IfGen<ScopedLeafKeys<M, K> & string, string>, targetLocale?: LocaleKey<M>) => boolean;
179
+ t: (key?: IfGen<ScopedLeafKeys<M, K> & string, string>, replacements?: Replacement | RichReplacement) => string;
180
+ } & TranslatorBaseProps<M> & TranslatorClientProps<M>;
181
+
182
+ /**
183
+ * React hook to access a ready-to-use translator instance in the client.
184
+ *
185
+ * - Provides `t`, `hasKey`, `messages`, `locale`, `isLoading` and `setLocale`.
186
+ * - Supports optional `preKey` to create a scoped translator for nested keys.
187
+ * - Can accept a generic type parameter `M` to strongly type your messages.
188
+ */
189
+ declare function useTranslator<C extends GenConfigKeys = "__default__">(): TranslatorInstance<GenMessages<C>>;
190
+ declare function useTranslator<C extends GenConfigKeys = "__default__", K extends PreKey<C> = PreKey<C>>(preKey: IfGen<K, string>): ScopedTranslatorInstance<GenMessages<C>, K>;
165
191
 
166
192
  interface LinkProps extends Omit<LinkProps$1, "href">, Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
167
193
  href?: Url;
168
- locale?: string;
194
+ locale?: GenLocale;
169
195
  }
170
196
  /**
171
- * Localized Link component wrapping Next.js Link.
197
+ * Localized Link component
172
198
  *
173
- * If loader type is "import", triggers a full page reload by setting window.location.href.
174
- * Otherwise, it updates the locale in context without a reload.
199
+ * - Wraps Next.js Link and handles locale switching.
200
+ * - Full reload occurs only if the locale changes and requires it; otherwise updates context.
175
201
  */
176
202
  declare const Link: ({ href, locale, children, onClick, ...props }: LinkProps) => React.JSX.Element;
177
203
 
178
204
  /**
179
- * Custom hook to get the localized pathname.
205
+ * usePathname hook
180
206
  *
181
- * It fetches the raw pathname from Next.js,
182
- * then prefixes it with the current locale
183
- * based on the app's configuration and locale context.
207
+ * Wraps Next.js usePathname and returns the current pathname prefixed with the active locale.
184
208
  */
185
209
  declare const usePathname: () => string;
186
210
 
211
+ /**
212
+ * useRouter hook.
213
+ *
214
+ * Wraps Next.js useRouter and provides push/replace methods that automatically switch locale.
215
+ */
187
216
  declare const useRouter: () => {
188
217
  back(): void;
189
218
  forward(): void;
190
219
  refresh(): void;
191
220
  prefetch(href: string, options?: next_dist_shared_lib_app_router_context_shared_runtime.PrefetchOptions): void;
192
221
  push: (href: string, options?: NavigateOptions & {
193
- locale?: Locale;
222
+ locale?: GenLocale;
194
223
  }) => void;
195
224
  replace: (href: string, options?: NavigateOptions & {
196
225
  locale?: Locale;
197
226
  }) => void;
198
227
  };
199
228
 
229
+ /**
230
+ * redirect utility.
231
+ *
232
+ * - Wraps Next.js redirect and applies locale-aware navigation.
233
+ * - Automatically prefixes the pathname with the correct locale.
234
+ * - External URLs are redirected directly without modification.
235
+ */
236
+ declare const redirect: ({ config, locale, url, type, }: {
237
+ config: IntorResolvedConfig;
238
+ locale?: GenLocale;
239
+ url: string;
240
+ type?: RedirectType | undefined;
241
+ }) => Promise<never>;
242
+
200
243
  declare const PATHNAME_HEADER_NAME = "x-intor-pathname";
201
244
 
202
- export { IntorProvider, type IntorProviderProps, Link, PATHNAME_HEADER_NAME, TranslateHandlersProvider, type TranslateHandlersProviderProps, useIntor, usePathname, useRouter };
245
+ export { IntorProvider, type IntorProviderProps, Link, PATHNAME_HEADER_NAME, TranslateHandlersProvider, type TranslateHandlersProviderProps, redirect, usePathname, useRouter, useTranslator };
@@ -3,8 +3,9 @@ import { logry } from 'logry';
3
3
  import Keyv from 'keyv';
4
4
  import { Translator } from 'intor-translator';
5
5
  import NextLink from 'next/link';
6
- import { usePathname as usePathname$1, useRouter as useRouter$1 } from 'next/navigation';
7
6
  import { formatUrl } from 'next/dist/shared/lib/router/utils/format-url';
7
+ import { usePathname as usePathname$1, useRouter as useRouter$1, redirect as redirect$1 } from 'next/navigation';
8
+ import { cookies, headers } from 'next/headers';
8
9
 
9
10
  // src/adapters/next/contexts/intor-provider/intor-provider.tsx
10
11
  var ConfigContext = React7.createContext(void 0);
@@ -104,13 +105,19 @@ var DEFAULT_FORMATTER_CONFIG = {
104
105
  };
105
106
  function getLogger({
106
107
  id,
107
- formatterConfig = DEFAULT_FORMATTER_CONFIG,
108
+ formatterConfig,
109
+ preset,
108
110
  ...options
109
111
  }) {
110
112
  const pool = getGlobalLoggerPool();
111
113
  let logger = pool.get(id);
112
114
  if (!logger) {
113
- logger = logry({ id, formatterConfig, ...options });
115
+ logger = logry({
116
+ id,
117
+ formatterConfig: !formatterConfig && !preset ? DEFAULT_FORMATTER_CONFIG : formatterConfig,
118
+ preset,
119
+ ...options
120
+ });
114
121
  pool.set(id, logger);
115
122
  if (pool.size > 1e3) {
116
123
  const keys = Array.from(pool.keys());
@@ -211,6 +218,55 @@ var resolveNamespaces = ({
211
218
  }
212
219
  };
213
220
 
221
+ // src/shared/utils/locale/normalize-locale.ts
222
+ var normalizeLocale = (locale = "", supportedLocales = []) => {
223
+ if (!locale || supportedLocales.length === 0) return;
224
+ const toCanonical = (input) => {
225
+ try {
226
+ return Intl.getCanonicalLocales(input)[0]?.toLowerCase();
227
+ } catch {
228
+ return;
229
+ }
230
+ };
231
+ const canonicalLocale = toCanonical(locale);
232
+ if (!canonicalLocale) return;
233
+ const supportedCanonicalMap = /* @__PURE__ */ new Map();
234
+ for (const l of supportedLocales) {
235
+ const normalized = toCanonical(l);
236
+ if (normalized) {
237
+ supportedCanonicalMap.set(normalized, l);
238
+ }
239
+ }
240
+ if (supportedCanonicalMap.has(canonicalLocale)) {
241
+ return supportedCanonicalMap.get(canonicalLocale);
242
+ }
243
+ const baseLang = canonicalLocale.split("-")[0];
244
+ for (const [key, original] of supportedCanonicalMap) {
245
+ const supportedBase = key.split("-")[0];
246
+ if (supportedBase === baseLang) {
247
+ return original;
248
+ }
249
+ }
250
+ return;
251
+ };
252
+
253
+ // src/shared/utils/locale/resolve-preferred-locale.ts
254
+ var resolvePreferredLocale = (acceptLanguageHeader, supportedLocales) => {
255
+ if (!acceptLanguageHeader || !supportedLocales || supportedLocales.length === 0) {
256
+ return;
257
+ }
258
+ const supportedLocalesSet = new Set(supportedLocales);
259
+ const preferred = acceptLanguageHeader.split(",").map((part) => {
260
+ const [lang, qValue] = part.split(";");
261
+ const q = qValue ? parseFloat(qValue.split("=")[1]) : 1;
262
+ if (isNaN(q)) {
263
+ return { lang: lang.trim(), q: 0 };
264
+ }
265
+ return { lang: lang.trim(), q };
266
+ }).sort((a, b) => b.q - a.q).find(({ lang }) => supportedLocalesSet.has(lang))?.lang;
267
+ return preferred;
268
+ };
269
+
214
270
  // src/shared/utils/pathname/normalize-pathname.ts
215
271
  var normalizePathname = (rawPathname, options = {}) => {
216
272
  const length = rawPathname.length;
@@ -308,13 +364,13 @@ var fetchMessages = async ({
308
364
  const params = new URLSearchParams(searchParams);
309
365
  params.append("locale", locale);
310
366
  const url = `${apiUrl}?${params.toString()}`;
311
- const headers = {
367
+ const headers2 = {
312
368
  "Content-Type": "application/json",
313
369
  ...apiHeaders
314
370
  };
315
371
  const response = await fetch(url, {
316
372
  method: "GET",
317
- headers,
373
+ headers: headers2,
318
374
  cache: "no-store"
319
375
  });
320
376
  if (!response.ok) {
@@ -394,7 +450,10 @@ var loadApiMessages = async ({
394
450
  logger.warn("No apiUrl provided. Skipping fetch.");
395
451
  return;
396
452
  }
397
- const pool = getGlobalMessagesPool();
453
+ let pool;
454
+ if (cache.enabled) {
455
+ pool = getGlobalMessagesPool();
456
+ }
398
457
  const key = normalizeCacheKey([
399
458
  loggerOptions.id,
400
459
  basePath,
@@ -403,7 +462,7 @@ var loadApiMessages = async ({
403
462
  [...namespaces ?? []].sort().join(",")
404
463
  ]);
405
464
  if (cache.enabled && key) {
406
- const cached = await pool.get(key);
465
+ const cached = await pool?.get(key);
407
466
  if (cached) {
408
467
  logger.debug("Messages cache hit.", { key });
409
468
  return cached;
@@ -419,7 +478,7 @@ var loadApiMessages = async ({
419
478
  });
420
479
  if (messages) {
421
480
  if (cache.enabled && key) {
422
- await pool.set(key, messages, cache.ttl);
481
+ await pool?.set(key, messages, cache.ttl);
423
482
  }
424
483
  return messages;
425
484
  }
@@ -437,7 +496,7 @@ var loadApiMessages = async ({
437
496
  searchParams: decodeURIComponent(searchParams.toString())
438
497
  });
439
498
  if (cache.enabled && key) {
440
- await pool.set(key, fallbackResult.messages, cache.ttl);
499
+ await pool?.set(key, fallbackResult.messages, cache.ttl);
441
500
  }
442
501
  return fallbackResult.messages;
443
502
  }
@@ -543,8 +602,8 @@ var useInitLocaleCookie = ({
543
602
  if (typeof document === "undefined") return;
544
603
  const { cookie, routing } = config;
545
604
  const { firstVisit } = routing;
546
- const cookies = document.cookie.split(";").map((c) => c.trim());
547
- const isCookieExists = cookies.some((c) => c.startsWith(`${cookie.name}=`));
605
+ const cookies2 = document.cookie.split(";").map((c) => c.trim());
606
+ const isCookieExists = cookies2.some((c) => c.startsWith(`${cookie.name}=`));
548
607
  if (isCookieExists) return;
549
608
  if (!firstVisit.redirect) return;
550
609
  if (cookie.disabled || !cookie.autoSetCookie) return;
@@ -663,11 +722,11 @@ var IntorProvider = ({
663
722
  return /* @__PURE__ */ React7.createElement(ConfigProvider, { value: { config, pathname } }, /* @__PURE__ */ React7.createElement(MessagesProvider, { value: { messages } }, /* @__PURE__ */ React7.createElement(LocaleProvider, { value: { initialLocale } }, /* @__PURE__ */ React7.createElement(TranslatorProvider, null, children))));
664
723
  };
665
724
 
666
- // src/adapters/next/hooks/use-intor/use-intor.ts
667
- function useIntor(preKey) {
725
+ // src/adapters/next/hooks/use-translator/use-translator.ts
726
+ function useTranslator2(preKey) {
668
727
  const { translator } = useTranslator();
669
728
  const { setLocale } = useLocale();
670
- const sharedMethod = {
729
+ const props = {
671
730
  messages: translator.messages,
672
731
  locale: translator.locale,
673
732
  isLoading: translator.isLoading,
@@ -675,10 +734,10 @@ function useIntor(preKey) {
675
734
  };
676
735
  if (preKey) {
677
736
  const { hasKey, t } = translator.scoped(preKey);
678
- return { ...sharedMethod, hasKey, t };
737
+ return { ...props, hasKey, t };
679
738
  } else {
680
739
  const { hasKey, t } = translator;
681
- return { ...sharedMethod, hasKey, t };
740
+ return { ...props, hasKey, t };
682
741
  }
683
742
  }
684
743
 
@@ -740,10 +799,7 @@ var localizePathname = ({
740
799
  };
741
800
  };
742
801
 
743
- // src/adapters/next/tools/utils/should-full-reload.ts
744
- var shouldFullReload = (loaderOptions) => {
745
- return loaderOptions?.type === "import" || loaderOptions?.type === "api" && loaderOptions.fullReload === true;
746
- };
802
+ // src/adapters/next/navigation/use-pathname.ts
747
803
  var usePathname = () => {
748
804
  const { config } = useConfig();
749
805
  const { locale } = useLocale();
@@ -755,75 +811,90 @@ var usePathname = () => {
755
811
  });
756
812
  return localePrefixedPathname;
757
813
  };
758
- var Link = ({
759
- href,
814
+
815
+ // src/adapters/next/navigation/utils/should-full-reload.ts
816
+ var shouldFullReload = ({
817
+ config,
818
+ targetPathname,
760
819
  locale,
761
- children,
762
- onClick,
763
- ...props
820
+ currentLocale
764
821
  }) => {
822
+ const loader = config.loader;
823
+ if (!loader || !loader.type) return false;
824
+ if (loader.type === "api" && !loader.fullReload) return false;
825
+ const { maybeLocale, isLocalePrefixed } = extractPathname({
826
+ config,
827
+ pathname: targetPathname
828
+ });
829
+ const isDifferentLocale = locale && locale !== currentLocale || isLocalePrefixed && maybeLocale !== currentLocale;
830
+ return isDifferentLocale ? true : false;
831
+ };
832
+
833
+ // src/adapters/next/navigation/utils/use-locale-switch.ts
834
+ var useLocaleSwitch = () => {
765
835
  const { config } = useConfig();
766
836
  const { locale: currentLocale, setLocale } = useLocale();
767
- const targetLocale = locale || currentLocale;
768
837
  const pathname = usePathname();
769
- const formattedUrl = href ? typeof href === "string" ? href : formatUrl(href) : void 0;
770
- const { localePrefixedPathname } = localizePathname({
771
- config,
772
- pathname: formattedUrl ?? pathname,
773
- locale: targetLocale
774
- });
775
- const resolvedHref = localePrefixedPathname;
776
- const handleClick = (e) => {
777
- onClick?.(e);
778
- if (shouldFullReload(config.loader)) {
838
+ const resolveHref = ({
839
+ href,
840
+ locale
841
+ }) => {
842
+ const isLocaleValid = locale && config.supportedLocales?.includes(locale);
843
+ const targetLocale = isLocaleValid ? locale : currentLocale;
844
+ const targetPathname = href ?? pathname;
845
+ const isExternal = targetPathname.startsWith("http");
846
+ const resolvedHref = !isExternal ? localizePathname({
847
+ config,
848
+ pathname: targetPathname,
849
+ locale: targetLocale
850
+ }).localePrefixedPathname : targetPathname;
851
+ return { resolvedHref, isExternal, targetLocale, targetPathname };
852
+ };
853
+ const switchLocale = ({
854
+ href,
855
+ locale
856
+ }) => {
857
+ const { resolvedHref, isExternal, targetLocale, targetPathname } = resolveHref({ href, locale });
858
+ if (isExternal) return;
859
+ if (shouldFullReload({ config, targetPathname, locale, currentLocale })) {
779
860
  setLocaleCookieBrowser({ cookie: config.cookie, locale: targetLocale });
780
861
  window.location.href = resolvedHref;
781
862
  return;
782
863
  } else {
783
864
  setLocale(targetLocale);
784
865
  }
866
+ return resolvedHref;
867
+ };
868
+ return { resolveHref, switchLocale };
869
+ };
870
+
871
+ // src/adapters/next/navigation/link.tsx
872
+ var Link = ({
873
+ href,
874
+ locale,
875
+ children,
876
+ onClick,
877
+ ...props
878
+ }) => {
879
+ const formatted = typeof href === "string" ? href : href ? formatUrl(href) : void 0;
880
+ const { resolveHref, switchLocale } = useLocaleSwitch();
881
+ const { resolvedHref } = resolveHref({ href: formatted, locale });
882
+ const handleClick = (e) => {
883
+ onClick?.(e);
884
+ switchLocale({ href: formatted, locale });
785
885
  };
786
886
  return /* @__PURE__ */ React7.createElement(NextLink, { href: resolvedHref, onClick: handleClick, ...props }, children);
787
887
  };
788
888
  var useRouter = () => {
789
- const { config } = useConfig();
790
- const { locale: currentLocale, setLocale } = useLocale();
791
889
  const { push, replace, ...rest } = useRouter$1();
890
+ const { switchLocale } = useLocaleSwitch();
792
891
  const pushWithLocale = (href, options) => {
793
- const targetLocale = options?.locale || currentLocale;
794
- if (options?.locale) {
795
- const { localePrefixedPathname } = localizePathname({
796
- config,
797
- pathname: href,
798
- locale: targetLocale
799
- });
800
- href = localePrefixedPathname;
801
- }
802
- if (shouldFullReload(config.loader)) {
803
- window.location.href = href;
804
- return;
805
- } else {
806
- setLocale(targetLocale);
807
- }
808
- push(href, options);
892
+ const resolvedHref = switchLocale({ href, locale: options?.locale });
893
+ if (resolvedHref) push(resolvedHref, options);
809
894
  };
810
895
  const replaceWithLocale = (href, options) => {
811
- const targetLocale = options?.locale || currentLocale;
812
- if (options?.locale) {
813
- const { localePrefixedPathname } = localizePathname({
814
- config,
815
- pathname: href,
816
- locale: targetLocale
817
- });
818
- href = localePrefixedPathname;
819
- }
820
- if (shouldFullReload(config.loader)) {
821
- window.location.href = href;
822
- return;
823
- } else {
824
- setLocale(targetLocale);
825
- }
826
- replace(href, options);
896
+ const resolvedHref = switchLocale({ href, locale: options?.locale });
897
+ if (resolvedHref) replace(resolvedHref, options);
827
898
  };
828
899
  return {
829
900
  push: pushWithLocale,
@@ -835,4 +906,55 @@ var useRouter = () => {
835
906
  // src/adapters/next/shared/constants/pathname-header-name.ts
836
907
  var PATHNAME_HEADER_NAME = "x-intor-pathname";
837
908
 
838
- export { IntorProvider, Link, PATHNAME_HEADER_NAME, TranslateHandlersProvider, useIntor, usePathname, useRouter };
909
+ // src/adapters/next/server/get-i18n-context.ts
910
+ var getI18nContext = async (config) => {
911
+ const baseLogger = getLogger({ id: config.id, ...config.logger });
912
+ const logger = baseLogger.child({ scope: "next-adapter" });
913
+ const cookiesStore = await cookies();
914
+ const headersStore = await headers();
915
+ const { defaultLocale, supportedLocales = [], cookie, routing } = config;
916
+ let locale;
917
+ if (!cookie.disabled) {
918
+ const localeFromCookie = cookiesStore.get(cookie.name)?.value;
919
+ locale = normalizeLocale(localeFromCookie, supportedLocales);
920
+ if (locale) {
921
+ logger.trace("Locale retrieved from cookie.", { locale });
922
+ }
923
+ }
924
+ if (!locale && routing.firstVisit.localeSource === "browser") {
925
+ const aLHeader = headersStore.get("accept-language") || void 0;
926
+ const preferredLocale = resolvePreferredLocale(aLHeader, supportedLocales);
927
+ locale = normalizeLocale(preferredLocale, supportedLocales);
928
+ logger.trace("Locale retrieved from header.", { locale });
929
+ }
930
+ const pathname = headersStore.get(PATHNAME_HEADER_NAME);
931
+ if (pathname) {
932
+ logger.trace("Pathname retrieved from header.", { pathname });
933
+ }
934
+ return {
935
+ locale: locale || defaultLocale,
936
+ pathname: pathname || ""
937
+ };
938
+ };
939
+
940
+ // src/adapters/next/navigation/redirect.ts
941
+ var redirect = async ({
942
+ config,
943
+ locale,
944
+ url,
945
+ type
946
+ }) => {
947
+ if (url.startsWith("http")) {
948
+ redirect$1(url);
949
+ }
950
+ const isLocaleValid = locale && config.supportedLocales?.includes(locale);
951
+ const { locale: detectedLocale } = await getI18nContext(config);
952
+ const { localePrefixedPathname } = localizePathname({
953
+ config,
954
+ pathname: url,
955
+ locale: isLocaleValid ? locale : detectedLocale
956
+ });
957
+ redirect$1(localePrefixedPathname, type);
958
+ };
959
+
960
+ export { IntorProvider, Link, PATHNAME_HEADER_NAME, TranslateHandlersProvider, redirect, usePathname, useRouter, useTranslator2 as useTranslator };
@@ -266,7 +266,10 @@ var createResponse = ({
266
266
  override
267
267
  });
268
268
  }
269
- const finalResponse = setPathnameHeader({ request, response });
269
+ const finalResponse = setPathnameHeader({
270
+ request,
271
+ response
272
+ });
270
273
  return finalResponse;
271
274
  };
272
275
  var determineInitialLocale = async (config) => {