next-intl 4.0.3 → 4.2.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.
@@ -40,8 +40,10 @@ function createSharedNavigationFns(getLocale, routing) {
40
40
  href: pathnames == null ? pathname : {
41
41
  pathname,
42
42
  params
43
- }
44
- }, locale != null || undefined) : pathname;
43
+ },
44
+ // Always include a prefix when changing locales
45
+ forcePrefix: locale != null || undefined
46
+ }) : pathname;
45
47
  return /*#__PURE__*/jsx(BaseLink, {
46
48
  ref: ref
47
49
  // @ts-expect-error -- This is ok
@@ -56,9 +58,9 @@ function createSharedNavigationFns(getLocale, routing) {
56
58
  });
57
59
  }
58
60
  const LinkWithRef = /*#__PURE__*/forwardRef(Link);
59
- function getPathname(args, /** @private Removed in types returned below */
60
- _forcePrefix) {
61
+ function getPathname(args) {
61
62
  const {
63
+ forcePrefix,
62
64
  href,
63
65
  locale
64
66
  } = args;
@@ -81,7 +83,7 @@ function createSharedNavigationFns(getLocale, routing) {
81
83
  pathnames: config.pathnames
82
84
  });
83
85
  }
84
- return applyPathnamePrefix(pathname, locale, config, _forcePrefix);
86
+ return applyPathnamePrefix(pathname, locale, config, forcePrefix);
85
87
  }
86
88
  function getRedirectFn(fn) {
87
89
  /** @see https://next-intl.dev/docs/routing/navigation#redirect */
@@ -96,8 +98,7 @@ function createSharedNavigationFns(getLocale, routing) {
96
98
  Link: LinkWithRef,
97
99
  redirect: redirect$1,
98
100
  permanentRedirect: permanentRedirect$1,
99
- // Remove `_forcePrefix` from public API
100
- getPathname: getPathname
101
+ getPathname
101
102
  };
102
103
  }
103
104
 
