Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions docs/01-app/02-guides/internationalization.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
title: Internationalization
description: Add support for multiple languages with internationalized routing and localized content.
related:
description: Related API references and conventions.
links:
- app/api-reference/functions/next-root-params
---

Next.js enables you to configure the routing and rendering of content to support multiple languages. Making your site adaptive to different locales includes translated content (localization) and internationalized routes.
Expand Down Expand Up @@ -178,6 +182,75 @@ export default async function Page({ params }) {

Because all layouts and pages in the `app/` directory default to [Server Components](/docs/app/getting-started/server-and-client-components), we do not need to worry about the size of the translation files affecting our client-side JavaScript bundle size. This code will **only run on the server**, and only the resulting HTML will be sent to the browser.

## Sharing the locale across your app

The locale is often needed beyond the page that receives it, such as in shared data-fetching utilities or deeply nested components. Instead of prop drilling `lang` through each layer, we can read it directly with [`next/root-params`](/docs/app/api-reference/functions/next-root-params).

`next/root-params` exports a getter for each dynamic segment above the root layout. Since every route is nested under `app/[lang]`, `lang` is a root parameter, and any Server Component or server-side utility can call its getter. We can move the locale lookup into `getDictionary`, so callers no longer pass `lang`:

```ts filename="app/[lang]/dictionaries.ts" highlight={1,2,15,16,17} switcher
import { lang } from 'next/root-params'
import { notFound } from 'next/navigation'

const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}

export type Locale = keyof typeof dictionaries

export const hasLocale = (locale: string): locale is Locale =>
locale in dictionaries

export const getDictionary = async () => {
const locale = await lang()
if (!hasLocale(locale)) notFound()
return dictionaries[locale]()
}
```

```js filename="app/[lang]/dictionaries.js" highlight={1,2,12,13,14} switcher
import { lang } from 'next/root-params'
import { notFound } from 'next/navigation'

const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}

export const hasLocale = (locale) => locale in dictionaries

export const getDictionary = async () => {
const locale = await lang()
if (!hasLocale(locale)) notFound()
return dictionaries[locale]()
}
```

> **Good to know:** Files that import from `next/root-params` do not need `import 'server-only'`. The import already fails at build time if used in a Client Component.

Pages and components then call `getDictionary()` with no arguments, since the locale is resolved internally:

```tsx filename="app/[lang]/page.tsx" highlight={4} switcher
import { getDictionary } from './dictionaries'

export default async function Page() {
const dict = await getDictionary()
return <button>{dict.products.cart}</button> // Add to Cart
}
```

```jsx filename="app/[lang]/page.js" highlight={4} switcher
import { getDictionary } from './dictionaries'

export default async function Page() {
const dict = await getDictionary()
return <button>{dict.products.cart}</button> // Add to Cart
}
```

> **Good to know:** Root parameter getters run in Server Components and server-side utilities, but not in Client Components, Server Actions, or Route Handlers. See [`next/root-params`](/docs/app/api-reference/functions/next-root-params) for the full API and its behavior with caching.

## Static Rendering

To generate static routes for a given set of locales, we can use `generateStaticParams` with any page or layout. This can be global, for example, in the root layout:
Expand Down
15 changes: 11 additions & 4 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4339,6 +4339,7 @@ function runDevValidationInBackground(
ctx: AppRenderContext,
fallbackRouteParams: OpaqueFallbackRouteParams | null,
prerenderResumeDataCache: ReturnType<typeof createPrerenderResumeDataCache>,
shellStage: RenderStage.Static | RenderStage.Runtime,
getDevRenderDidError: () => boolean,
createRequestStore: () => RequestStore,
getPayload: (requestStore: RequestStore) => Promise<RSCPayload>,
Expand Down Expand Up @@ -4393,7 +4394,8 @@ function runDevValidationInBackground(
createRequestStore,
getPayload,
onError,
prerenderResumeDataCache
prerenderResumeDataCache,
shellStage
)

// Unlike the cold streamed render, which fills the caches, the warm
Expand Down Expand Up @@ -4440,7 +4442,8 @@ interface StagedDevRenderSetup {
* the request store.
*/
function setUpStagedDevRender(
requestStore: RequestStore
requestStore: RequestStore,
shellStage: RenderStage.Static | RenderStage.Runtime
): StagedDevRenderSetup {
const cacheSignal = new CacheSignal()
trackPendingModules(cacheSignal)
Expand All @@ -4460,6 +4463,7 @@ function setUpStagedDevRender(
requestStore.headers
)
requestStore.cacheSignal = cacheSignal
requestStore.shellStage = shellStage

const environmentName = () =>
getEnvironmentNameForStage(stageController.currentStage)
Expand Down Expand Up @@ -4715,7 +4719,8 @@ async function renderWithWarmCachesForValidationInDev(
createRequestStore: () => RequestStore,
getPayload: (requestStore: RequestStore) => Promise<RSCPayload>,
onError: (error: unknown) => void,
prerenderResumeDataCache: ReturnType<typeof createPrerenderResumeDataCache>
prerenderResumeDataCache: ReturnType<typeof createPrerenderResumeDataCache>,
shellStage: RenderStage.Static | RenderStage.Runtime
): Promise<DevValidationInputs> {
const { ComponentMod, setReactDebugChannel } = ctx.renderOpts
const { clientModules } = getClientReferenceManifest()
Expand All @@ -4728,6 +4733,7 @@ async function renderWithWarmCachesForValidationInDev(
})

const requestStore = createRequestStore()
requestStore.shellStage = shellStage
requestStore.resumeDataCache = createRenderResumeDataCache(
prerenderResumeDataCache
)
Expand Down Expand Up @@ -4830,7 +4836,7 @@ async function stagedRenderWithCachesInDev(
prerenderResumeDataCache,
stageController,
environmentName,
} = setUpStagedDevRender(requestStore)
} = setUpStagedDevRender(requestStore, shellStage)

let validationDebugChannel: AnyStream | undefined
const debugChannel = setReactDebugChannel && createNodeDebugChannel()
Expand Down Expand Up @@ -4867,6 +4873,7 @@ async function stagedRenderWithCachesInDev(
ctx,
fallbackRouteParams,
prerenderResumeDataCache,
shellStage,
getDevRenderDidError,
createRequestStore,
getPayload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ export interface RequestStore extends CommonWorkUnitStore {

// DEV-only
usedDynamic?: boolean
/**
* Which shell this dev render produces: `Static` for an initial HTML load, a
* plain client navigation, or an HMR refresh; `Runtime` for a client
* navigation into a runtime-prefetch route, where the runtime-prefetchable
* content has already settled on the client.
*/
shellStage?: RenderStage.Static | RenderStage.Runtime
}

export type InstantValidationSamples = {
Expand Down
Loading
Loading