next-i18n-router
Version:
Next.js App Router internationalized routing and locale detection.
239 lines (156 loc) • 9.7 kB
Markdown
<div align="center">
<a href="https://i18nexus.com" target="_blank">
<img src="./assets/sponsor.png" alt="i18nexus Logo" width="350" />
</a>
</div>
# next-i18n-router
**Adds internationalized routing for Next.js apps that use the App Router**
## App Router internationalized routing 🎉
### Why is this library needed?
With the release of the App Router, internationalized routing has been removed as a built-in Next.js feature. This library adds back internationalized routing in addition to locale detection and optional cookie usage to set a user's preferred language.
This library can be used with any of the popular Javascript i18n libraries such as `react-i18next` and `react-intl`. Tutorials and examples can be found [here](#usage-with-popular-i18n-libraries).
## Installation
```sh
npm install next-i18n-router
```
## Example Usage
First, nest all pages and layouts inside of a dynamic segment named `[locale]`:
```
└── app
└── [locale]
├── layout.js
└── page.js
```
Create a file called `i18nConfig.js` at the root of your project to store your config:
```js
const i18nConfig = {
locales: ['en', 'de', 'ja'],
defaultLocale: 'en'
};
module.exports = i18nConfig;
```
Create a `proxy.js` file (or `middleware.js` if you're using Next.js 15 or earlier) in the root of your project. If your app uses a `/src` directory, put it in `/src`.
This is where `i18nRouter` will be used to handle internationalized redirects and rewrites:
```js
import { i18nRouter } from 'next-i18n-router';
import i18nConfig from './i18nConfig';
// name this function "middleware" in Next 15 or earlier
export function proxy(request) {
return i18nRouter(request, i18nConfig);
}
// only applies this logic to files in the app directory
export const config = {
matcher: '/((?!api|static|.*\\..*|_next).*)'
};
```
In your root layout, add a `notFound` redirect for any unsupported locales:
```js
...
import { notFound } from 'next/navigation';
...
export default function RootLayout({ children, params: { locale } }) {
if (!i18nConfig.locales.includes(locale)) {
notFound();
}
return (
...
);
}
```
You now have internationalized routing!
## Config Options
| Option | Default value | Type | Required? |
| ----------------- | --------------- | --------------------------------- | --------- |
| `locales` | | string[] | ✔ |
| `defaultLocale` | | string | ✔ |
| `prefixDefault` | `false` | boolean | |
| `localeDetector` | (See below) | function \| false | |
| `localeCookie` | `'NEXT_LOCALE'` | string | |
| `noPrefix` | `false` | boolean | |
| `serverSetCookie` | `'always'` | "always" \| "if-empty" \| "never" | |
| `cookieOptions` | (See below) | object | |
| `basePath` | `''` | string | |
## Locale Path Prefixing
By default, the `defaultLocale`'s path is not prefixed with the locale. For example, if `defaultLocale` is set to `en` and `locales` is set to `['en', 'de']`, the paths will appear as follows:
**English**: `/products`
**German**: `/de/products`
To also include your default locale in the path, set the `prefixDefault` config option to `true`.
To hide all locales from the path, set the `noPrefix` config option to `true`.
## Locale Detection
By default, this library parses the request's `accept-language` header and determines which of your `locales` is preferred using `/intl-localematcher`. This logic can be disabled or customized using the `localeDetector` config option (below).
Using the `accept-language` header is the recommended strategy outlined in the [Next.js docs](https://nextjs.org/docs/app/building-your-application/routing/internationalization) and is similar to the previous Pages Router implementation.
### Custom Locale Detection (optional)
If you would prefer to handle locale detection yourself, you can set the `localeDetector` option with your own locale detection function:
```js
const i18nConfig = {
locales: ['en', 'de', 'ja'],
defaultLocale: 'en',
localeDetector: (request, config) => {
// your custom locale detection logic
return 'the-locale';
}
};
module.exports = i18nConfig;
```
You can also set the `localeDetector` option to `false` if you wish to opt out of any locale detection.
### Locale Cookie (optional)
You can override the `localeDetector` using the `NEXT_LOCALE=the-locale` cookie. For example, you can set this cookie when a user opts to change to a different language. When they return to your site, their preferred language will already be set.
If you would prefer to use a different cookie key other than `NEXT_LOCALE`, you can set the `localeCookie` option.
### serverSetCookie (optional)
The `serverSetCookie` option automatically changes a visitor's preferred locale cookie by simply visiting a pathname that contains a locale.
`'always'`(default): When the pathname of a request includes a locale, that locale will be set as the cookie by the middleware. This means that locale detection and any existing locale cookie will be ignored if a locale exists in the request's pathname. Locale detection and the reading of any existing cookie will still be run on pathnames that do not include a locale.
`'if-empty'`: Same as `'always'`, except the middleware will not overwrite the cookie if one already exists.
`'never'`: The middleware will not automatically set the cookie.
If you are using `noPrefix`, the `serverSetCookie` option does not do anything since there is no locale in the pathname to read from. All language changing must be done by setting the cookie manually.
### cookieOptions (optional)
The server sets the cookie by setting the `Set-Cookie` HTTP response header on the `NextResponse`. [(Learn More)](https://nextjs.org/docs/app/api-reference/functions/next-response#setname-value)
By default, `cookieOptions` is set to:
```
{
sameSite: 'lax',
maxAge: 31536000,
path: {the basePath of the incoming NextRequest}
}
```
You can set your own `cookieOptions` object containing any of the valid `Set-Cookie` attributes: [MDN: Set-Cookie](https://nextjs.org/docs/app/api-reference/functions/next-response)
## Using `basePath` (optional)
This is only needed if you are using the `basePath` option in `next.config.js`. You will need to also include it as the `basePath` option in your `i18nConfig`.
As can be read about [here](https://github.com/vercel/next.js/issues/47085), you will also need to update your `matcher` in your middleware config to include `{ source: '/' }`:
```js
export const config = {
matcher: ['/((?!api|static|.*\\..*|_next).*)', { source: '/' }]
};
```
## Getting the current locale
### In a Client Component:
The current locale can be retrieved in a Client Component using the `useCurrentLocale` hook:
```js
'use client';
import { useCurrentLocale } from 'next-i18n-router/client';
import i18nConfig from '@/i18nConfig';
function ExampleClientComponent() {
const locale = useCurrentLocale(i18nConfig);
...
}
```
### In a Server Component:
The current locale should be accessed from the component's `params` props:
```js
function ExampleServerComponent({ params: { locale } }) {
...
}
```
# Usage with popular i18n libraries
## react-intl
The `react-intl` library works great with the App Router. But it does require a litte extra configuration for usage in Server Components.
For a full walkthrough on using `react-intl` with `next-i18n-router` (plus Google Translate/DeepL integration), see [this tutorial](https://i18nexus.com/tutorials/nextjs/react-intl).
You can also find an example project [here](https://github.com/i18nexus/next-i18n-router/tree/main/examples/react-intl-example).
## react-i18next
If you use `i18next`, you can now use either `next-i18next` or `react-i18next` with the App Router. `next-i18next` previously only worked with the Pages Router, but it recently added App Router support in 2026. If you want to use `next-i18next`, see [this `next-i18next` App Router tutorial](https://i18nexus.com/tutorials/nextjs/next-i18next-app-router). `next-i18n-router` is not needed for that setup.
If you're interested in using `react-i18next` directly instead, see [this tutorial](https://i18nexus.com/tutorials/nextjs/react-i18next).
You can also find an example project [here](https://github.com/i18nexus/next-i18n-router/tree/main/examples/i18next-example).
# FAQ
## How do I create a dropdown for a user to change the language?
In our [example projects](https://github.com/i18nexus/next-i18n-router/tree/main/examples) you will find a `LanguageChanger` component showing how to do this. Note that `router.refresh` is called after changing languages. This is because Next will not route the request through the middleware if the page happens to be cached on the client. `router.refresh` ensures the middleware is run on language change, allowing the locale cookie to be set properly.
## My not-found page is not working. What's wrong?
This is likely because of our use of the `[locale]` dynamic segment. This is not a bug with this library. It is a design choice of NextJS when using a not-found page in a dynamic segment. To solve this, we recommend making the adjustment described [here](https://github.com/i18nexus/next-i18n-router/issues/36#issuecomment-1821887026).