@@ -62,12 +62,15 @@ function compileLocalizedPathname({
62
62
 
63
63
  // Clean up optional catch-all segments that were not replaced
64
64
  compiled = compiled.replace(/\[\[\.\.\..+\]\]/g, '');
65
- compiled = normalizeTrailingSlash(compiled);
66
65
  if (compiled.includes('[')) {
67
66
  // Next.js throws anyway, therefore better provide a more helpful error message
68
67
  throw new Error(`Insufficient params provided for localized pathname.\nTemplate: ${template}\nParams: ${JSON.stringify(params)}`);
69
68
  }
69
+ compiled = normalizeTrailingSlash(compiled);
70
+ compiled = encodePathname(compiled);
70
71
  if (query) {
72
+ // This also encodes non-ASCII characters by
73
+ // using `new URLSearchParams()` internally
71
74
  compiled += serializeSearchParams(query);
72
75
  }
73
76
  return compiled;
@@ -90,6 +93,29 @@ function compileLocalizedPathname({
90
93
  return result;
91
94
  }
92
95
  }
96
+ function encodePathname(pathname) {
97
+ // Generally, to comply with RFC 3986 and Google's best practices for URL structures
98
+ // (https://developers.google.com/search/docs/crawling-indexing/url-structure),
99
+ // we should always encode non-ASCII characters.
100
+ //
101
+ // There are two places where next-intl interacts with potentially non-ASCII URLs:
102
+ // 1. Middleware: When mapping a localized pathname to a non-localized pathname internally
103
+ // 2. Navigation APIs: When generating a URLs to be used for <Link /> & friends
104
+ //
105
+ // Next.js normalizes incoming pathnames to always be encoded, therefore we can safely
106
+ // decode them there (see middleware.tsx). On the other hand, Next.js doesn't consistently
107
+ // encode non-ASCII characters that are passed to navigation APIs:
108
+ // 1. <Link /> doesn't encode non-ASCII characters
109
+ // 2. useRouter() uses `new URL()` internally, which will encode—but only if necessary
110
+ // 3. redirect() uses useRouter() on the client, but on the server side only
111
+ // assigns the location header without encoding.
112
+ //
113
+ // In addition to this, for getPathname() we need to encode non-ASCII characters.
114
+ //
115
+ // Therefore, the bottom line is that next-intl should take care of encoding non-ASCII
116
+ // characters in all cases, but can rely on `new URL()` to not double-encode characters.
117
+ return pathname.split('/').map(segment => encodeURIComponent(segment)).join('/');
118
+ }
93
119
  function getRoute(locale, pathname, pathnames) {
94
120
  const sortedPathnames = getSortedPathnames(Object.keys(pathnames));
95
121
  const decoded = decodeURI(pathname);
@@ -1 +1 @@
1
- import{redirect as e,permanentRedirect as t}from"next/navigation";import{forwardRef as o}from"react";import{receiveRoutingConfig as r}from"../../routing/config.js";import n from"../../shared/use.js";import{isLocalizableHref as a,isPromise as i}from"../../shared/utils.js";import m from"./BaseLink.js";import{serializeSearchParams as c,compileLocalizedPathname as l,applyPathnamePrefix as f,normalizeNameOrNameWithParams as s}from"./utils.js";import{jsx as p}from"react/jsx-runtime";function u(u,h){const j=r(h||{}),d=j.pathnames;function g({href:e,locale:t,...o},r){let c,l;"object"==typeof e?(c=e.pathname,l=e.params):c=e;const f=a(e),s=u(),h=i(s)?n(s):s,g=f?k({locale:t||h,href:null==d?c:{pathname:c,params:l}},null!=t||void 0):c;return p(m,{ref:r,href:"object"==typeof e?{...e,pathname:g}:g,locale:t,localeCookie:j.localeCookie,...o})}const y=o(g);function k(e,t){const{href:o,locale:r}=e;let n;return null==d?"object"==typeof o?(n=o.pathname,o.query&&(n+=c(o.query))):n=o:n=l({locale:r,...s(o),pathnames:j.pathnames}),f(n,r,j,t)}function b(e){return function(t,...o){return e(k(t),...o)}}const x=b(e),q=b(t);return{config:j,Link:y,redirect:x,permanentRedirect:q,getPathname:k}}export{u as default};
1
+ import{redirect as e,permanentRedirect as t}from"next/navigation";import{forwardRef as o}from"react";import{receiveRoutingConfig as r}from"../../routing/config.js";import n from"../../shared/use.js";import{isLocalizableHref as a,isPromise as i}from"../../shared/utils.js";import c from"./BaseLink.js";import{serializeSearchParams as f,compileLocalizedPathname as m,applyPathnamePrefix as l,normalizeNameOrNameWithParams as s}from"./utils.js";import{jsx as p}from"react/jsx-runtime";function u(u,h){const j=r(h||{}),d=j.pathnames;function g({href:e,locale:t,...o},r){let f,m;"object"==typeof e?(f=e.pathname,m=e.params):f=e;const l=a(e),s=u(),h=i(s)?n(s):s,g=l?y({locale:t||h,href:null==d?f:{pathname:f,params:m},forcePrefix:null!=t||void 0}):f;return p(c,{ref:r,href:"object"==typeof e?{...e,pathname:g}:g,locale:t,localeCookie:j.localeCookie,...o})}const x=o(g);function y(e){const{forcePrefix:t,href:o,locale:r}=e;let n;return null==d?"object"==typeof o?(n=o.pathname,o.query&&(n+=f(o.query))):n=o:n=m({locale:r,...s(o),pathnames:j.pathnames}),l(n,r,j,t)}function k(e){return function(t,...o){return e(y(t),...o)}}const b=k(e),P=k(t);return{config:j,Link:x,redirect:b,permanentRedirect:P,getPathname:y}}export{u as default};
@@ -1 +1 @@
1
- import{getSortedPathnames as e,matchesPathname as n,isLocalizableHref as t,prefixPathname as r,getLocalizedTemplate as o,normalizeTrailingSlash as a,getLocalePrefix as i}from"../../shared/utils.js";function c(e){return"string"==typeof e?{pathname:e}:e}function s(e){function n(e){return String(e)}const t=new URLSearchParams;for(const[r,o]of Object.entries(e))Array.isArray(o)?o.forEach((e=>{t.append(r,n(e))})):t.set(r,n(o));return"?"+t.toString()}function f({pathname:e,locale:n,params:t,pathnames:r,query:i}){function c(e){let n=r[e];return n||(n=e),n}function f(e,r){let c=o(e,n,r);return t&&Object.entries(t).forEach((([e,n])=>{let t,r;Array.isArray(n)?(t=`(\\[)?\\[...${e}\\](\\])?`,r=n.map((e=>String(e))).join("/")):(t=`\\[${e}\\]`,r=String(n)),c=c.replace(new RegExp(t,"g"),r)})),c=c.replace(/\[\[\.\.\..+\]\]/g,""),c=a(c),i&&(c+=s(i)),c}if("string"==typeof e){return f(c(e),e)}{const{pathname:n,...t}=e;return{...t,pathname:f(c(n),n)}}}function u(t,r,a){const i=e(Object.keys(a)),c=decodeURI(r);for(const e of i){const r=a[e];if("string"==typeof r){if(n(r,c))return e}else if(n(o(r,t,e),c))return e}return r}function l(e,n=window.location.pathname){return"/"===e?n:n.replace(e,"")}function p(e,n,o,a){const{mode:c}=o.localePrefix;let s;return void 0!==a?s=a:t(e)&&("always"===c?s=!0:"as-needed"===c&&(s=o.domains?!o.domains.some((e=>e.defaultLocale===n)):n!==o.defaultLocale)),s?r(i(n,o.localePrefix),e):e}export{p as applyPathnamePrefix,f as compileLocalizedPathname,l as getBasePath,u as getRoute,c as normalizeNameOrNameWithParams,s as serializeSearchParams};
1
+ import{getSortedPathnames as e,matchesPathname as n,isLocalizableHref as t,prefixPathname as r,getLocalizedTemplate as o,normalizeTrailingSlash as a,getLocalePrefix as i}from"../../shared/utils.js";function c(e){return"string"==typeof e?{pathname:e}:e}function s(e){function n(e){return String(e)}const t=new URLSearchParams;for(const[r,o]of Object.entries(e))Array.isArray(o)?o.forEach((e=>{t.append(r,n(e))})):t.set(r,n(o));return"?"+t.toString()}function f({pathname:e,locale:n,params:t,pathnames:r,query:i}){function c(e){let n=r[e];return n||(n=e),n}function f(e,r){let c=o(e,n,r);return t&&Object.entries(t).forEach((([e,n])=>{let t,r;Array.isArray(n)?(t=`(\\[)?\\[...${e}\\](\\])?`,r=n.map((e=>String(e))).join("/")):(t=`\\[${e}\\]`,r=String(n)),c=c.replace(new RegExp(t,"g"),r)})),c=c.replace(/\[\[\.\.\..+\]\]/g,""),c=a(c),c=function(e){return e.split("/").map((e=>encodeURIComponent(e))).join("/")}(c),i&&(c+=s(i)),c}if("string"==typeof e){return f(c(e),e)}{const{pathname:n,...t}=e;return{...t,pathname:f(c(n),n)}}}function u(t,r,a){const i=e(Object.keys(a)),c=decodeURI(r);for(const e of i){const r=a[e];if("string"==typeof r){if(n(r,c))return e}else if(n(o(r,t,e),c))return e}return r}function l(e,n=window.location.pathname){return"/"===e?n:n.replace(e,"")}function p(e,n,o,a){const{mode:c}=o.localePrefix;let s;return void 0!==a?s=a:t(e)&&("always"===c?s=!0:"as-needed"===c&&(s=o.domains?!o.domains.some((e=>e.defaultLocale===n)):n!==o.defaultLocale)),s?r(i(n,o.localePrefix),e):e}export{p as applyPathnamePrefix,f as compileLocalizedPathname,l as getBasePath,u as getRoute,c as normalizeNameOrNameWithParams,s as serializeSearchParams};
@@ -335,6 +335,7 @@ export default function createNavigation<const AppLocales extends Locales, const
335
335
  query?: import("../shared/utils.js").QueryParams;
336
336
  }) : never : never;
337
337
  locale: Locale;
338
+ forcePrefix?: boolean;
338
339
  }) => string>[0]["href"], options?: (Partial<import("next/dist/shared/lib/app-router-context.shared-runtime.js").NavigateOptions> & {
339
340
  locale?: Locale;
340
341
  }) | undefined) => void;
