# Localize your app with the Next.js App Router SDK

> Recipe: configure a localized Next.js App Router app with Uniform. Give the locale to the middleware, the routes, the preview and the static params.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/localization

> **Developer Preview:**
>
> This page documents the developer preview of the SDK, version `20.81.1-alpha.25.sha-f076f9b857`.

**Task:** show each page in the correct locale, in the live site, the preview and the static build.

The Uniform Route API returns the composition in the locale of the route. The locale comes from the path, or from the `locale` option of the middleware. For the setup of locales in Uniform, refer to [Localization](https://docs.uniform.app/docs/guides/composition/localization).

## How the SDK finds the locale

The locale goes into the page state, and from there to the Route API. The SDK sets `pageState.locale` in this sequence:

1. The `locale` option of the middleware, when you set it.
2. Else, the `locale` dynamic input of the matched project map node. For example, the node `/:locale/about` matches `/fr/about`, and the locale is `fr`. The resolver sets it after the route matches.

Components can read the locale in `context.pageState.locale`, and the dynamic inputs in `context.dynamicInputs`.

## Locale in the URL

This is the most frequent setup. The locale is the first segment of the URL, and the project map has a `:locale` node.

### Add the default locale to the path

A visitor can open a URL without a locale, for example `/about`. Use `rewriteRequestPath` to add the default locale:

`middleware.ts`

```ts
import { vercelUniformEdgeMiddleware } from "@uniformdev/next-app-router/vercel";

const locales = ["en", "fr", "de"];
const defaultLocale = "en";

export default vercelUniformEdgeMiddleware({
  rewriteRequestPath: async ({ url }) => {
    const [firstSegment] = url.pathname.split("/").filter(Boolean);
    const hasLocale = firstSegment !== undefined && locales.includes(firstSegment);
    return { path: hasLocale ? url.pathname : `/${defaultLocale}${url.pathname}` };
  },
});
```

The URL in the browser does not change. Only the route path in the code gets the locale.

## Locale from a cookie or a header

When the locale is not in the URL, set the `locale` option for each request:

`middleware.ts`

```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import type { NextRequest } from "next/server";

export default function middleware(request: NextRequest) {
  const locale = request.cookies.get("NEXT_LOCALE")?.value ?? "en";
  return handleUniformRoute({ request, locale });
}
```

The locale goes into the code. Thus each locale has its own cached page.

## Put the composition route under the locale

You can use a `[locale]` segment in the app folder, for example to set `<html lang>` in a layout. Return the new destination from `rewriteDestinationPath`:

`middleware.ts`

```ts
import { uniformMiddleware } from "@uniformdev/next-app-router/middleware";

const locales = ["en", "fr", "de"];

export default uniformMiddleware({
  rewriteDestinationPath: async ({ code, pageState, source }) => {
    // The route path starts with the locale, for example /fr/about.
    const [firstSegment] = pageState.routePath.split("/").filter(Boolean);
    const locale = firstSegment !== undefined && locales.includes(firstSegment) ? firstSegment : "en";
    return source === "route" ? `/${locale}/uniform/${code}` : `/${locale}/playground/${code}`;
  },
});
```

Then put the pages in `app/[locale]/uniform/[code]/page.tsx` and `app/[locale]/playground/[code]/page.tsx`.

`pageState.locale` has a value in the middleware only when you set the `locale` option. The `locale` dynamic input of the route is known only after the page resolves the route. Thus the example reads the locale from the route path. A playground request has the composition ID as its route path, so it uses the default locale.

## Preview

Canvas sends the selected preview locale to the preview handler in the `locale` query string. The handler gives it to your functions, but it does not add it to the redirect URL of a composition. If your URLs contain the locale, add it in `resolveFullPath`:

`app/api/preview/route.ts`

```ts
import {
  createPreviewGETRouteHandler,
  createPreviewOPTIONSRouteHandler,
  createPreviewPOSTRouteHandler,
} from "@uniformdev/next-app-router/handler";

export const GET = createPreviewGETRouteHandler({
  // The project map path can contain the :locale segment already.
  resolveFullPath: ({ path, slug, locale }) => {
    const target = path ?? slug;
    if (!target || !locale || target.startsWith(`/${locale}`)) return target;
    return `/${locale}${target}`;
  },
});
export const POST = createPreviewPOSTRouteHandler();
export const OPTIONS = createPreviewOPTIONSRouteHandler();
```

For a pattern preview, the handler adds `locale` to the query string of the playground URL. The playground does not use this value. It uses the `locale` option of the middleware only.

> **Warning:**
>
> Do not add the locale to the playground path with `processPlaygroundPath`. The middleware finds a pattern preview only when the path is the same as `playgroundPath`.

## Static generation

Put each locale and path in the `paths` of `createUniformStaticParams`. This example uses a project map with a `/:locale` node:

`app/uniform/[code]/page.tsx`

```tsx
import { createUniformStaticParams, getProjectMapClient } from "@uniformdev/next-app-router";

const locales = ["en", "fr", "de"];

export const generateStaticParams = async () => {
  const { nodes } = await getProjectMapClient({ cache: { type: "default" } }).getNodes({});

  // The node "/:locale/about" gives "/en/about", "/fr/about" and "/de/about".
  // Nodes with other dynamic segments, for example "/:locale/products/:slug", are skipped.
  const paths = (nodes ?? [])
    .map((node) => node.path)
    .filter((path) => path.startsWith("/:locale") && !path.slice("/:locale".length).includes(":"))
    .flatMap((path) => locales.map((locale) => path.replace("/:locale", `/${locale}`)));

  return createUniformStaticParams({ paths });
};
```

Use the same path format as the middleware. If the middleware sets the `locale` option, set the same `locale` in `createUniformStaticParams`. Then the prebuilt codes are the same as the codes from the middleware:

```tsx
export const generateStaticParams = () =>
  createUniformStaticParams({ paths: ["/about", "/contact"], locale: "fr" });
```

A path that has no composition gives no code. `createUniformStaticParams` does not send the `locale` to the Route API. The Route API finds the locale from the path.
