Architecture Philosophy

I chose next-intl because it is incredibly lightweight and explicitly built for the modern Next.js 16 paradigm. Instead of loading massive translation dictionaries on the client, translations are resolved on the server and only the required strings are sent to the browser.

Routing

URL-Based Locales

Every route in the boilerplate is wrapped in a [locale]folder segment. This ensures SEO-friendly URLs like /fr/dashboardand strict state management across the application.

Storage

Flat Dictionary

Unlike highly fragmented setups, I keep all translations within a singlemessages folder at the root. This makes handing files off to translators or AI tools incredibly straightforward.

Core Configuration

The engine is controlled by two specific files in the src/i18n directory.

src/i18n/routing.tsRoute Definitions

This file defines the supported languages and the default fallback. It also exports locale-aware navigation helpers (Link,useRouter) that you should use instead of the standard Next.js imports to ensure the locale is preserved across clicks.

import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";

export const routing = defineRouting({
locales: ["en", "fr", "es", "de"],
defaultLocale: "en",
});

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);
src/i18n/request.tsRequest Config

This file executes on the server for every incoming request. It determines which JSON dictionary to load based on the URL parameter.

import { getRequestConfig } from "next-intl/server";
import { routing } from "./routing";

export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});

Translation Files

I keep all translations inside the messages/directory at the root of the project. Out of the box, the boilerplate supports English (en.json), French (fr.json), Spanish (es.json), and German (de.json).

You can nest keys deeply within these JSON files to keep things organized by page or component.

{
"Landing": {
"heroTitle": "Ship your startup faster",
"getStarted": "Get Started Now"
}
}

Usage in Server Components

Because server components execute on the backend, fetching translations is an asynchronous operation. You must use the getTranslations method.

import { getTranslations } from "next-intl/server";

export default async function HeroSection() {
const t = await getTranslations("Landing");

return (
<div>
<h1>{t("heroTitle")}</h1>
</div>
);
}

Usage in Client Components

When you need interactivity (e.g., inside forms or buttons), you use theuseTranslations hook. The Next.js provider automatically injects the JSON dictionary into the client tree, making this synchronous.

"use client";

import { useTranslations } from "next-intl";

export function InteractiveButton() {
const t = useTranslations("Landing");

return <button onClick={() => alert("Clicked")}>{t("getStarted")}</button>;
}

Adding a New Locale

Extending the boilerplate to support another language is a simple two-step process.

1

Create the Dictionary

Create a new JSON file inside the messages/ folder matching your target language code (e.g., it.json for Italian). Copy the structure from en.json and translate the values.

2

Update the Routing Config

Open src/i18n/routing.ts and append your new language code to the locales array. The middleware will now automatically intercept and route requests for that language.

Frequently Asked Questions

Can I use Markdown or rich text in translations?

Yes. next-intl supports rich text formatting. You can embed tags like <b>bold</b> in your JSON values and resolve them in your components by passing rendering functions to the translation hook.

How do I translate metadata (SEO)?

In your page.tsx or layout.tsx, you can generate dynamic metadata. Use the getTranslationsfunction inside the exported generateMetadata function to assign translated titles and descriptions based on the requested locale.

Official Documentation

Explore advanced formatting, date/time localization, and dynamic variable injection.