@@ -359,6 +360,7 @@ export default function createNavigation<const AppLocales extends Locales, const
359
360
  query?: import("../shared/utils.js").QueryParams;
360
361
  }) : never : never;
361
362
  locale: Locale;
363
+ forcePrefix?: boolean;
362
364
  }) => string>[0]["href"], options?: (Partial<import("next/dist/shared/lib/app-router-context.shared-runtime.js").NavigateOptions> & {
363
365
  locale?: Locale;
364
366
  }) | undefined) => void;
@@ -383,6 +385,7 @@ export default function createNavigation<const AppLocales extends Locales, const
383
385
  query?: import("../shared/utils.js").QueryParams;
384
386
  }) : never : never;
385
387
  locale: Locale;
388
+ forcePrefix?: boolean;
386
389
  }) => string>[0]["href"], options?: (Partial<import("next/dist/shared/lib/app-router-context.shared-runtime.js").PrefetchOptions> & {
387
390
  locale?: Locale;
388
391
  }) | undefined) => void;
@@ -410,8 +413,9 @@ export default function createNavigation<const AppLocales extends Locales, const
410
413
  query?: import("../shared/utils.js").QueryParams;
411
414
  }) : never : never;
412
415
  locale: Locale;
416
+ forcePrefix?: boolean;
413
417
  }) => string;
