Skip to content
better-i18n.com

@better-i18n/next integrates with next-intl: it supplies the request config, the middleware and the CDN fetching, and you keep using standard next-intl hooks in your components.

Prerequisites #

Step 1: Install #

Bash
npm install @better-i18n/next
# or: bun add @better-i18n/next

Step 2: Create i18n.config.ts #

At the project root, because the CLI reads the same file:

TypeScript
import { createI18n } from "@better-i18n/next";

export const i18n = createI18n({
  projectId: "my-company/web-app",
  defaultLocale: "en",
});

createI18n returns an object — requestConfig, betterMiddleware, getMessages, getLocales, getManifest, proxy — so keep it whole as i18n rather than destructuring hooks out of it. The translation hooks come from next-intl, not from here.

You do not list your locales: they come from the project's manifest on the CDN, so adding a language in the dashboard does not mean editing this file.

Step 3: Wire up next-intl #

TypeScript
// src/i18n/request.ts
import { i18n } from "../i18n.config";

export default i18n.requestConfig;

That is the whole server-side setup — no provider to mount in layout.tsx and no getMessages call to write by hand.

Step 4: Add the middleware #

TypeScript
// middleware.ts
import { i18n } from "./i18n.config";

export default i18n.betterMiddleware();

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

Locale detection (cookie, then browser language) is built in, so there is no detectLocale of your own to write.

If you have auth, pass a callback and keep both behaviours:

TypeScript
export default i18n.betterMiddleware(async (request, { locale }) => {
  const isLoggedIn = !!request.cookies.get("session")?.value;
  if (!isLoggedIn && request.nextUrl.pathname.includes("/dashboard")) {
    return NextResponse.redirect(new URL(`/${locale}/login`, request.url));
  }
  // Return nothing and the i18n response is used, headers intact.
});

Returning nothing from the callback is the important half: that is how the i18n response — and the locale headers on it — survives your auth check.

Step 5: Use translations #

TSX
// app/page.tsx
import { useTranslations } from "next-intl";

export default function Home() {
  const t = useTranslations("common");
  return <h1>{t("welcome")}</h1>;
}

Standard next-intl hooks, which is also what makes better-i18n scan able to find your keys — it looks for these calls.

Revalidation (optional) #

Two windows, both Next.js ISR revalidation rather than an in-memory cache:

OptionProduction defaultDev default
messagesRevalidateSeconds50
manifestRevalidateSeconds36000
TypeScript
export const i18n = createI18n({
  projectId: "my-company/web-app",
  defaultLocale: "en",
  messagesRevalidateSeconds: 30,
});

Zero in development is deliberate: an edit shows up on the next request while you work, and production still caches.

Next steps #