Skip to main content

Internationalization (i18n)

Introduction​

Use the i18n configuration when a locale changes a route pathname. Blueprint Auth uses the configuration to match protected routes and to create redirect URLs.

Configure i18n for these routing strategies:

  • Next.js subpath routing, such as /fr/dashboard
  • Translated route segments, such as /fr/tableau-de-bord
  • Custom locale routing that changes a pathname

You can omit this configuration when all locales use the same pathnames. You can also omit it for domain routing when only the domain changes.

Before you begin​

Complete the Pages Router setup or the App Router setup.

Configuration​

OptionTypeRequiredDescription
i18n.localeCookiestringNoThe name of the cookie that stores the locale. The default is NEXT_LOCALE.
i18n.getLocalizedPathname(options) => stringYesReturns the localized pathname for one configured route.

getLocalizedPathname receives locale, pathname, params, and url. params is present for a configured pathname that has dynamic segments.

Always include the required home route in appRoutes:

lib/auth/config.ts
import { createAuthConfig } from "@krakentech/blueprint-auth";

export const authConfig = createAuthConfig({
appRoutes: {
dashboard: { pathname: "/dashboard" },
home: { pathname: "/" },
login: { pathname: "/login" },
},
i18n: {
localeCookie: "NEXT_LOCALE",
getLocalizedPathname({ pathname }) {
return pathname;
},
},
});

Replace the example callback with one of the routing strategies below.

Run locale middleware first​

Locale middleware can redirect a request before auth runs. Return that redirect without calling the auth middleware. Pass other responses to the auth middleware. This preserves rewrites and response headers.

proxy.ts
import { createAuthMiddleware } from "@krakentech/blueprint-auth/middleware";
import createNextIntlMiddleware from "next-intl/middleware";
import type { NextRequest } from "next/server";
import { routing } from "@/i18n/routing";
import { authConfig } from "@/lib/auth/config";

const handleI18n = createNextIntlMiddleware(routing);
const handleAuth = createAuthMiddleware(authConfig);

export async function proxy(request: NextRequest) {
const response = handleI18n(request);

if (response.headers.has("location")) {
return response;
}

return handleAuth(request, response);
}

export const config = {
matcher: ["/", "/((?!api|_next|_vercel|.*\\..*).*)"],
};

For Next.js 15 or earlier, save this file as middleware.ts and rename proxy to middleware.

Routing examples​

Use this example with the Pages Router subpath routing feature. It does not prefix the default locale.

lib/i18n.ts
export const LOCALES = ["en", "fr", "de"] as const;
export const DEFAULT_LOCALE = "en";

export type Locale = (typeof LOCALES)[number];

export function isLocale(value: string | undefined): value is Locale {
return value !== undefined && LOCALES.some((locale) => locale === value);
}

export function resolveLocale(
locale: string | undefined,
url: URL,
): Locale {
if (isLocale(locale)) {
return locale;
}

const pathnameLocale = url.pathname.split("/")[1];
return isLocale(pathnameLocale) ? pathnameLocale : DEFAULT_LOCALE;
}
lib/auth/config.ts
import { createAuthConfig } from "@krakentech/blueprint-auth";
import { DEFAULT_LOCALE, resolveLocale } from "@/lib/i18n";

export const authConfig = createAuthConfig({
appRoutes: {
dashboard: { pathname: "/dashboard" },
home: { pathname: "/" },
login: { pathname: "/login" },
},
i18n: {
localeCookie: "NEXT_LOCALE",
getLocalizedPathname({ locale, pathname, url }) {
const resolvedLocale = resolveLocale(locale, url);

if (resolvedLocale === DEFAULT_LOCALE) {
return pathname;
}

return pathname === "/"
? `/${resolvedLocale}`
: `/${resolvedLocale}${pathname}`;
},
},
});

Redirect protected requests​

redirectToLogin uses the same i18n configuration as the middleware. It localizes the login route. It uses the current request pathname as nextPage by default.

pages/dashboard.tsx
import { BlueprintAuthErrorCode } from "@krakentech/blueprint-auth";
import type { GetServerSidePropsContext } from "next";
import { getAuth, redirectToLogin } from "@/lib/auth/server";

export async function getServerSideProps(context: GetServerSidePropsContext) {
const auth = await getAuth.user({ context });

if (!auth) {
return redirectToLogin({
context,
errorCode: BlueprintAuthErrorCode.AuthenticationRequired,
});
}

return { props: {} };
}

Pass nextPage: null when the login URL must omit the return destination.

Dynamic routes​

Blueprint Auth passes wildcard values for dynamic segments when it builds route matchers. A single segment receives "*". A catch-all segment receives ["**"].

This callback supports static routes and dynamic routes:

getLocalizedPathname({ locale, url, ...href }) {
const resolvedLocale = resolveLocale(locale, url);
return getPathname({ href, locale: resolvedLocale });
},

Add every dynamic pathname from appRoutes and allowList to your translated pathname configuration. If the localization callback throws, Blueprint Auth falls back to a nonlocalized glob. That fallback might not match translated segments.

Middleware matchers​

Use the recommended catch-all matcher:

export const config = {
matcher: ["/", "/((?!api|_next|_vercel|.*\\..*).*)"],
};

Localized allowlists​

Blueprint Auth also passes allowList entries to getLocalizedPathname. Use base pathnames in the configuration:

lib/auth/config.ts
import { createAuthConfig } from "@krakentech/blueprint-auth";
import { hasLocale } from "next-intl";
import { getPathname } from "@/i18n/navigation";
import { routing } from "@/i18n/routing";

export const authConfig = createAuthConfig({
appRoutes: {
anon: {
pathname: "/feedback/[feedbackId]",
getAnonParams({ url }) {
return { preSignedKey: url.searchParams.get("key") };
},
allowList: ["/feedback/[feedbackId]/public-info"],
},
dashboard: { pathname: "/dashboard" },
home: { pathname: "/" },
login: { pathname: "/login" },
},
i18n: {
getLocalizedPathname({ locale, url, ...href }) {
const requestedLocale = locale ?? url.pathname.split("/")[1];
const resolvedLocale = hasLocale(routing.locales, requestedLocale)
? requestedLocale
: routing.defaultLocale;
return getPathname({ href, locale: resolvedLocale });
},
},
});

With translated next-intl pathnames, prefer Next.js dynamic segment syntax. A glob such as /feedback/* is not a pathname key, so getPathname cannot translate it.

Next steps​

API reference​