414
- redirect: (args: Omit<{
418
+ redirect: (args: {
415
419
  href: [AppPathnames] extends [never] ? string | {
416
420
  pathname: string;
417
421
  query?: import("../shared/utils.js").QueryParams;
@@ -431,8 +435,9 @@ export default function createNavigation<const AppLocales extends Locales, const
431
435
  query?: import("../shared/utils.js").QueryParams;
432
436
  }) : never : never;
433
437
  locale: Locale;
434
- }, "domain">, type?: import("next/navigation.js").RedirectType | undefined) => never;
435
- permanentRedirect: (args: Omit<{
438
+ forcePrefix?: boolean;
439
+ }, type?: import("next/navigation.js").RedirectType | undefined) => never;
440
+ permanentRedirect: (args: {
436
441
  href: [AppPathnames] extends [never] ? string | {
437
442
  pathname: string;
438
443
  query?: import("../shared/utils.js").QueryParams;
@@ -452,5 +457,6 @@ export default function createNavigation<const AppLocales extends Locales, const
452
457
  query?: import("../shared/utils.js").QueryParams;
453
458
  }) : never : never;
454
459
  locale: Locale;
455
- }, "domain">, type?: import("next/navigation.js").RedirectType | undefined) => never;
460
+ forcePrefix?: boolean;
461
+ }, type?: import("next/navigation.js").RedirectType | undefined) => never;
456
462
  };
@@ -313,7 +313,7 @@ export default function createNavigation<const AppLocales extends Locales, const
313
313
  pathname: T;
314
314
  } & Omit<import("url").UrlObject, "pathname">) : never : never;
315
315
  }, "ref"> & import("react").RefAttributes<HTMLAnchorElement>>;
316
- redirect: (args: Omit<{
316
+ redirect: (args: {
317
317
  href: [AppPathnames] extends [never] ? string | {
318
318
  pathname: string;
319
319
  query?: import("../shared/utils.js").QueryParams;
@@ -333,8 +333,9 @@ export default function createNavigation<const AppLocales extends Locales, const
333
333
  query?: import("../shared/utils.js").QueryParams;
334
334
  }) : never : never;
335
335
  locale: import("use-intl").Locale;
336
- }, "domain">, type?: import("next/navigation.js").RedirectType | undefined) => never;
337
- permanentRedirect: (args: Omit<{
336
+ forcePrefix?: boolean;
337
+ }, type?: import("next/navigation.js").RedirectType | undefined) => never;
338
+ permanentRedirect: (args: {
338
339
  href: [AppPathnames] extends [never] ? string | {
339
340
  pathname: string;
340
341
  query?: import("../shared/utils.js").QueryParams;
@@ -354,7 +355,8 @@ export default function createNavigation<const AppLocales extends Locales, const
354
355
  query?: import("../shared/utils.js").QueryParams;
355
356
  }) : never : never;
356
357
  locale: import("use-intl").Locale;
357
- }, "domain">, type?: import("next/navigation.js").RedirectType | undefined) => never;
358
+ forcePrefix?: boolean;
359
+ }, type?: import("next/navigation.js").RedirectType | undefined) => never;
358
360
  getPathname: (args: {
359
361
  href: [AppPathnames] extends [never] ? string | {
360
362
  pathname: string;
@@ -375,5 +377,6 @@ export default function createNavigation<const AppLocales extends Locales, const
375
377
  query?: import("../shared/utils.js").QueryParams;
376
378
  }) : never : never;
377
379
  locale: import("use-intl").Locale;
380
+ forcePrefix?: boolean;
378
381
  }) => string;
