diff --git a/docs/pages/blog/next-intl-3-0.mdx b/docs/pages/blog/next-intl-3-0.mdx index 8ca854fc2..d02f98365 100644 --- a/docs/pages/blog/next-intl-3-0.mdx +++ b/docs/pages/blog/next-intl-3-0.mdx @@ -31,7 +31,7 @@ The latter two have already been added in minor versions, but 3.0 cleans up the `next-intl` now requires two additional setup steps when you're using the App Router: -1. [The `i18n.ts` module](/docs/getting-started/app-router#i18nts) provides configuration for Server Components +1. [The `i18n.ts` module](/docs/getting-started/app-router#i18n-request) provides configuration for Server Components 2. [`next-intl/plugin`](/docs/getting-started/app-router#nextconfigjs) needs to be added to link your `i18n.ts` module to `next-intl` ### New navigation APIs for the App Router diff --git a/docs/pages/docs/environments/actions-metadata-route-handlers.mdx b/docs/pages/docs/environments/actions-metadata-route-handlers.mdx index 44c6929f7..52b17aae8 100644 --- a/docs/pages/docs/environments/actions-metadata-route-handlers.mdx +++ b/docs/pages/docs/environments/actions-metadata-route-handlers.mdx @@ -205,7 +205,7 @@ If you're using the [`pathnames`](/docs/routing#pathnames) setting, you can gene ```tsx import {MetadataRoute} from 'next'; import {locales, defaultLocale} from '@/config'; -import {getPathname} from '@/routing'; +import {getPathname} from '@/i18n/routing'; // Adapt this as necessary const host = 'https://acme.com'; diff --git a/docs/pages/docs/environments/core-library.mdx b/docs/pages/docs/environments/core-library.mdx index 67c88abb2..c9639afc0 100644 --- a/docs/pages/docs/environments/core-library.mdx +++ b/docs/pages/docs/environments/core-library.mdx @@ -8,7 +8,7 @@ While `next-intl` is primarily intended to be used in Next.js apps, the core is 1. [Routing APIs](/docs/routing) 2. [Awaitable APIs](/docs/environments/actions-metadata-route-handlers) for Server Actions, Metadata and Route Handlers -3. [Server Components integration](/docs/environments/server-client-components) along with `i18n.ts` +3. [Server Components integration](/docs/environments/server-client-components) along with `i18n/request.ts` In case Server Components establish themselves in React apps outside of Next.js, the support for Server Components might be moved to the core library in the future. diff --git a/docs/pages/docs/environments/error-files.mdx b/docs/pages/docs/environments/error-files.mdx index 370a6e8c6..9d713a88e 100644 --- a/docs/pages/docs/environments/error-files.mdx +++ b/docs/pages/docs/environments/error-files.mdx @@ -77,18 +77,16 @@ export default function RootLayout({children}) { } ``` -For the 404 page to render, we need to call the `notFound` function in [`i18n.ts`](/docs/usage/configuration#i18nts) when we detect an incoming `locale` param that isn't a valid locale. +For the 404 page to render, we need to call the `notFound` function in [`i18n/request.ts`](/docs/usage/configuration#i18n-request) when we detect an incoming `locale` param that isn't a valid locale. -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {notFound} from 'next/navigation'; import {getRequestConfig} from 'next-intl/server'; - -// Can be imported from a shared config -const locales = ['en', 'de']; +import {routing} from '@/i18n/routing'; export default getRequestConfig(async ({locale}) => { // Validate that the incoming `locale` parameter is valid - if (!locales.includes(locale as any)) notFound(); + if (!routing.locales.includes(locale as any)) notFound(); return { // ... diff --git a/docs/pages/docs/environments/server-client-components.mdx b/docs/pages/docs/environments/server-client-components.mdx index 24f29beb8..83441bbde 100644 --- a/docs/pages/docs/environments/server-client-components.mdx +++ b/docs/pages/docs/environments/server-client-components.mdx @@ -91,7 +91,7 @@ If you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useT
How does the Server Components integration work? -`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n.ts`](/docs/usage/configuration#i18nts). +`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n/request.ts`](/docs/usage/configuration#i18n-request). Hooks are currently primarly known for being used in Client Components since they are typically stateful or don't apply to a server environment. However, hooks like [`useId`](https://react.dev/reference/react/useId) can be used in Server Components too. Similarly, `next-intl` provides a hooks-based API that looks identical, regardless of if it's used in a Server or Client Component. @@ -106,13 +106,13 @@ If you implement components that qualify as shared components, it can be benefic However, there's no need to dogmatically use non-async functions exclusively for handling internationalization—use what fits your app best. -In regard to performance, async functions and hooks can be used very much interchangeably. The configuration from [`i18n.ts`](/docs/usage/configuration#i18nts) is only loaded once upon first usage and both implementations use request-based caching internally where relevant. The only minor difference is that async functions have the benefit that rendering can be resumed right after an async function has been invoked. In contrast, in case a hook call triggers the initialization in `i18n.ts`, the component will suspend until the config is resolved and will re-render subsequently, possibly re-executing component logic prior to the hook call. However, once config has been resolved as part of a request, hooks will execute synchronously without suspending, resulting in less overhead in comparison to async functions since rendering can be resumed without having to wait for the microtask queue to flush (see [resuming a suspended component by replaying its execution](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#resuming-a-suspended-component-by-replaying-its-execution) in the corresponding React RFC). +Regarding performance, async functions and hooks can be used interchangeably. The configuration from [`i18n/request.ts`](/docs/usage/configuration#i18n-request) is only loaded once upon first usage and both implementations use request-based caching internally where relevant. The only minor difference is that async functions have the benefit that rendering can be resumed right after an async function has been invoked. In contrast, in case a hook call triggers the initialization in `i18n/request.ts`, the component will suspend until the config is resolved and will re-render subsequently, possibly re-executing component logic prior to the hook call. However, once config has been resolved as part of a request, hooks will execute synchronously without suspending, resulting in less overhead in comparison to async functions since rendering can be resumed without having to wait for the microtask queue to flush (see [resuming a suspended component by replaying its execution](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#resuming-a-suspended-component-by-replaying-its-execution) in the corresponding React RFC).
## Using internationalization in Client Components -Depending on your situation, you may need to handle internationalization in Client Components as well. While providing all messages to the client side is typically the easiest way to [get started](/docs/getting-started/app-router#layout) and a reasonable approach for many apps, you can be more selective about which messages are passed to the client side if you're interested in optimizing the performance of your app. +Depending on your situation, you may need to handle internationalization in Client Components. While providing all messages to the client side is typically the easiest way to [get started](/docs/getting-started/app-router#layout) and a reasonable approach for many apps, you can be more selective about which messages are passed to the client side if you're interested in optimizing the performance of your app.
How does loading messages on the client side relate to performance? @@ -298,7 +298,7 @@ import {NextIntlClientProvider, useMessages} from 'next-intl'; import ClientCounter from './ClientCounter'; export default function Counter() { - // Receive messages provided in `i18n.ts` … + // Receive messages provided in `i18n/request.ts` … const messages = useMessages(); return ( @@ -332,7 +332,7 @@ import {NextIntlClientProvider} from 'next-intl'; import {getMessages} from 'next-intl/server'; export default async function RootLayout(/* ... */) { - // Receive messages provided in `i18n.ts` + // Receive messages provided in `i18n/request.ts` const messages = await getMessages(); return ( diff --git a/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx b/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx index c735247d6..02248791e 100644 --- a/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx +++ b/docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx @@ -28,9 +28,10 @@ Now, we're going to create the following file structure: │ └── ... ├── next.config.mjs (2) └── src - ├── routing.ts (3) + ├── i18n + │ ├── routing.ts (3) + │ └── request.ts (5) ├── middleware.ts (4) - ├── i18n.ts (5) └── app └── [locale] ├── layout.tsx (6) @@ -93,7 +94,7 @@ module.exports = withNextIntl(nextConfig); -### `src/routing.ts` [#i18n-routing] +### `src/i18n/routing.ts` [#i18n-routing] We'll integrate with Next.js' routing in two places: @@ -104,7 +105,7 @@ This enables you to work with pathnames like `/about`, while i18n aspects like l To share the configuration between these two places, we'll set up `routing.ts`: -```ts filename="src/routing.ts" +```ts filename="src/i18n/routing.ts" import {defineRouting} from 'next-intl/routing'; import {createSharedPathnamesNavigation} from 'next-intl/navigation'; @@ -130,7 +131,7 @@ Once we have our routing configuration in place, we can use it to set up the mid ```tsx filename="src/middleware.ts" import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing); @@ -140,11 +141,11 @@ export const config = { }; ``` -### `src/i18n.ts` [#i18n-request] +### `src/i18n/request.ts` [#i18n-request] `next-intl` creates a request-scoped configuration object, which you can use to provide messages and other options based on the user's locale to Server Components. -```tsx filename="src/i18n.ts" +```tsx filename="src/i18n/request.ts" import {notFound} from 'next/navigation'; import {getRequestConfig} from 'next-intl/server'; import {routing} from './routing'; @@ -154,22 +155,22 @@ export default getRequestConfig(async ({locale}) => { if (!routing.locales.includes(locale as any)) notFound(); return { - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); ``` -
+
Can I move this file somewhere else? -This file is supported out-of-the-box as `./i18n.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`. +This file is supported out-of-the-box as `./i18n/request.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`. If you prefer to move this file somewhere else, you can optionally provide a path to the plugin: ```js filename="next.config.mjs" const withNextIntl = createNextIntlPlugin( // Specify a custom path here - './somewhere/else/i18n.ts' + './somewhere/else/request.ts' ); ``` @@ -177,7 +178,7 @@ const withNextIntl = createNextIntlPlugin( ### `src/app/[locale]/layout.tsx` [#layout] -The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n.ts` to Client Components via `NextIntlClientProvider`. +The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n/request.ts` to Client Components via `NextIntlClientProvider`. ```tsx filename="app/[locale]/layout.tsx" import {NextIntlClientProvider} from 'next-intl'; @@ -206,7 +207,7 @@ export default async function LocaleLayout({ } ``` -Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here, but `messages` need to be passed explicitly. +Note that `NextIntlClientProvider` automatically inherits configuration from `i18n/request.ts` here, but `messages` need to be passed explicitly. ### `src/app/[locale]/page.tsx` [#page] @@ -216,7 +217,7 @@ Now you can use translations and other functionality from `next-intl` in your co ```tsx filename="app/[locale]/page.tsx" import {useTranslations} from 'next-intl'; -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; export default function HomePage() { const t = useTranslations('HomePage'); @@ -264,11 +265,10 @@ When using the setup with i18n routing, `next-intl`will currently opt into dynam Since we are using a dynamic route segment for the `[locale]` param, we need to pass all possible values to Next.js via [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) so that the routes can be rendered at build time. ```tsx filename="app/[locale]/layout.tsx" -// Can be imported from a shared config -const locales = ['en', 'de']; +import {routing} from '@/i18n/routing'; export function generateStaticParams() { - return locales.map((locale) => ({locale})); + return routing.locales.map((locale) => ({locale})); } ``` @@ -306,7 +306,7 @@ export default function IndexPage({params: {locale}}) { **Keep in mind that:** -1. The locale that you pass to `unstable_setRequestLocale` should be validated (e.g. in [`i18n.ts`](/docs/usage/configuration#i18nts)). +1. The locale that you pass to `unstable_setRequestLocale` should be validated (e.g. in [`i18n/request.ts`](/docs/usage/configuration#i18n-request)). 2. You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently. diff --git a/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx b/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx index 3d656576c..ca9e5c38c 100644 --- a/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx +++ b/docs/pages/docs/getting-started/app-router/without-i18n-routing.mdx @@ -28,7 +28,8 @@ Now, we're going to create the following file structure: │ └── ... ├── next.config.mjs (2) └── src - ├── i18n.ts (3) + ├── i18n + │ └── request.tsx (3) └── app ├── layout.tsx (4) └── page.tsx (5) @@ -87,11 +88,11 @@ module.exports = withNextIntl(nextConfig); -### `i18n.ts` [#i18n-request] +### `i18n/request.ts` [#i18n-request] `next-intl` creates a request-scoped configuration object, which you can use to provide messages and other options based on the user's locale to Server Components. -```tsx filename="src/i18n.ts" +```tsx filename="src/i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async () => { @@ -101,15 +102,15 @@ export default getRequestConfig(async () => { return { locale, - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); ``` -
+
Can I move this file somewhere else? -This file is supported out-of-the-box as `./i18n.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`. +This file is supported out-of-the-box as `./i18n/request.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`. If you prefer to move this file somewhere else, you can optionally provide a path to the plugin: @@ -124,7 +125,7 @@ const withNextIntl = createNextIntlPlugin( ### `app/layout.tsx` [#layout] -The `locale` that was provided in `i18n.ts` is available via `getLocale` and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n.ts` to Client Components via `NextIntlClientProvider`. +The `locale` that was provided in `i18n/request.ts` is available via `getLocale` and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n/request.ts` to Client Components via `NextIntlClientProvider`. ```tsx filename="app/layout.tsx" import {NextIntlClientProvider} from 'next-intl'; @@ -153,7 +154,7 @@ export default async function RootLayout({ } ``` -Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here, but `messages` need to be passed explicitly. +Note that `NextIntlClientProvider` automatically inherits configuration from `i18n/request.ts` here, but `messages` need to be passed explicitly. ### `app/page.tsx` [#page] diff --git a/docs/pages/docs/routing.mdx b/docs/pages/docs/routing.mdx index bcac0c2fc..d4054d941 100644 --- a/docs/pages/docs/routing.mdx +++ b/docs/pages/docs/routing.mdx @@ -22,7 +22,7 @@ This enables you to express your app in terms of APIs like ` The routing configuration that is shared between the [middleware](/docs/routing/middleware) and [the navigation APIs](/docs/routing/navigation) can be defined with the `defineRouting` function. -```tsx filename="src/routing.ts" +```tsx filename="src/i18n/routing.ts" import {defineRouting} from 'next-intl/routing'; export const routing = defineRouting({ diff --git a/docs/pages/docs/routing/middleware.mdx b/docs/pages/docs/routing/middleware.mdx index a66cbfe84..bc19969fd 100644 --- a/docs/pages/docs/routing/middleware.mdx +++ b/docs/pages/docs/routing/middleware.mdx @@ -16,7 +16,7 @@ The middleware receives a [`routing`](/docs/routing#define-routing) configuratio ```tsx filename="middleware.ts" import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing); @@ -122,7 +122,7 @@ If you want to rely entirely on the URL to resolve the locale, you can set the ` ```tsx filename="middleware.ts" {5} import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing, { localeDetection: false @@ -146,7 +146,7 @@ In this case, you can opt-out of this behavior by setting `alternateLinks` to `f ```tsx filename="middleware.ts" {5} import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing, { alternateLinks: false // Defaults to `true` @@ -186,7 +186,7 @@ If you need to customize the alternate links, you can either turn them off and p import createMiddleware from 'next-intl/middleware'; import LinkHeader from 'http-link-header'; import {NextRequest} from 'next/server'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; const handleI18nRouting = createMiddleware(routing); @@ -230,7 +230,7 @@ A [Next.js `matcher`](https://nextjs.org/docs/app/building-your-application/rout ```tsx filename="middleware.ts" import {NextRequest} from 'next/server'; import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; const handleI18nRouting = createMiddleware(routing); @@ -386,7 +386,7 @@ Note that if you use a [`localePrefix`](/docs/routing#locale-prefix) other than ```tsx filename="middleware.ts" import {clerkMiddleware, createRouteMatcher} from '@clerk/nextjs/server'; import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; const handleI18nRouting = createMiddleware(routing); @@ -453,7 +453,7 @@ Now, we can integrate the Supabase middleware with the one from `next-intl`: ```tsx filename="middleware.ts" import createMiddleware from 'next-intl/middleware'; import {type NextRequest} from 'next/server'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; import {updateSession} from './utils/supabase/middleware'; const handleI18nRouting = createMiddleware(routing); @@ -482,7 +482,7 @@ For pathnames specified in [the `pages` object](https://next-auth.js.org/configu import {withAuth} from 'next-auth/middleware'; import createMiddleware from 'next-intl/middleware'; import {NextRequest} from 'next/server'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; const publicPages = ['/', '/login']; @@ -575,7 +575,7 @@ To recover from this error, please make sure that: 2. If you're using a setup _with_ i18n routing: 1. You're using APIs from `next-intl` (including [the navigation APIs](/docs/routing/navigation)) exclusively within the `[locale]` segment. 2. Your [middleware matcher](#matcher-config) matches all routes of your application, including dynamic segments with potentially unexpected characters like dots (e.g. `/users/jane.doe`). - 3. If you're using [`localePrefix: 'as-needed'`](/docs/routing#locale-prefix-as-needed), the `locale` segment effectively acts like a catch-all for all unknown routes. You should make sure that the `locale` is [validated](/docs/usage/configuration#i18nts) before it's used by any APIs from `next-intl`. + 3. If you're using [`localePrefix: 'as-needed'`](/docs/routing#locale-prefix-as-needed), the `locale` segment effectively acts like a catch-all for all unknown routes. You should make sure that the `locale` is [validated](/docs/usage/configuration#i18n-request) before it's used by any APIs from `next-intl`. 4. To implement static rendering, make sure to [provide a static locale](/docs/getting-started/app-router/with-i18n-routing#static-rendering) to `next-intl` instead of using `force-static`. 3. If you're using using a setup _without_ i18n routing: 1. You don't read the `locale` param in `getRequestConfig` but instead return it. diff --git a/docs/pages/docs/routing/navigation.mdx b/docs/pages/docs/routing/navigation.mdx index 699bae7ff..118e3af13 100644 --- a/docs/pages/docs/routing/navigation.mdx +++ b/docs/pages/docs/routing/navigation.mdx @@ -16,7 +16,7 @@ Depending on if you're using the [`pathnames`](/docs/routing#pathnames) setting, - `createSharedPathnamesNavigation`: Pathnames are shared across all locales (default) - `createLocalizedPathnamesNavigation`: Pathnames are provided per locale (use with `pathnames`) -These functions are typically called in a central module like [`src/routing.ts`](/docs/getting-started/app-router/with-i18n-routing#i18n-routing) in order to provide easy access to navigation APIs in your components and should receive a [`routing`](/docs/routing) configuration that is shared with the middleware. +These functions are typically called in a central module like [`src/i18n/routing.ts`](/docs/getting-started/app-router/with-i18n-routing#i18n-routing) in order to provide easy access to navigation APIs in your components and should receive a [`routing`](/docs/routing) configuration that is shared with the middleware. @@ -95,7 +95,7 @@ This component wraps [`next/link`](https://nextjs.org/docs/app/api-reference/com ```tsx -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; // When the user is on `/en`, the link will point to `/en/about` About @@ -119,7 +119,7 @@ The [`useSelectedLayoutSegment` hook](https://nextjs.org/docs/app/api-reference/ import {useSelectedLayoutSegment} from 'next/navigation'; import {ComponentProps} from 'react'; -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; export default function NavigationLink({ href, @@ -157,7 +157,7 @@ See also the Next.js docs on [creating an active link component](https://nextjs. When using [localized pathnames](/docs/routing#pathnames), the `href` prop corresponds to an internal pathname, but will be mapped to a locale-specific pathname. ```tsx -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; // When the user is on `/de`, the link will point to `/de/ueber-uns` About @@ -192,7 +192,7 @@ If you need to create a component that receives an `href` prop that is forwarded ```tsx filename="StyledLink.tsx" import {ComponentProps} from 'react'; -import {Link, pathnames} from '@/routing'; +import {Link, pathnames} from '@/i18n/routing'; export default function StyledLink({ color, @@ -217,7 +217,7 @@ To ensure that only valid pathnames can be passed to the component, we can accep import {useSelectedLayoutSegment} from 'next/navigation'; import {ComponentProps} from 'react'; -import {Link, pathnames} from '@/routing'; +import {Link, pathnames} from '@/i18n/routing'; export default function NavigationLink< Pathname extends keyof typeof pathnames @@ -291,7 +291,7 @@ If you need to navigate programmatically, e.g. in an event handler, `next-intl` ```tsx 'use client'; -import {useRouter} from '@/routing'; +import {useRouter} from '@/i18n/routing'; const router = useRouter(); @@ -314,7 +314,7 @@ By combining [`usePathname`](#usepathname) with [`useRouter`](#userouter), you c ```tsx 'use client'; -import {usePathname, useRouter} from '@/routing'; +import {usePathname, useRouter} from '@/i18n/routing'; const pathname = usePathname(); const router = useRouter(); @@ -331,7 +331,7 @@ When using [localized pathnames](/docs/routing#pathnames), the provided `href` c ```tsx 'use client'; -import {useRouter} from '@/routing'; +import {useRouter} from '@/i18n/routing'; const router = useRouter(); @@ -364,7 +364,7 @@ Note that if you have dynamic params on some routes, you should pass those as we ```tsx 'use client'; -import {usePathname, useRouter} from '@/routing'; +import {usePathname, useRouter} from '@/i18n/routing'; import {useParams} from 'next/navigation'; const pathname = usePathname(); @@ -394,7 +394,7 @@ To retrieve the pathname without a potential locale prefix, you can call `usePat ```tsx 'use client'; -import {usePathname} from '@/routing'; +import {usePathname} from '@/i18n/routing'; // When the user is on `/en`, this will be `/` const pathname = usePathname(); @@ -408,7 +408,7 @@ When using [localized pathnames](/docs/routing#pathnames), the returned pathname ```tsx 'use client'; -import {usePathname} from '@/routing'; +import {usePathname} from '@/i18n/routing'; // When the user is on `/de/ueber-uns`, this will be `/about` const pathname = usePathname(); @@ -427,7 +427,7 @@ If you want to interrupt the render and redirect to another page, you can invoke ```tsx -import {redirect} from '@/routing'; +import {redirect} from '@/i18n/routing'; // When the user is on `/en`, this will be `/en/login` redirect('/login'); @@ -442,7 +442,7 @@ router.push('/users/12'); When using [localized pathnames](/docs/routing#pathnames), the provided `href` corresponds to an internal pathname, but will be mapped to a locale-specific pathname. ```tsx -import {redirect} from '@/routing'; +import {redirect} from '@/i18n/routing'; // When the user is on `/en`, this will be `/en/login` redirect('/login'); @@ -481,7 +481,7 @@ If you need to construct a particular pathname based on a locale, you can call t ```tsx filename="page.tsx" -import {getPathname} from '@/routing'; +import {getPathname} from '@/i18n/routing'; export async function generateMetadata({params: {locale}}) { // Example: This page accepts search params like `?sort=asc`. diff --git a/docs/pages/docs/usage/configuration.mdx b/docs/pages/docs/usage/configuration.mdx index a1102a559..70f0f5399 100644 --- a/docs/pages/docs/usage/configuration.mdx +++ b/docs/pages/docs/usage/configuration.mdx @@ -9,29 +9,27 @@ Configuration properties that you use across your Next.js app can be set globall ## Client- and Server Components [#client-server-components] -Depending on if you handle [internationalization in Server- or Client Components](/docs/environments/server-client-components), the configuration from `i18n.ts` or `NextIntlClientProvider` will be applied respectively. +Depending on if you handle [internationalization in Server- or Client Components](/docs/environments/server-client-components), the configuration from `i18n/request.ts` or `NextIntlClientProvider` will be applied respectively. -### `i18n.ts` & `getRequestConfig` +### `i18n/request.ts` & `getRequestConfig` [#i18n-request] -`i18n.ts` can be used to provide configuration for **Server Components** via the `getRequestConfig` function and should be set up based on whether you're using [i18n routing](/docs/getting-started/app-router) or not. +`i18n/request.ts` can be used to provide configuration for **server-only** code, i.e. Server Components, Server Actions & friends. The configuration is provided via the `getRequestConfig` function and needs to be set up based on whether you're using [i18n routing](/docs/getting-started/app-router) or not. -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {notFound} from 'next/navigation'; import {getRequestConfig} from 'next-intl/server'; - -// Can be imported from a shared config -const locales = ['en', 'de']; +import {routing} from '@/i18n/routing'; export default getRequestConfig(async ({locale}) => { // Validate that the incoming `locale` parameter is valid - if (!locales.includes(locale as any)) notFound(); + if (!routing.locales.includes(locale as any)) notFound(); return { - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); ``` @@ -39,7 +37,7 @@ export default getRequestConfig(async ({locale}) => { -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async () => { @@ -49,7 +47,7 @@ export default getRequestConfig(async () => { return { locale, - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); ``` @@ -61,6 +59,22 @@ The configuration object is created once for each request by internally using Re Since this function is executed during the Server Components render pass, you can call functions like [`cookies()`](https://nextjs.org/docs/app/api-reference/functions/cookies) and [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) to return configuration that is request-specific. +
+Can I move this file somewhere else? + +This file is supported out-of-the-box as `./i18n/request.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`. + +If you prefer to move this file somewhere else, you can optionally provide a path to the plugin: + +```js filename="next.config.mjs" +const withNextIntl = createNextIntlPlugin( + // Specify a custom path here + './somewhere/else/request.ts' +); +``` + +
+ ### `NextIntlClientProvider` `NextIntlClientProvider` can be used to provide configuration for **Client Components**. @@ -189,17 +203,17 @@ Colocating your messages with app code is beneficial because it allows developer That being said, `next-intl` is agnostic to how you store messages and allows you to freely define an async function that fetches them while your app renders: - + -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async ({locale}) => { // ... return { - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); ``` @@ -226,7 +240,7 @@ import {NextIntlClientProvider} from 'next-intl'; import {getMessages} from 'next-intl/server'; async function Component({children}) { - // Read messages configured via `i18n.ts` + // Read messages configured via `i18n/request.ts` const messages = await getMessages(); return ( @@ -293,10 +307,10 @@ Note that [the VSCode integration for `next-intl`](/docs/workflows/vscode-integr Specifying a time zone affects the rendering of dates and times. By default, the time zone of the server runtime will be used, but can be customized as necessary. - + -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async ({locale}) => { @@ -351,10 +365,10 @@ When formatting [relative dates and times](/docs/usage/dates-times#formatting-re If you prefer to override the default, you can provide an explicit value for `now`: - + -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async ({locale}) => { @@ -403,10 +417,10 @@ Component. To achieve consistent date, time, number and list formatting, you can define a set of global formats. - + -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async ({locale}) => { @@ -510,10 +524,10 @@ function Component() { To achieve consistent usage of translation values and reduce redundancy, you can define a set of global default values. This configuration can also be used to apply consistent styling of commonly used rich text elements. - + -```tsx filename="i18n.tsx" +```tsx filename="i18n/request.tsx" import {getRequestConfig} from 'next-intl/server'; export default getRequestConfig(async ({locale}) => { @@ -555,10 +569,10 @@ By default, when a message fails to resolve or when the formatting failed, an er This behavior can be customized with the `onError` and `getMessageFallback` configuration option. - + -```tsx filename="i18n.ts" +```tsx filename="i18n/request.ts" import {getRequestConfig} from 'next-intl/server'; import {IntlErrorCode} from 'next-intl'; diff --git a/docs/pages/docs/workflows/linting.mdx b/docs/pages/docs/workflows/linting.mdx index 1c207b168..b54232649 100644 --- a/docs/pages/docs/workflows/linting.mdx +++ b/docs/pages/docs/workflows/linting.mdx @@ -21,23 +21,23 @@ Be careful though that this doesn't catch hardcoded attributes (e.g. `aria-label If you are using [i18n routing](/docs/getting-started/app-router), you might want to ensure that developers consistently use the [navigation APIs](/docs/routing/navigation) that you've configured in your project. -In this example, developers will be prompted to import from `@/routing` when they try to import navigation APIs from Next.js. +In this example, developers will be prompted to import from `@/i18n/routing` when they try to import navigation APIs from Next.js. ```javascript filename="eslint.config.js" // ... rules: { - // Consistently import navigation APIs from `@/routing` + // Consistently import navigation APIs from `@/i18n/routing` 'no-restricted-imports': [ 'error', { name: 'next/link', - message: 'Please import from `@/routing` instead.' + message: 'Please import from `@/i18n/routing` instead.' }, { name: 'next/navigation', importNames: ['redirect', 'permanentRedirect', 'useRouter', 'usePathname'], - message: 'Please import from `@/routing` instead.' + message: 'Please import from `@/i18n/routing` instead.' } ] } diff --git a/examples/example-app-router-migration/src/app/[locale]/page.tsx b/examples/example-app-router-migration/src/app/[locale]/page.tsx index 7d9835925..ce3510ecb 100644 --- a/examples/example-app-router-migration/src/app/[locale]/page.tsx +++ b/examples/example-app-router-migration/src/app/[locale]/page.tsx @@ -1,7 +1,7 @@ import {useLocale, useTranslations} from 'next-intl'; import LocaleSwitcher from '@/components/LocaleSwitcher'; import PageLayout from '@/components/PageLayout'; -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; export default function Index() { const t = useTranslations('Index'); diff --git a/examples/example-app-router-migration/src/i18n.ts b/examples/example-app-router-migration/src/i18n/request.ts similarity index 82% rename from examples/example-app-router-migration/src/i18n.ts rename to examples/example-app-router-migration/src/i18n/request.ts index 5f6998781..c65915183 100644 --- a/examples/example-app-router-migration/src/i18n.ts +++ b/examples/example-app-router-migration/src/i18n/request.ts @@ -7,6 +7,6 @@ export default getRequestConfig(async ({locale}) => { if (!routing.locales.includes(locale as any)) notFound(); return { - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); diff --git a/examples/example-app-router-migration/src/routing.ts b/examples/example-app-router-migration/src/i18n/routing.ts similarity index 100% rename from examples/example-app-router-migration/src/routing.ts rename to examples/example-app-router-migration/src/i18n/routing.ts diff --git a/examples/example-app-router-migration/src/middleware.ts b/examples/example-app-router-migration/src/middleware.ts index 9d12dbc77..5cc4efb87 100644 --- a/examples/example-app-router-migration/src/middleware.ts +++ b/examples/example-app-router-migration/src/middleware.ts @@ -1,5 +1,5 @@ import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing); diff --git a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/NavLink.tsx b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/NavLink.tsx index f3283b4c9..ee28bc4ac 100644 --- a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/NavLink.tsx +++ b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/NavLink.tsx @@ -2,7 +2,7 @@ import {useSelectedLayoutSegment} from 'next/navigation'; import {ComponentProps} from 'react'; -import {Link} from '@/routing.public'; +import {Link} from '@/i18n/routing.public'; export default function NavLink({href, ...rest}: ComponentProps) { const selectedLayoutSegment = useSelectedLayoutSegment(); diff --git a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx index b1406d662..45ce1900d 100644 --- a/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx +++ b/examples/example-app-router-mixed-routing/src/app/(public)/[locale]/PublicNavigationLocaleSwitcher.tsx @@ -2,7 +2,7 @@ import {useLocale} from 'next-intl'; import {Locale} from '@/config'; -import {Link, usePathname} from '@/routing.public'; +import {Link, usePathname} from '@/i18n/routing.public'; export default function PublicNavigationLocaleSwitcher() { return ( diff --git a/examples/example-app-router-mixed-routing/src/i18n.ts b/examples/example-app-router-mixed-routing/src/i18n/request.ts similarity index 85% rename from examples/example-app-router-mixed-routing/src/i18n.ts rename to examples/example-app-router-mixed-routing/src/i18n/request.ts index bef470448..5af925362 100644 --- a/examples/example-app-router-mixed-routing/src/i18n.ts +++ b/examples/example-app-router-mixed-routing/src/i18n/request.ts @@ -1,15 +1,15 @@ import {headers} from 'next/headers'; import {notFound} from 'next/navigation'; import {getRequestConfig} from 'next-intl/server'; -import {locales} from './config'; -import {getUserLocale} from './db'; +import {locales} from '../config'; +import {getUserLocale} from '../db'; async function getConfig(locale: string) { // Validate that the incoming `locale` parameter is valid if (!locales.includes(locale as any)) notFound(); return { - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; } diff --git a/examples/example-app-router-mixed-routing/src/routing.public.ts b/examples/example-app-router-mixed-routing/src/i18n/routing.public.ts similarity index 86% rename from examples/example-app-router-mixed-routing/src/routing.public.ts rename to examples/example-app-router-mixed-routing/src/i18n/routing.public.ts index dd20155a9..02bf73034 100644 --- a/examples/example-app-router-mixed-routing/src/routing.public.ts +++ b/examples/example-app-router-mixed-routing/src/i18n/routing.public.ts @@ -1,6 +1,6 @@ import {createSharedPathnamesNavigation} from 'next-intl/navigation'; import {defineRouting} from 'next-intl/routing'; -import {locales, defaultLocale} from './config'; +import {locales, defaultLocale} from '../config'; export const routing = defineRouting({ locales, diff --git a/examples/example-app-router-mixed-routing/src/middleware.ts b/examples/example-app-router-mixed-routing/src/middleware.ts index b5821579a..01134cf75 100644 --- a/examples/example-app-router-mixed-routing/src/middleware.ts +++ b/examples/example-app-router-mixed-routing/src/middleware.ts @@ -1,6 +1,6 @@ import {NextRequest, NextResponse} from 'next/server'; import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing.public'; +import {routing} from './i18n/routing.public'; export default function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; diff --git a/examples/example-app-router-next-auth/src/components/LocaleSwitcher.tsx b/examples/example-app-router-next-auth/src/components/LocaleSwitcher.tsx index d482e0321..8efe94f42 100644 --- a/examples/example-app-router-next-auth/src/components/LocaleSwitcher.tsx +++ b/examples/example-app-router-next-auth/src/components/LocaleSwitcher.tsx @@ -1,5 +1,5 @@ import {useLocale, useTranslations} from 'next-intl'; -import {Link, usePathname} from '@/routing'; +import {Link, usePathname} from '@/i18n/routing'; export default function LocaleSwitcher() { const t = useTranslations('LocaleSwitcher'); diff --git a/examples/example-app-router-next-auth/src/i18n.ts b/examples/example-app-router-next-auth/src/i18n/request.ts similarity index 82% rename from examples/example-app-router-next-auth/src/i18n.ts rename to examples/example-app-router-next-auth/src/i18n/request.ts index 5f6998781..c65915183 100644 --- a/examples/example-app-router-next-auth/src/i18n.ts +++ b/examples/example-app-router-next-auth/src/i18n/request.ts @@ -7,6 +7,6 @@ export default getRequestConfig(async ({locale}) => { if (!routing.locales.includes(locale as any)) notFound(); return { - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); diff --git a/examples/example-app-router-next-auth/src/routing.ts b/examples/example-app-router-next-auth/src/i18n/routing.ts similarity index 100% rename from examples/example-app-router-next-auth/src/routing.ts rename to examples/example-app-router-next-auth/src/i18n/routing.ts diff --git a/examples/example-app-router-next-auth/src/middleware.ts b/examples/example-app-router-next-auth/src/middleware.ts index 0d6469ea5..500a469ee 100644 --- a/examples/example-app-router-next-auth/src/middleware.ts +++ b/examples/example-app-router-next-auth/src/middleware.ts @@ -1,7 +1,7 @@ import {NextRequest} from 'next/server'; import {withAuth} from 'next-auth/middleware'; import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; const publicPages = [ '/', diff --git a/examples/example-app-router-playground/next.config.mjs b/examples/example-app-router-playground/next.config.mjs index ebcc612c3..f30c3b7f9 100644 --- a/examples/example-app-router-playground/next.config.mjs +++ b/examples/example-app-router-playground/next.config.mjs @@ -3,7 +3,7 @@ import mdxPlugin from '@next/mdx'; import createNextIntlPlugin from 'next-intl/plugin'; -const withNextIntl = createNextIntlPlugin('./src/i18n.tsx'); +const withNextIntl = createNextIntlPlugin('./src/i18n/request.tsx'); const withMdx = mdxPlugin(); export default withMdx( diff --git a/examples/example-app-router-playground/src/app/[locale]/client/ClientContent.tsx b/examples/example-app-router-playground/src/app/[locale]/client/ClientContent.tsx index fef183bf5..19ed49fe1 100644 --- a/examples/example-app-router-playground/src/app/[locale]/client/ClientContent.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/client/ClientContent.tsx @@ -1,7 +1,7 @@ 'use client'; import {useNow, useTimeZone, useLocale} from 'next-intl'; -import {Link, usePathname} from '@/routing'; +import {Link, usePathname} from '@/i18n/routing'; export default function ClientContent() { const now = useNow(); diff --git a/examples/example-app-router-playground/src/app/[locale]/client/redirect/page.tsx b/examples/example-app-router-playground/src/app/[locale]/client/redirect/page.tsx index afce3b8e6..0cfe7fff9 100644 --- a/examples/example-app-router-playground/src/app/[locale]/client/redirect/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/client/redirect/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import {redirect} from '@/routing'; +import {redirect} from '@/i18n/routing'; export default function ClientRedirectPage() { redirect('/client'); diff --git a/examples/example-app-router-playground/src/app/[locale]/nested/UnlocalizedPathname.tsx b/examples/example-app-router-playground/src/app/[locale]/nested/UnlocalizedPathname.tsx index e13c24961..2358b342e 100644 --- a/examples/example-app-router-playground/src/app/[locale]/nested/UnlocalizedPathname.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/nested/UnlocalizedPathname.tsx @@ -1,6 +1,6 @@ 'use client'; -import {usePathname} from '@/routing'; +import {usePathname} from '@/i18n/routing'; export default function UnlocalizedPathname() { return

{usePathname()}

; diff --git a/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx b/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx index eb16409ab..46c94b8ad 100644 --- a/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx @@ -1,6 +1,6 @@ import {Metadata} from 'next'; import {useTranslations} from 'next-intl'; -import {getPathname, routing, Locale} from '@/routing'; +import {getPathname, routing, Locale} from '@/i18n/routing'; type Props = { params: { diff --git a/examples/example-app-router-playground/src/app/[locale]/page.tsx b/examples/example-app-router-playground/src/app/[locale]/page.tsx index 9faf53f04..47ad4b27b 100644 --- a/examples/example-app-router-playground/src/app/[locale]/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/page.tsx @@ -11,7 +11,7 @@ import LocaleSwitcher from '../../components/LocaleSwitcher'; import PageLayout from '../../components/PageLayout'; import MessagesAsPropsCounter from '../../components/client/01-MessagesAsPropsCounter'; import MessagesOnClientCounter from '../../components/client/02-MessagesOnClientCounter'; -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; type Props = { searchParams: Record; diff --git a/examples/example-app-router-playground/src/app/[locale]/redirect/page.tsx b/examples/example-app-router-playground/src/app/[locale]/redirect/page.tsx index 1a1164218..970f7ab2c 100644 --- a/examples/example-app-router-playground/src/app/[locale]/redirect/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/redirect/page.tsx @@ -1,4 +1,4 @@ -import {redirect} from '@/routing'; +import {redirect} from '@/i18n/routing'; export default function Redirect() { redirect('/client'); diff --git a/examples/example-app-router-playground/src/components/ClientLink.tsx b/examples/example-app-router-playground/src/components/ClientLink.tsx index 882587511..108e23321 100644 --- a/examples/example-app-router-playground/src/components/ClientLink.tsx +++ b/examples/example-app-router-playground/src/components/ClientLink.tsx @@ -1,7 +1,7 @@ 'use client'; import {ComponentProps} from 'react'; -import {Link, Pathnames} from '@/routing'; +import {Link, Pathnames} from '@/i18n/routing'; export default function NavigationLink( props: ComponentProps> diff --git a/examples/example-app-router-playground/src/components/ClientRouterWithoutProvider.tsx b/examples/example-app-router-playground/src/components/ClientRouterWithoutProvider.tsx index 65109f278..ba8aa2fac 100644 --- a/examples/example-app-router-playground/src/components/ClientRouterWithoutProvider.tsx +++ b/examples/example-app-router-playground/src/components/ClientRouterWithoutProvider.tsx @@ -1,6 +1,6 @@ 'use client'; -import {useRouter} from '@/routing'; +import {useRouter} from '@/i18n/routing'; export default function ClientRouterWithoutProvider() { const router = useRouter(); diff --git a/examples/example-app-router-playground/src/components/LocaleSwitcher.tsx b/examples/example-app-router-playground/src/components/LocaleSwitcher.tsx index a2ee117d0..c0ae98b8e 100644 --- a/examples/example-app-router-playground/src/components/LocaleSwitcher.tsx +++ b/examples/example-app-router-playground/src/components/LocaleSwitcher.tsx @@ -1,5 +1,5 @@ import {useLocale, useTranslations} from 'next-intl'; -import {Link} from '@/routing'; +import {Link} from '@/i18n/routing'; export default function LocaleSwitcher() { const t = useTranslations('LocaleSwitcher'); diff --git a/examples/example-app-router-playground/src/components/NavigationLink.tsx b/examples/example-app-router-playground/src/components/NavigationLink.tsx index 898953d08..55d4302b0 100644 --- a/examples/example-app-router-playground/src/components/NavigationLink.tsx +++ b/examples/example-app-router-playground/src/components/NavigationLink.tsx @@ -2,7 +2,7 @@ import {useSelectedLayoutSegment} from 'next/navigation'; import {ComponentProps} from 'react'; -import {Link, Pathnames} from '@/routing'; +import {Link, Pathnames} from '@/i18n/routing'; export default function NavigationLink({ href, diff --git a/examples/example-app-router-playground/src/i18n.tsx b/examples/example-app-router-playground/src/i18n/request.tsx similarity index 91% rename from examples/example-app-router-playground/src/i18n.tsx rename to examples/example-app-router-playground/src/i18n/request.tsx index bae27675e..dbb30daed 100644 --- a/examples/example-app-router-playground/src/i18n.tsx +++ b/examples/example-app-router-playground/src/i18n/request.tsx @@ -1,7 +1,7 @@ import {headers} from 'next/headers'; import {notFound} from 'next/navigation'; import {getRequestConfig} from 'next-intl/server'; -import defaultMessages from '../messages/en.json'; +import defaultMessages from '../../messages/en.json'; import {routing} from './routing'; export default getRequestConfig(async ({locale}) => { @@ -10,7 +10,8 @@ export default getRequestConfig(async ({locale}) => { const now = headers().get('x-now'); const timeZone = headers().get('x-time-zone') ?? 'Europe/Vienna'; - const localeMessages = (await import(`../messages/${locale}.json`)).default; + const localeMessages = (await import(`../../messages/${locale}.json`)) + .default; const messages = {...defaultMessages, ...localeMessages}; return { diff --git a/examples/example-app-router-playground/src/routing.ts b/examples/example-app-router-playground/src/i18n/routing.ts similarity index 100% rename from examples/example-app-router-playground/src/routing.ts rename to examples/example-app-router-playground/src/i18n/routing.ts diff --git a/examples/example-app-router-playground/src/middleware.ts b/examples/example-app-router-playground/src/middleware.ts index a89f3d46a..9844a0d5d 100644 --- a/examples/example-app-router-playground/src/middleware.ts +++ b/examples/example-app-router-playground/src/middleware.ts @@ -1,5 +1,5 @@ import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing); diff --git a/examples/example-app-router-single-locale/src/i18n.ts b/examples/example-app-router-single-locale/src/i18n/request.ts similarity index 68% rename from examples/example-app-router-single-locale/src/i18n.ts rename to examples/example-app-router-single-locale/src/i18n/request.ts index 0f89abd1f..447a6b92c 100644 --- a/examples/example-app-router-single-locale/src/i18n.ts +++ b/examples/example-app-router-single-locale/src/i18n/request.ts @@ -5,6 +5,6 @@ export default getRequestConfig(async () => { return { locale, - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); diff --git a/examples/example-app-router-without-i18n-routing/src/components/LocaleSwitcherSelect.tsx b/examples/example-app-router-without-i18n-routing/src/components/LocaleSwitcherSelect.tsx index 18c8f6974..e325bbb31 100644 --- a/examples/example-app-router-without-i18n-routing/src/components/LocaleSwitcherSelect.tsx +++ b/examples/example-app-router-without-i18n-routing/src/components/LocaleSwitcherSelect.tsx @@ -4,7 +4,7 @@ import {CheckIcon, LanguageIcon} from '@heroicons/react/24/solid'; import * as Select from '@radix-ui/react-select'; import clsx from 'clsx'; import {useTransition} from 'react'; -import {Locale} from '@/config'; +import {Locale} from '@/i18n/config'; import {setUserLocale} from '@/services/locale'; type Props = { diff --git a/examples/example-app-router-without-i18n-routing/src/config.ts b/examples/example-app-router-without-i18n-routing/src/i18n/config.ts similarity index 100% rename from examples/example-app-router-without-i18n-routing/src/config.ts rename to examples/example-app-router-without-i18n-routing/src/i18n/config.ts diff --git a/examples/example-app-router-without-i18n-routing/src/i18n.ts b/examples/example-app-router-without-i18n-routing/src/i18n/request.ts similarity index 58% rename from examples/example-app-router-without-i18n-routing/src/i18n.ts rename to examples/example-app-router-without-i18n-routing/src/i18n/request.ts index ecc942538..e9ada4619 100644 --- a/examples/example-app-router-without-i18n-routing/src/i18n.ts +++ b/examples/example-app-router-without-i18n-routing/src/i18n/request.ts @@ -1,11 +1,11 @@ import {getRequestConfig} from 'next-intl/server'; -import {getUserLocale} from './services/locale'; +import {getUserLocale} from '../services/locale'; export default getRequestConfig(async () => { const locale = await getUserLocale(); return { locale, - messages: (await import(`../messages/${locale}.json`)).default + messages: (await import(`../../messages/${locale}.json`)).default }; }); diff --git a/examples/example-app-router-without-i18n-routing/src/services/locale.ts b/examples/example-app-router-without-i18n-routing/src/services/locale.ts index 3f271dbd2..0b2360950 100644 --- a/examples/example-app-router-without-i18n-routing/src/services/locale.ts +++ b/examples/example-app-router-without-i18n-routing/src/services/locale.ts @@ -1,7 +1,7 @@ 'use server'; import {cookies} from 'next/headers'; -import {Locale, defaultLocale} from '@/config'; +import {Locale, defaultLocale} from '@/i18n/config'; // In this example the locale is read from a cookie. You could alternatively // also read it from a database, backend service, or any other source. diff --git a/examples/example-app-router/src/app/[locale]/layout.tsx b/examples/example-app-router/src/app/[locale]/layout.tsx index 2a10fb4e1..d5f7ab421 100644 --- a/examples/example-app-router/src/app/[locale]/layout.tsx +++ b/examples/example-app-router/src/app/[locale]/layout.tsx @@ -8,7 +8,7 @@ import { } from 'next-intl/server'; import {ReactNode} from 'react'; import Navigation from '@/components/Navigation'; -import {routing} from '@/routing'; +import {routing} from '@/i18n/routing'; const inter = Inter({subsets: ['latin']}); diff --git a/examples/example-app-router/src/app/sitemap.ts b/examples/example-app-router/src/app/sitemap.ts index a8d40237f..7ae7ca2eb 100644 --- a/examples/example-app-router/src/app/sitemap.ts +++ b/examples/example-app-router/src/app/sitemap.ts @@ -1,6 +1,6 @@ import {MetadataRoute} from 'next'; import {host} from '@/config'; -import {Locale, getPathname, routing} from '@/routing'; +import {Locale, getPathname, routing} from '@/i18n/routing'; export default function sitemap(): MetadataRoute.Sitemap { return [getEntry('/'), getEntry('/pathnames')]; diff --git a/examples/example-app-router/src/components/LocaleSwitcher.tsx b/examples/example-app-router/src/components/LocaleSwitcher.tsx index 8463a6643..2b88afc23 100644 --- a/examples/example-app-router/src/components/LocaleSwitcher.tsx +++ b/examples/example-app-router/src/components/LocaleSwitcher.tsx @@ -1,6 +1,6 @@ import {useLocale, useTranslations} from 'next-intl'; import LocaleSwitcherSelect from './LocaleSwitcherSelect'; -import {routing} from '@/routing'; +import {routing} from '@/i18n/routing'; export default function LocaleSwitcher() { const t = useTranslations('LocaleSwitcher'); diff --git a/examples/example-app-router/src/components/LocaleSwitcherSelect.tsx b/examples/example-app-router/src/components/LocaleSwitcherSelect.tsx index 0e35f76ea..051a36f61 100644 --- a/examples/example-app-router/src/components/LocaleSwitcherSelect.tsx +++ b/examples/example-app-router/src/components/LocaleSwitcherSelect.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; import {useParams} from 'next/navigation'; import {ChangeEvent, ReactNode, useTransition} from 'react'; -import {Locale, usePathname, useRouter} from '@/routing'; +import {Locale, usePathname, useRouter} from '@/i18n/routing'; type Props = { children: ReactNode; diff --git a/examples/example-app-router/src/components/NavigationLink.tsx b/examples/example-app-router/src/components/NavigationLink.tsx index e5db16ef2..5a25b474d 100644 --- a/examples/example-app-router/src/components/NavigationLink.tsx +++ b/examples/example-app-router/src/components/NavigationLink.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; import {useSelectedLayoutSegment} from 'next/navigation'; import {ComponentProps} from 'react'; -import {Link, Pathnames} from '@/routing'; +import {Link, Pathnames} from '@/i18n/routing'; export default function NavigationLink({ href, diff --git a/examples/example-app-router/src/i18n.ts b/examples/example-app-router/src/i18n/request.ts similarity index 82% rename from examples/example-app-router/src/i18n.ts rename to examples/example-app-router/src/i18n/request.ts index 03895176a..b04a04503 100644 --- a/examples/example-app-router/src/i18n.ts +++ b/examples/example-app-router/src/i18n/request.ts @@ -10,8 +10,8 @@ export default getRequestConfig(async ({locale}) => { messages: ( await (locale === 'en' ? // When using Turbopack, this will enable HMR for `en` - import('../messages/en.json') - : import(`../messages/${locale}.json`)) + import('../../messages/en.json') + : import(`../../messages/${locale}.json`)) ).default }; }); diff --git a/examples/example-app-router/src/routing.ts b/examples/example-app-router/src/i18n/routing.ts similarity index 100% rename from examples/example-app-router/src/routing.ts rename to examples/example-app-router/src/i18n/routing.ts diff --git a/examples/example-app-router/src/middleware.ts b/examples/example-app-router/src/middleware.ts index 69b07db77..b25094067 100644 --- a/examples/example-app-router/src/middleware.ts +++ b/examples/example-app-router/src/middleware.ts @@ -1,5 +1,5 @@ import createMiddleware from 'next-intl/middleware'; -import {routing} from './routing'; +import {routing} from './i18n/routing'; export default createMiddleware(routing); diff --git a/packages/next-intl/src/plugin.tsx b/packages/next-intl/src/plugin.tsx index 594da1f8a..4a9117ccb 100644 --- a/packages/next-intl/src/plugin.tsx +++ b/packages/next-intl/src/plugin.tsx @@ -4,6 +4,15 @@ import fs from 'fs'; import path from 'path'; import type {NextConfig} from 'next'; +function withExtensions(localPath: string) { + return [ + `${localPath}.ts`, + `${localPath}.tsx`, + `${localPath}.js`, + `${localPath}.jsx` + ]; +} + function resolveI18nPath(providedPath?: string, cwd?: string) { function resolvePath(pathname: string) { const parts = []; @@ -25,29 +34,27 @@ function resolveI18nPath(providedPath?: string, cwd?: string) { return providedPath; } else { for (const candidate of [ - './i18n.tsx', - './i18n.ts', - './i18n.js', - './i18n.jsx', - './src/i18n.tsx', - './src/i18n.ts', - './src/i18n.js', - './src/i18n.jsx' + ...withExtensions('./i18n'), + ...withExtensions('./src/i18n'), + ...withExtensions('./i18n/request'), + ...withExtensions('./src/i18n/request') ]) { if (pathExists(candidate)) { return candidate; } } - throw new Error(`\n\nCould not locate i18n config. Create one at \`./(src/)i18n.{js,jsx,ts,tsx}\` or specify a custom location: + throw new Error(`\n\nCould not locate i18n request config for next-intl. + +These paths are supported by default: +- ./(src/)i18n/request.{js,jsx,ts,tsx} +- ./(src/)i18n.{js,jsx,ts,tsx} -const withNextIntl = require('next-intl/plugin')( - './path/to/i18n.tsx' -); +Alternatively, you can specify a custom location in your Next.js config: -module.exports = withNextIntl({ - // Other Next.js configuration ... -});\n`); +const withNextIntl = createNextIntlPlugin( + './path/to/i18n/request.tsx' +);\n`); } }