Migration Guide
Migrate from other i18n libraries to Intl‑T
Before migrating, make sure you understand the core concepts of intl-t. See the Basic Usage section for details.
1. Prepare your translations
intl-t
supports flexible JSON files with deeply nested nodes. However, you should review the Reserved Keywords before using them in your translations. All locale translation files must have the same structure, keys, and nodes. intl-t
will warn you if there are any discrepancies.
// en.json
{
"homepage": {
"welcome": "Welcome, {user}!",
}
}
// es.json
{
"homepage": {
"welcome": "Bienvenido, {user}!",
}
}
It's recommended to have a central translation meta file for general data, such as allowedLocales
and defaultLocale
. This is useful for navigation and middleware.
export const allowedLocales = ["en", "es"];
2. Set up your translation configuration
Use async createTranslation with the locales
option as a function to preload locales on the server and dynamically import them on the client.
import { createTranslation, getLocales } from "intl-t/next";
import { allowedLocales } from "./locales";
type Locale = typeof import("./messages/en.json");
export const { Translation, useTranslation, getTranslation } = await createTranslation({
allowedLocales,
locales: locale => import(`./messages/${locale}.json`) as Promise<Locale>,
});
If you want to use type declarations for each locale, you should set up the configuration as follows:
import { createTranslation, getLocales } from "intl-t/next";
import { allowedLocales } from "./locales";
export const t = createTranslation({
locales: {
en: () => import("./messages/en.json"),
es: () => import("./messages/es.json"),
// ...
},
});
You can generate literal string declarations for your JSON files using generateDeclarations
function.
import { generateDeclarations } from "intl-t/declarations";
generateDeclarations("messages");
If you're using Next.js in production, you may need to patch React to support translation objects:
import patch from "intl-t/patch";
import React from "react";
import jsx from "react/jsx-runtime";
process.env.NODE_ENV !== "development" && patch(React, jsx);
Then import this patch at the top of your translation file:
import "./patch";
// ...
3. Configure navigation
import { createNavigation } from "intl-t/navigation";
import { allowedLocales } from "./locales";
export const { middleware, generateStaticParams, Link, redirect, useRouter } = createNavigation({ allowedLocales });
export { middleware as default } from "@/i18n/navigation";
export const config = {
matcher: ["/((?!api|static|.*\\..*|_next).*)"],
};
To customize the
intl-t
middleware, you can extractwithMiddleware
function to wrap the function in a chain or as needed.middleware(request, event, response)
See the complete navigation documentation in the Navigation section.
4. Set up your root layout
By default, the locale is managed using the [locale]
route parameter. However, you can fully customize this behavior. For example, you can get the locale dynamically from headers, detect the domain, geolocation, or custom HTTP Request. See the Navigation section for more details. There is also a mini example in the dynamic rendering with Next.js section that shows how to avoid using the [locale]
param by detecting the locale from headers.
/app/[locale]/...
import { Translation } from "@/i18n/translation";
import { setRequestLocale } from "intl-t/next";
export { generateStaticParams } from "@/i18n/navigation";
interface Props {
params: Promise<{ locale: typeof Translation.locale }>;
children: React.ReactNode;
}
export default async function RootLayout({ children, params }: Props) {
const { locale } = await params;
if (!Translation.locales.includes(locale)) return;
setRequestLocale(locale);
return (
<html lang={locale}>
<body>
<Translation>{children}</Translation>
</body>
</html>
);
}
5. Use translations in your code
With React Server Components (Static):
import { getTranslation } from "@/i18n/translation";
export default function Component() {
const t = getTranslation();
return <div>{t("greeting", { name: "Ivan" })}</div>;
}
Read more about static rendering with Intl-T
With React Server Components (Dynamic):
If you don't provide a Translation Provider or don't use setRequestLocale
if required, you can use await getTranslation()
for dynamic rendering in Next.js.
import { getTranslation } from "@/i18n/translation";
export default function Component() {
const t = await getTranslation();
return <div>{t("greeting", { name: "Ivan" })}</div>;
}
Read more about dynamic rendering with Intl-T
With Server Actions:
The locale is automatically detected from headers.
"use server";
import { getTranslation } from "@/i18n/translation";
export function greeting() {
const t = await getTranslation(); // use await to get locale from headers
return t("greeting", { name: "Ivan" });
}
With Client Components (Hydration):
"use client";
import { useTranslation } from "@/i18n/translation";
export default function Component() {
const { t } = useTranslation();
return <div>{t("greeting", { name: "Ivan" })}</div>;
}
For easier migration from other i18n libraries, you can use the getTranslations
and useTranslations
aliases, exactly the same and keep type safety. getTranslation
and useTranslation
are functionally the same and adapt depending on the environment.
You can also use them as translation object directly, e.g., useTranslation.greeting.es({ name: "Ivan" })
—it's modular, type-safe, and flexible.
With metadata:
// layout.tsx
export async function generateMetadata({ params }) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslation();
return t.metadata.toJSON();
}
Link Navigation Component:
import { Link } from "@/i18n/navigation";
import { Translation } from "@/i18n/translation";
export default function LanguageSwitcher() {
const { Translation, t } = useTranslation("languages");
return (
<nav>
<h2>{t("title")}</h2>
<ul>
{t.allowedLocales.map(locale => (
<Link locale={locale} key={locale}>
<Translation.change variable={{ locale }} /> {/* example of Translation component */}
</Link>
))}
</ul>
</nav>
);
}
Router Hook:
import { useRouter } from "@/i18n/navigation";
export default function Component() {
const router = useRouter();
function onClick() {
router.push("/hello", { locale: "fr" });
}
return (
<div onClick={onClick}>
{router.locale} {router.pathname}
</div>
);
}