Skip to content

Commit cb6b998

Browse files
committed
Merge remote-tracking branch 'origin/main' into v4
# Conflicts: # packages/next-intl/.size-limit.ts # packages/next-intl/src/navigation/react-client/createLocalizedPathnamesNavigation.tsx # packages/next-intl/src/navigation/react-client/createNavigation.tsx # packages/next-intl/src/navigation/react-client/createSharedPathnamesNavigation.tsx # packages/next-intl/src/server/react-client/index.test.tsx
2 parents 21b882a + 55a243a commit cb6b998

19 files changed

+129
-44
lines changed

.github/workflows/main.yml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jobs:
1111
env:
1212
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
1313
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
14+
TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && (github.event.pull_request.base.sha || github.event.before) }}
1415
steps:
1516
# General setup
1617
- uses: actions/checkout@v4

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## 3.25.3 (2024-11-26)
7+
8+
### Bug Fixes
9+
10+
* Follow-up for [#1573](https://github.com/amannn/next-intl/issues/1573) to also handle the case when a non-default locale is in use ([#1578](https://github.com/amannn/next-intl/issues/1578)) ([fd71741](https://github.com/amannn/next-intl/commit/fd7174179881a19e3573fceb9c6e903923644761)), closes [#1568](https://github.com/amannn/next-intl/issues/1568) – by @amannn
11+
12+
## 3.25.2 (2024-11-25)
13+
14+
### Bug Fixes
15+
16+
* Handle inconsistency in Next.js when using `usePathname` with custom prefixes, `localePrefix: 'as-needed'` and static rendering ([#1573](https://github.com/amannn/next-intl/issues/1573)) ([20fd0f0](https://github.com/amannn/next-intl/commit/20fd0f0015839357893bcd256ff880a98b01ea1f)) – by @amannn
17+
618
## 3.25.1 (2024-11-13)
719

820
### Bug Fixes

docs/src/components/Analytics.tsx

-5
This file was deleted.

docs/src/pages/_app.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import {Analytics} from '@vercel/analytics/next';
12
import {SpeedInsights} from '@vercel/speed-insights/next';
23
import {AppProps} from 'next/app';
34
import {Inter} from 'next/font/google';
45
import {ReactNode} from 'react';
5-
import Analytics from '@/components/Analytics';
66
import 'nextra-theme-docs/style.css';
77
import '../styles.css';
88

@@ -17,8 +17,8 @@ export default function App({Component, pageProps}: Props) {
1717
return (
1818
<div className={inter.className}>
1919
{getLayout(<Component {...pageProps} />)}
20-
<Analytics />
2120
<SpeedInsights />
21+
<Analytics />
2222
</div>
2323
);
2424
}

docs/src/pages/docs/getting-started/app-router/with-i18n-routing.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export const routing = defineRouting({
119119

120120
// Lightweight wrappers around Next.js' navigation APIs
121121
// that will consider the routing configuration
122-
export const {Link, redirect, usePathname, useRouter} =
122+
export const {Link, redirect, usePathname, useRouter, getPathname} =
123123
createNavigation(routing);
124124
```
125125

docs/src/pages/docs/routing.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const routing = defineRouting({
8383
});
8484
```
8585

86-
Additionally, you should adapt your middleware matcher to detect [unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix) for this routing strategy to work as expected.
86+
**Important**: For this routing strategy to work as expected, you should additionally adapt your middleware matcher to detect [unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix).
8787

8888
Note that if a superfluous locale prefix like `/en/about` is requested, the middleware will automatically redirect to the unprefixed version `/about`. This can be helpful in case you're redirecting from another locale and you want to update a potential cookie value first (e.g. [`<Link />`](/docs/routing/navigation#link) relies on this mechanism).
8989

docs/src/pages/docs/routing/middleware.mdx

+2-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ This enables:
123123
<Details id="matcher-avoid-hardcoding">
124124
<summary>Can I avoid hardcoding the locales in the `matcher` config?</summary>
125125

126-
A [Next.js `matcher`](https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher) needs to be statically analyzable, therefore you can't use variables to generate this value dynamically. However, you can implement the matcher dynamically instead:
126+
A [Next.js `matcher`](https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher) needs to be statically analyzable, therefore you can't use variables to generate this value. However, you can alternatively implement a programmatic condition in the middleware:
127127

128128
```tsx filename="middleware.ts"
129129
import {NextRequest} from 'next/server';
@@ -135,6 +135,7 @@ const handleI18nRouting = createMiddleware(routing);
135135
export default function middleware(request: NextRequest) {
136136
const {pathname} = request.nextUrl;
137137

138+
// Matches '/', as well as all paths that start with a locale like '/en'
138139
const shouldHandle =
139140
pathname === '/' ||
140141
new RegExp(`^/(${locales.join('|')})(/.*)?$`).test(

docs/src/pages/docs/routing/navigation.mdx

+7-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {defineRouting} from 'next-intl/routing';
1818

1919
export const routing = defineRouting(/* ... */);
2020

21-
export const {Link, redirect, usePathname, useRouter} =
21+
export const {Link, redirect, usePathname, useRouter, getPathname} =
2222
createNavigation(routing);
2323
```
2424

@@ -32,10 +32,11 @@ In case you're building an app where locales can be added and removed at runtime
3232
```tsx filename="routing.ts"
3333
import {createNavigation} from 'next-intl/navigation';
3434

35-
export const {Link, redirect, usePathname, useRouter} = createNavigation({
36-
// ... potentially other routing
37-
// config, but no `locales` ...
38-
});
35+
export const {Link, redirect, usePathname, useRouter, getPathname} =
36+
createNavigation({
37+
// ... potentially other routing
38+
// config, but no `locales` ...
39+
});
3940
```
4041

4142
Note however that the `locales` argument for the middleware is still mandatory. If you need to fetch the available locales at runtime, you can provide the routing configuration for the middleware [dynamically per request](/docs/routing/middleware#composing-other-middlewares).
@@ -324,7 +325,7 @@ function UserProfile({userId}: {userId?: string}) {
324325
To work around this limitation, you can add an explicit type annotation to the `redirect` function:
325326

326327
```tsx filename="routing.ts"
327-
const {redirect: _redirect} = createNavigation(routing);
328+
const {/* ..., */ redirect: _redirect} = createNavigation(routing);
328329

329330
// Enable type narrowing after calling `redirect`
330331
export const redirect: typeof _redirect = _redirect;

lerna.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "node_modules/@lerna-lite/cli/schemas/lerna-schema.json",
3-
"version": "3.25.1",
3+
"version": "3.25.3",
44
"packages": [
55
"packages/*"
66
],

packages/next-intl/.size-limit.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,50 @@ import type {SizeLimitConfig} from 'size-limit';
22

33
const config: SizeLimitConfig = [
44
{
5-
name: "import * from 'next-intl' (react-client, production)",
5+
name: "import * from 'next-intl' (react-client)",
66
path: 'dist/esm/production/index.react-client.js',
77
limit: '13.065 KB'
88
},
99
{
10-
name: "import {NextIntlClientProvider} from 'next-intl' (react-client, production)",
10+
name: "import {NextIntlClientProvider} from 'next-intl' (react-client)",
1111
import: '{NextIntlClientProvider}',
1212
path: 'dist/esm/production/index.react-client.js',
1313
limit: '1 KB'
1414
},
1515
{
16-
name: "import * from 'next-intl' (react-server, production)",
16+
name: "import * from 'next-intl' (react-server)",
1717
path: 'dist/esm/production/index.react-server.js',
1818
limit: '14.005 KB'
1919
},
2020
{
21-
name: "import {createNavigation} from 'next-intl/navigation' (react-client, production)",
21+
name: "import {createNavigation} from 'next-intl/navigation' (react-client)",
2222
path: 'dist/esm/production/navigation.react-client.js',
2323
import: '{createNavigation}',
24-
limit: '2.445 KB'
24+
limit: '2.475 KB'
2525
},
2626
{
27-
name: "import {createNavigation} from 'next-intl/navigation' (react-server, production)",
27+
name: "import {createNavigation} from 'next-intl/navigation' (react-server)",
2828
path: 'dist/esm/production/navigation.react-server.js',
2929
import: '{createNavigation}',
30-
limit: '3.245 KB'
30+
limit: '3.25 KB'
3131
},
3232
{
33-
name: "import * from 'next-intl/server' (react-client, production)",
33+
name: "import * from 'next-intl/server' (react-client)",
3434
path: 'dist/esm/production/server.react-client.js',
3535
limit: '1 KB'
3636
},
3737
{
38-
name: "import * from 'next-intl/server' (react-server, production)",
38+
name: "import * from 'next-intl/server' (react-server)",
3939
path: 'dist/esm/production/server.react-server.js',
4040
limit: '13.335 KB'
4141
},
4242
{
43-
name: "import * from 'next-intl/middleware' (production)",
43+
name: "import * from 'next-intl/middleware'",
4444
path: 'dist/esm/production/middleware.js',
45-
limit: '9.265 KB'
45+
limit: '9.305 KB'
4646
},
4747
{
48-
name: "import * from 'next-intl/routing' (production)",
48+
name: "import * from 'next-intl/routing'",
4949
path: 'dist/esm/production/routing.js',
5050
limit: '1 KB'
5151
}

packages/next-intl/CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## 3.25.3 (2024-11-26)
7+
8+
### Bug Fixes
9+
10+
* Follow-up for [#1573](https://github.com/amannn/next-intl/issues/1573) to also handle the case when a non-default locale is in use ([#1578](https://github.com/amannn/next-intl/issues/1578)) ([fd71741](https://github.com/amannn/next-intl/commit/fd7174179881a19e3573fceb9c6e903923644761)), closes [#1568](https://github.com/amannn/next-intl/issues/1568) – by @amannn
11+
12+
## 3.25.2 (2024-11-25)
13+
14+
### Bug Fixes
15+
16+
* Handle inconsistency in Next.js when using `usePathname` with custom prefixes, `localePrefix: 'as-needed'` and static rendering ([#1573](https://github.com/amannn/next-intl/issues/1573)) ([20fd0f0](https://github.com/amannn/next-intl/commit/20fd0f0015839357893bcd256ff880a98b01ea1f)) – by @amannn
17+
618
## 3.25.1 (2024-11-13)
719

820
### Bug Fixes

packages/next-intl/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "next-intl",
3-
"version": "3.25.1",
3+
"version": "3.25.3",
44
"sideEffects": false,
55
"author": "Jan Amann <jan@amann.work>",
66
"funding": [

packages/next-intl/src/navigation/react-client/createNavigation.test.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,36 @@ describe("localePrefix: 'as-needed'", () => {
527527
});
528528
});
529529

530+
describe("localePrefix: 'as-needed', custom `prefixes`", () => {
531+
const {usePathname} = createNavigation({
532+
defaultLocale,
533+
locales,
534+
localePrefix: {
535+
mode: 'as-needed',
536+
prefixes: {
537+
en: '/english',
538+
de: '/deutsch'
539+
}
540+
}
541+
});
542+
const renderPathname = getRenderPathname(usePathname);
543+
544+
// https://github.com/vercel/next.js/issues/73085
545+
it('is tolerant when a locale is used in the pathname for the default locale', () => {
546+
mockCurrentLocale('en');
547+
mockLocation({pathname: '/en/about'});
548+
renderPathname();
549+
screen.getByText('/about');
550+
});
551+
552+
it('is tolerant when a locale is used in the pathname for a non-default locale', () => {
553+
mockCurrentLocale('de');
554+
mockLocation({pathname: '/de/about'});
555+
renderPathname();
556+
screen.getByText('/about');
557+
});
558+
});
559+
530560
describe("localePrefix: 'as-needed', with `basePath` and `domains`", () => {
531561
const {useRouter} = createNavigation({
532562
locales,

packages/next-intl/src/navigation/react-client/createNavigation.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default function createNavigation<
4949
function usePathname(): [AppPathnames] extends [never]
5050
? string
5151
: keyof AppPathnames {
52-
const pathname = useBasePathname(config.localePrefix);
52+
const pathname = useBasePathname(config);
5353
const locale = useLocale();
5454

5555
// @ts-expect-error -- Mirror the behavior from Next.js, where `null` is returned when `usePathname` is used outside of Next, but the types indicate that a string is always returned.

packages/next-intl/src/navigation/react-client/useBasePathname.test.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ function mockPathname(pathname: string) {
1616
}
1717

1818
function Component() {
19-
const pathname = useBasePathname({
20-
// The mode is not used, only the absence of
21-
// `prefixes` is relevant for this test suite
22-
mode: 'as-needed'
19+
return useBasePathname({
20+
localePrefix: {
21+
// The mode is not used, only the absence of
22+
// `prefixes` is relevant for this test suite
23+
mode: 'as-needed'
24+
}
2325
});
24-
return <>{pathname}</>;
2526
}
2627

2728
describe('unprefixed routing', () => {

packages/next-intl/src/navigation/react-client/useBasePathname.tsx

+22-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
Locales
88
} from '../../routing/types.tsx';
99
import {
10+
getLocaleAsPrefix,
1011
getLocalePrefix,
1112
hasPathnamePrefixed,
1213
unprefixPathname
@@ -15,7 +16,10 @@ import {
1516
export default function useBasePathname<
1617
AppLocales extends Locales,
1718
AppLocalePrefixMode extends LocalePrefixMode
18-
>(localePrefix: LocalePrefixConfigVerbose<AppLocales, AppLocalePrefixMode>) {
19+
>(config: {
20+
localePrefix: LocalePrefixConfigVerbose<AppLocales, AppLocalePrefixMode>;
21+
defaultLocale?: AppLocales[number];
22+
}) {
1923
// The types aren't entirely correct here. Outside of Next.js
2024
// `useParams` can be called, but the return type is `null`.
2125

@@ -34,12 +38,24 @@ export default function useBasePathname<
3438
return useMemo(() => {
3539
if (!pathname) return pathname;
3640

37-
const prefix = getLocalePrefix(locale, localePrefix);
41+
let unlocalizedPathname = pathname;
42+
43+
const prefix = getLocalePrefix(locale, config.localePrefix);
3844
const isPathnamePrefixed = hasPathnamePrefixed(prefix, pathname);
39-
const unlocalizedPathname = isPathnamePrefixed
40-
? unprefixPathname(pathname, prefix)
41-
: pathname;
45+
46+
if (isPathnamePrefixed) {
47+
unlocalizedPathname = unprefixPathname(pathname, prefix);
48+
} else if (
49+
config.localePrefix.mode === 'as-needed' &&
50+
config.localePrefix.prefixes
51+
) {
52+
// Workaround for https://github.com/vercel/next.js/issues/73085
53+
const localeAsPrefix = getLocaleAsPrefix(locale);
54+
if (hasPathnamePrefixed(localeAsPrefix, pathname)) {
55+
unlocalizedPathname = unprefixPathname(pathname, localeAsPrefix);
56+
}
57+
}
4258

4359
return unlocalizedPathname;
44-
}, [locale, localePrefix, pathname]);
60+
}, [config.localePrefix, locale, pathname]);
4561
}

packages/next-intl/src/shared/utils.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,14 @@ export function getLocalePrefix<
9797
(localePrefix.mode !== 'never' && localePrefix.prefixes?.[locale]) ||
9898
// We return a prefix even if `mode: 'never'`. It's up to the consumer
9999
// to decide to use it or not.
100-
'/' + locale
100+
getLocaleAsPrefix(locale)
101101
);
102102
}
103103

104+
export function getLocaleAsPrefix(locale: string) {
105+
return '/' + locale;
106+
}
107+
104108
export function templateToRegex(template: string): RegExp {
105109
const regexPattern = template
106110
// Replace optional catchall ('[[...slug]]')

packages/use-intl/CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## 3.25.3 (2024-11-26)
7+
8+
### Bug Fixes
9+
10+
* Follow-up for [#1573](https://github.com/amannn/next-intl/issues/1573) to also handle the case when a non-default locale is in use ([#1578](https://github.com/amannn/next-intl/issues/1578)) ([fd71741](https://github.com/amannn/next-intl/commit/fd7174179881a19e3573fceb9c6e903923644761)), closes [#1568](https://github.com/amannn/next-intl/issues/1568) – by @amannn
11+
12+
## 3.25.2 (2024-11-25)
13+
14+
### Bug Fixes
15+
16+
* Handle inconsistency in Next.js when using `usePathname` with custom prefixes, `localePrefix: 'as-needed'` and static rendering ([#1573](https://github.com/amannn/next-intl/issues/1573)) ([20fd0f0](https://github.com/amannn/next-intl/commit/20fd0f0015839357893bcd256ff880a98b01ea1f)) – by @amannn
17+
618
## 3.25.1 (2024-11-13)
719

820
### Bug Fixes

packages/use-intl/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "use-intl",
3-
"version": "3.25.1",
3+
"version": "3.25.3",
44
"sideEffects": false,
55
"author": "Jan Amann <jan@amann.work>",
66
"description": "Internationalization (i18n) for React",

0 commit comments

Comments
 (0)