379
382
  };
@@ -331,29 +331,38 @@ export default function createSharedNavigationFns<const AppLocales extends Local
331
331
  pathname: T;
332
332
  } & Omit<import("url").UrlObject, "pathname">) : never : never;
333
333
  }, "ref"> & import("react").RefAttributes<HTMLAnchorElement>>;
334
- redirect: (args: Omit<Parameters<(args: {
334
+ redirect: (args: Parameters<(args: {
335
335
  /** @see https://next-intl.dev/docs/routing/navigation#getpathname */
336
336
  href: [AppPathnames] extends [never] ? string | {
337
337
  pathname: string;
338
338
  query?: QueryParams;
339
339
  } : HrefOrHrefWithParams<keyof AppPathnames>;
340
+ /** The locale to compute the pathname for. */
340
341
  locale: Locale;
341
- }, _forcePrefix?: boolean) => string>[0], "domain">, type?: import("next/navigation.js").RedirectType | undefined) => never;
342
- permanentRedirect: (args: Omit<Parameters<(args: {
342
+ /** Will prepend the pathname with the locale prefix, regardless of your `localePrefix` setting. This can be helpful to update a locale cookie when changing locales. */
343
+ forcePrefix?: boolean;
344
+ }) => string>[0], type?: import("next/navigation.js").RedirectType | undefined) => never;
345
+ permanentRedirect: (args: Parameters<(args: {
343
346
  /** @see https://next-intl.dev/docs/routing/navigation#getpathname */
344
347
  href: [AppPathnames] extends [never] ? string | {
345
348
  pathname: string;
346
349
  query?: QueryParams;
347
350
  } : HrefOrHrefWithParams<keyof AppPathnames>;
351
+ /** The locale to compute the pathname for. */
348
352
  locale: Locale;
349
- }, _forcePrefix?: boolean) => string>[0], "domain">, type?: import("next/navigation.js").RedirectType | undefined) => never;
350
- getPathname: (args: Parameters<(args: {
353
+ /** Will prepend the pathname with the locale prefix, regardless of your `localePrefix` setting. This can be helpful to update a locale cookie when changing locales. */
354
+ forcePrefix?: boolean;
355
+ }) => string>[0], type?: import("next/navigation.js").RedirectType | undefined) => never;
356
+ getPathname: (args: {
351
357
  /** @see https://next-intl.dev/docs/routing/navigation#getpathname */
352
358
  href: [AppPathnames] extends [never] ? string | {
353
359
  pathname: string;
354
360
  query?: QueryParams;
355
361
  } : HrefOrHrefWithParams<keyof AppPathnames>;
362
+ /** The locale to compute the pathname for. */
356
363
  locale: Locale;
357
- }, _forcePrefix?: boolean) => string>[0]) => string;
364
+ /** Will prepend the pathname with the locale prefix, regardless of your `localePrefix` setting. This can be helpful to update a locale cookie when changing locales. */
365
+ forcePrefix?: boolean;
366
+ }) => string;
358
367
  };
359
368
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-intl",
3
- "version": "4.0.3",
3
+ "version": "4.2.0",
4
4
  "sideEffects": false,
5
5
  "author": "Jan Amann <jan@amann.work>",
6
6
  "funding": [
@@ -112,7 +112,7 @@
112
112
  "dependencies": {
113
113
  "@formatjs/intl-localematcher": "^0.5.4",
114
114
  "negotiator": "^1.0.0",
115
- "use-intl": "^4.0.3"
115
+ "use-intl": "^4.2.0"
116
116
  },
117
117
  "peerDependencies": {
118
118
  "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0",
@@ -124,5 +124,5 @@
124
124
  "optional": true
125
125
  }
126
126
  },
127
- "gitHead": "19c27a23384fdf746fde30bf3919c2fd8c0fd568"
127
+ "gitHead": "e303923f7339356cf2c1d64adcdc9498531d8db8"
128
128
  }