Skip to content

Commit

Permalink
feat: Add navigation APIs for App Router (useRouter, usePathname
Browse files Browse the repository at this point in the history
…and `Link`) (#282)

Changes:
- Add navigation APIs for the App Router: `useRouter`, `usePathname` &
`Link` (see [docs](https://next-intl-docs.vercel.app/docs/next-13))
- Revamp Next.js 13 example
  • Loading branch information
amannn authored May 10, 2023
1 parent b2e863e commit e30a89b
Show file tree
Hide file tree
Showing 51 changed files with 1,076 additions and 179 deletions.
2 changes: 1 addition & 1 deletion docs/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = withNextra({
},
{
source: '/examples',
destination: '/examples/minimal',
destination: '/examples/next-13',
permanent: false
},

Expand Down
3 changes: 2 additions & 1 deletion docs/pages/docs/next-13/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"index": "Getting started",
"client-components": "Client Components",
"server-components": "Server Components (beta)",
"middleware": "Middleware"
"middleware": "Middleware",
"navigation": "Navigation"
}
51 changes: 0 additions & 51 deletions docs/pages/docs/next-13/client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -121,57 +121,6 @@ If you've encountered an issue, you can [explore the code for a working example]
version](/docs/next-13/server-components).
</Callout>

## Linking between pages

In the `pages` folder, Next.js automatically considers the current locale for `next/link`. Since this is no longer the case for the `app` directory, you can add a component that wraps `next/link` accordingly as a drop-in replacement.

```tsx filename="Link.tsx"
import {useLocale} from 'next-intl';
import NextLink from 'next/link';
import {ComponentProps, forwardRef} from 'react';

type Props = ComponentProps<typeof NextLink>;

function Link({href, locale, prefetch, ...rest}: Props, ref: Props['ref']) {
const defaultLocale = useLocale();
if (!locale) locale = defaultLocale;

// Turn prefetching off, to avoid updating the locale cookie for prefetch requests
if (locale !== defaultLocale) {
prefetch = false;
}

function getLocalizedHref(originalHref: string) {
return originalHref.replace(/^\//, '/' + locale + '/');
}

const localizedHref =
typeof href === 'string'
? getLocalizedHref(href)
: href.pathname != null
? {...href, pathname: getLocalizedHref(href.pathname)}
: href;

return (
<NextLink ref={ref} href={localizedHref} prefetch={prefetch} {...rest} />
);
}

export default forwardRef(Link);
```

```tsx
// Usage while being on `/en`
<Link href="/">Goes to `/en`</Link>
<Link href="/nested">Goes to `/en/nested`</Link>
<Link href={{pathname: "/nested"}}>Goes to `/en/nested`</Link>
```

<Callout>
Built-in support for i18n routing APIs is being considered (see [the Server
Components beta docs](/docs/next-13/server-components#routing)).
</Callout>

## Usage with the Metadata API

To provide metadata for a route, you can use the [core library](/docs/usage/core-library) from `next-intl`.
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/docs/next-13/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ To fill in the gaps and to leverage the new capabilities, `next-intl` now suppor
<Card
arrow
icon={<LinkIcon />}
title="Localized routing APIs (beta)"
href="/docs/next-13/server-components#routing"
title="Localized navigation APIs"
href="/docs/next-13/navigation"
/>
</Cards>
2 changes: 1 addition & 1 deletion docs/pages/docs/next-13/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ module.exports = withNextIntl({

Since `next-intl` isn't aware of the rewrites you've configured, you likely want to make some adjustments:

1. Translate the pathnames you're passing to routing APIs like `Link` based on the `locale`. See the [named routes example](https://github.com/amannn/next-intl/blob/feat/next-13-rsc/packages/example-next-13-named-routes/) that uses the proposed [routing APIs from the Server Components beta](http://localhost:3000/docs/next-13/server-components#routing).
1. Translate the pathnames you're passing to [navigation APIs](/docs/next-13/navigation) like `Link` based on the `locale`. See the [named routes example](https://github.com/amannn/next-intl/blob/feat/next-13-rsc/packages/example-next-13-named-routes/) that uses the proposed APIs from [the Server Components beta](http://localhost:3000/docs/next-13/server-components).
2. Turn off [the `alternateLinks` option](/docs/next-13/middleware#disable-alternate-links) and provide [search engine hints about localized versions of your content](https://developers.google.com/search/docs/specialty/international/localized-versions) by yourself.

## Composing other middlewares
Expand Down
71 changes: 71 additions & 0 deletions docs/pages/docs/next-13/navigation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Callout from 'components/Callout';

# Next.js 13 internationalized navigation

`next-intl` provides drop-in replacements for common Next.js navigation APIs that automatically handle the user locale behind the scenes.

### `Link`

This component wraps `next/link` and automatically prefixes the `href` with the current locale as necessary. If the default locale is matched, the `href` remains unchanged and no prefix is added.

```tsx
import {Link} from 'next-intl';

// When the user is on `/en`, the link will point to `/en/about`
<Link href="/about">About</Link>

// You can override the `locale` to switch to another language
<Link href="/" locale="de">Switch to German</Link>
```

### `useRouter`

If you need to navigate programmatically, e.g. in response to a form submission, `next-intl` provides a convience API that wraps `useRouter` from Next.js and automatically applies the locale of the user.

```tsx
'use client';

import {useRouter} from 'next-intl/client';

const router = useRouter();

// When the user is on `/en`, the router will navigate to `/en/about`
router.push('/about');
```

### `usePathname`

To retrieve the pathname without a potential locale prefix, you can call `usePathname`.

```tsx
'use client';

import {usePathname} from 'next-intl/client';

// When the user is on `/en`, this will be `/`
const pathname = usePathname();
```

### `redirect`

<Callout type="warning">
This API is only available in [the Server Components
beta](/docs/next-13/server-components).
</Callout>

If you want to interrupt the render of a Server Component and redirect to another page, you can invoke the `redirect` function from `next-intl/server`. This wraps [the `redirect` function from Next.js](https://beta.nextjs.org/docs/api-reference/redirect) and automatically applies the current locale.

```tsx {1, 8}
import {redirect} from 'next-intl/server';

export default async function Profile() {
const user = await fetchUser();

if (!user) {
// When the user is on `/en/profile`, this will be `/en/login`
redirect('/login');
}

// ...
}
```
69 changes: 2 additions & 67 deletions docs/pages/docs/next-13/server-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -167,71 +167,6 @@ If you've encountered an issue, you can [explore the code for a working example]

If you're in a transitioning phase, either from the `pages` directory to the `app` directory, or from Client Components to the Server Components beta, you can apply [`NextIntlClientProvider`](/docs/next-13/client-components) additionally ([example](https://github.com/amannn/next-intl/tree/feat/next-13-rsc/packages/example-next-13-with-pages)).

## Routing

`next-intl` provides drop-in replacements for common Next.js routing APIs that automatically handle the user locale behind the scenes.

### `Link`

`next-intl` provides a drop-in replacement for `next/link` that will automatically prefix the `href` with the current locale as necessary. If the default locale is matched, the `href` remains unchanged and no prefix is added.

```tsx
import {Link} from 'next-intl';

// When the user is on `/en`, the link will point to `/en/about`
<Link href="/about">About</Link>

// You can override the `locale` to switch to another language
<Link href="/" locale="de">Switch to German</Link>
```

### `useRouter`

If you need to navigate programmatically (e.g. in response to a form submission), `next-intl` provides a convience API that wraps `useRouter` from Next.js and automatically applies the locale of the user.

```tsx
'use client';

import {useRouter} from 'next-intl/client';

const router = useRouter();

// When the user is on `/en`, the router will navigate to `/en/about`
router.push('/about');
```

### `usePathname`

To retrieve the pathname without a potential locale prefix, you can call `usePathname`.

```tsx
'use client';

import {usePathname} from 'next-intl/client';

// When the user is on `/en`, this will be `/`
const pathname = usePathname();
```

### `redirect`

If you want to interrupt the render of a Server Component and redirect to another page, you can invoke the `redirect` function from `next-intl/server`. This wraps [the `redirect` function from Next.js](https://beta.nextjs.org/docs/api-reference/redirect) and automatically applies the current locale.

```tsx {1, 8}
import {redirect} from 'next-intl/server';

export default async function Profile() {
const user = await fetchUser();

if (!user) {
// When the user is on `/en/profile`, this will be `/en/login`
redirect('/login');
}

// ...
}
```

## Using translations in Client Components

If you need to use translations or other functionality from `next-intl` in Client Components, the best approach is to pass the labels as props or `children` from a Server Component.
Expand Down Expand Up @@ -438,8 +373,8 @@ module.exports = withNextIntl({
```

<Callout type="warning">
Unfortunately, it seems like this doesn't work anymore in Next.js 13.4. An
alternative is being investigated.
Unfortunately, this doesn't work anymore in Next.js 13.4. An alternative is
being investigated.
</Callout>

## Providing feedback
Expand Down
8 changes: 6 additions & 2 deletions docs/pages/examples/_meta.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{
"next-13": {
"title": "App Router",
"theme": {"layout": "full"}
},
"minimal": {
"title": "Minimal setup",
"title": "Pages Router",
"theme": {"layout": "full"}
},
"advanced": {
"title": "Advanced usage",
"title": "Pages Router (advanced)",
"theme": {"layout": "full"}
}
}
4 changes: 2 additions & 2 deletions docs/pages/examples/advanced.mdx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
title: Advanced usage
title: Pages Router (advanced)
full: true
---

import CodeSandbox from '../../components/CodeSandbox';

<CodeSandbox
title="Advanced usage"
title="Pages Router (advanced)"
src="https://codesandbox.io/s/github/amannn/next-intl/tree/main/examples/example-advanced?file=/src/pages/index.tsx"
/>
4 changes: 2 additions & 2 deletions docs/pages/examples/minimal.mdx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
title: Minimal setup
title: Pages Router
full: true
---

import CodeSandbox from '../../components/CodeSandbox';

<CodeSandbox
title="Minimal setup"
title="Pages Router"
src="https://codesandbox.io/s/github/amannn/next-intl/tree/main/examples/example?file=/src/pages/index.tsx"
/>
11 changes: 11 additions & 0 deletions docs/pages/examples/next-13.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: App Router
full: true
---

import CodeSandbox from '../../components/CodeSandbox';

<CodeSandbox
title="App Router"
src="https://codesandbox.io/s/github/amannn/next-intl/tree/main/examples/example-next-13?file=/src/app/[locale]/page.tsx"
/>
35 changes: 31 additions & 4 deletions examples/example-next-13/messages/de.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
{
"Index": {
"title": "Start",
"description": "Das ist die Startseite."
"IndexPage": {
"title": "next-intl Beispiel",
"description": "Dies ist ein Beispiel, das die Verwendung von <code>next-intl</code> mit dem Next.js App Router demonstriert. Bei Ändern der Sprache rechts oben ändert sich der Inhalt dieser Seite."
},
"AboutPage": {
"title": "Über",
"description": "<p>Auch das Routing ist internationalisiert.</p><p>Wenn du die Standardsprache Englisch verwendest, siehst du <code>/about</code> in der Adressleiste des Browsers auf dieser Seite.</p><p>Wenn du die Sprache auf Deutsch änderst, wird die URL mit der Locale ergänzt (<code>/de/about</code>).</p>"
},
"NotFoundPage": {
"title": "Seite nicht gefunden",
"description": "Bitte überprüfe die URL oder verwende die Navigation um zu einer bekannten Seite zu wechseln."
},
"LocaleLayout": {
"title": "next-intl Beispiel"
},
"LocaleSwitcher": {
"switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch} other {Unbekannt}} wechseln"
"label": "Sprache ändern",
"locale": "{locale, select, de {Deutsch} en {Englisch} other {Unbekannt}}"
},
"Navigation": {
"home": "Start",
"about": "Über"
},
"PageLayout": {
"links": {
"docs": {
"title": "Dokumentation",
"description": "Erfahre mehr über next-intl in der offiziellen Dokumentation.",
"href": "https://next-intl-docs.vercel.app/"
},
"source": {
"title": "Quellcode",
"description": "Sieh dir den Quellcode dieses Beispiels auf GitHub an.",
"href": "https://github.com/amannn/next-intl/tree/main/examples/example-next-13"
}
}
}
}
35 changes: 31 additions & 4 deletions examples/example-next-13/messages/en.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
{
"Index": {
"title": "Home",
"description": "This is the home page."
"IndexPage": {
"title": "next-intl example",
"description": "This is a basic example that demonstrates the usage of <code>next-intl</code> with the Next.js App Router. Try changing the locale in the top right corner and see how the content changes."
},
"AboutPage": {
"title": "About",
"description": "<p>The routing is internationalized too.</p><p>If you're using the default language English, you'll see <code>/about</code> in the browser address bar on this page.</p><p>If you change the locale to German, the URL is prefixed with the locale (<code>/de/about</code>).</p>"
},
"NotFoundPage": {
"title": "Page not found",
"description": "Please double-check the URL or use the navigation to go to a known page."
},
"LocaleLayout": {
"title": "next-intl example"
},
"LocaleSwitcher": {
"switchLocale": "Switch to {locale, select, de {German} en {English} other {Unknown}}"
"label": "Change language",
"locale": "{locale, select, de {German} en {English} other {Unknown}}"
},
"Navigation": {
"home": "Home",
"about": "About"
},
"PageLayout": {
"links": {
"docs": {
"title": "Docs",
"description": "Learn more about next-intl in the official docs.",
"href": "https://next-intl-docs.vercel.app/"
},
"source": {
"title": "Source code",
"description": "Browse the source code of this example on GitHub.",
"href": "https://github.com/amannn/next-intl/tree/main/examples/example-next-13"
}
}
}
}
Loading

3 comments on commit e30a89b

@vercel
Copy link

@vercel vercel bot commented on e30a89b May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-example-next-13 – ./examples/example-next-13

next-intl-example-next-13.vercel.app
next-intl-example-next-13-amannn.vercel.app
next-intl-example-next-13-git-main-amannn.vercel.app

@vercel
Copy link

@vercel vercel bot commented on e30a89b May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-docs – ./docs

next-intl-docs-git-main-amannn.vercel.app
next-intl-docs.vercel.app
next-intl-docs-amannn.vercel.app

@vercel
Copy link

@vercel vercel bot commented on e30a89b May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

example-next-13-next-auth – ./examples/example-next-13-next-auth

example-next-13-next-auth.vercel.app
example-next-13-next-auth-git-main-amannn.vercel.app
example-next-13-next-auth-amannn.vercel.app

Please sign in to comment.