Nextjs Internationalization

February 1, 2024

A simple Next.js project to demonstrate next-intl 3.0

During the project, we implement the innovations of next-intl 3.0 through a simple example. The main advantage is the easy localization of the page URLs and managing it. Next-intl works with both page and app router, but it truly shines with the app router. If you're looking to localize your Next.js website, it's definitely worth trying out.

Install

During the project, we start from scratch and will use basic Next.js in a TypeScript environment.

POWERSHELL
npx create-next-app@latest

For TypeScript, TailwindCSS, src/ folder, and the App router to be enabled.

(If you don't want to use the src/ folder, then the only change in the code is that you don't need to add the src folder to the paths.)

To achieve multilingual support, we will download the next-intl package.

POWERSHELL
npm i next-intl

Necessary basic settings

For consistency and simplicity, we modify app/page.tsx and add an app/about/page.tsx file for future testing.

TYPESCRIPT
//src/app/page.tsx
import Link from "next/link";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
Get started by editing
<Link href="/about">about</Link>
</main>
);
}
TYPESCRIPT
//src/app/about/page.tsx
import Link from "next/link";
export default function About() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
The 'About' page provides essential information about our company or project, including our mission and values. Join us to shape the future together!
<Link href="/">back to home</Link>
</main>
);
}

Basic setup for multilingual support

next.Config.mjs changes

JAVASCRIPT
//next.Config.mjs
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextIntl(nextConfig);

Add middleware.ts

TYPESCRIPT
//src/middleware.ts
import createMiddleware from "next-intl/middleware";
import { locales, defaultLocale } from "./navigation";
export default createMiddleware({
locales,
defaultLocale,
});
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(hu|en)/:path*"],
};

Add i18n.ts

TYPESCRIPT
//src/i18n.ts
import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import {locales} from "./navigation"
export default getRequestConfig(async ({ locale }) => {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`../messages/${locale}.json`)).default,
};
});

Add navigation.ts

Here we set up:

  • the required languages
  • the default language
  • and how different pages should use names
  • TYPESCRIPT
    //src/navigation.ts
    import {
    createSharedPathnamesNavigation,
    } from "next-intl/navigation";
    export const defaultLocale = "en";
    export const locales = ["en", "hu"] as const;
    export const { Link, redirect, usePathname, useRouter } =
    createSharedPathnamesNavigation({ locales });

    Creating bilingual texts

    This is done in a folder named messages in the root directory. In this folder, you need to create a corresponding json file for each language. I have already entered the necessary names here.

    JSON
    //messages/en.json
    {
    "Index": {
    "title": "Hello world!",
    "getStarted": "Get started by editing",
    "about": "about"
    },
    "About": {
    "aboutText": "The 'About' page provides essential information about our company or project, including our mission and values. Join us to shape the future together!",
    "home": "back to home"
    }
    }
    JSON
    //messages/hu.json
    {
    "Index": {
    "title": "Szia Világ!",
    "getStarted": "Kezdd el a szerkesztést",
    "about": "rólunk"
    },
    "About": {
    "aboutText": "Az 'About' oldal bemutatja cégünk vagy projektünk alapvető információit, küldetését és értékeit. Csatlakozz hozzánk, hogy együtt formálhassuk a jövőt!",
    "home": "vissza a főoldalra"
    }
    }

    Making the pages multilingual

    Create a [locale] folder under the app directory and place the "about" folder, the page.tsx, and the layout.tsx file inside it.

    Localization of the HTML language in the layout

    TYPESCRIPT
    //src/app/[locale]/layout.tsx
    ...
    export default function RootLayout({
    children,
    params: { locale },
    }: Readonly<{
    children: React.ReactNode;
    params: { locale: string };
    }>) {
    return (
    <html lang={locale}>
    <body className={inter.className}>{children}</body>
    </html>
    );
    }

    Replace the newly created Link element in the code with the one created using createSharedPathnamesNavigation.

    Use useTranslations. Under the Index designation, we import the other message elements to the main page. (If we have an async function, await getTranslations should be used instead of useTranslations)

    TYPESCRIPT
    //src/app/[locale]/page.tsx
    import { useTranslations } from "next-intl";
    import { Link } from "@/navigation";
    export default function Home() {
    const t = useTranslations("Index");
    return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
    {t("getStarted")}
    <Link href="/about">{t("about")}</Link>
    </main>
    );
    }

    Similarly, we replace the "about" page as well.

    TYPESCRIPT
    //src/app/[locale]/about/page.tsx
    import { useTranslations } from "next-intl";
    import { Link } from "@/navigation";
    export default function About() {
    const t = useTranslations("About");
    return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
    {t("aboutText")}
    <Link href="/">{t("home")}</Link>
    </main>
    );
    }

    Additionally, we need to add a component to the pages that allows us to switch languages.

    TYPESCRIPT
    //src/components/localeSwitcher.tsx
    "use client";
    import { useLocale } from "next-intl";
    import { useRouter, usePathname } from "../navigation";
    export default function LocaleSwitcher() {
    const locale = useLocale();
    const router = useRouter();
    const pathName = usePathname();
    const alternativeLocale = locale === "en" ? "hu" : "en";
    const switchLocale = () => {
    router.push(pathName, { locale: alternativeLocale });
    };
    return (
    <div className="rounded-lg border bg-white px-4 py-2 text-sm text-black dark:border-neutral-800 dark:bg-transparent dark:text-white ">
    <button onClick={switchLocale} className="">
    {locale}
    </button>
    </div>
    );
    }

    Then, it needs to be added to the page.tsx files.

    TYPESCRIPT
    //src/app/[locale]/page.tsx
    import LocaleSwitcher from "@/components/localeSwitcher";
    ...
    export default function Home() {
    const t = useTranslations("Index");
    return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
    <LocaleSwitcher />
    ...
    </main>
    );
    }

    With this setup, the language on our website can be adjusted, but the URLs are not multilingual.

    Applying multilingual slugs

    The modification is very simple, only two elements need to be changed: navigation.ts and middleware.ts

    TYPESCRIPT
    //src/navigation.ts
    import {
    Pathnames,
    createLocalizedPathnamesNavigation,
    } from "next-intl/navigation";
    ...
    export const pathnames = {
    "/": "/",
    "/about": {
    en: "/about",
    hu: "/rolunk",
    },
    } satisfies Pathnames<typeof locales>;
    export const { Link, redirect, usePathname, useRouter } =
    createLocalizedPathnamesNavigation({ locales, pathnames });
    TYPESCRIPT
    ...
    import { locales, pathnames, defaultLocale } from "./navigation";
    export default createMiddleware({
    // A list of all locales that are supported
    locales,
    pathnames,
    defaultLocale,
    });
    ...

    In the new setup, the "about" page is now found under "rolunk" in Hungarian.

    Where to Go Next

    This is a basic program, but it's a good starting point for setting up language localization with next-intl. The process works similarly on larger projects as well.

    Code

    The complete code can be found here:

    https://github.com/balazsfaragodev/Next.js---next-intl---TypeScript-Simple-Internationalization

    Share this article

    The Newsletter for Next-Level Tech Learning

    Start your day right with the daily newsletter that entertains and informs. Subscribe now for free!

    Related Articles