Nextjs Internationalization
February 1, 2024
February 1, 2024
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.
During the project, we start from scratch and will use basic Next.js in a TypeScript environment.
POWERSHELLnpx 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.
POWERSHELLnpm i next-intl
For consistency and simplicity, we modify app/page.tsx and add an app/about/page.tsx file for future testing.
TYPESCRIPT//src/app/page.tsximport 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.tsximport 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>);}
next.Config.mjs changes
JAVASCRIPT//next.Config.mjsimport createNextIntlPlugin from 'next-intl/plugin';const withNextIntl = createNextIntlPlugin();/** @type {import('next').NextConfig} */const nextConfig = {};export default withNextIntl(nextConfig);
Add middleware.ts
TYPESCRIPT//src/middleware.tsimport createMiddleware from "next-intl/middleware";import { locales, defaultLocale } from "./navigation";export default createMiddleware({locales,defaultLocale,});export const config = {// Match only internationalized pathnamesmatcher: ["/", "/(hu|en)/:path*"],};
Add i18n.ts
TYPESCRIPT//src/i18n.tsimport { 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 validif (!locales.includes(locale as any)) notFound();return {messages: (await import(`../messages/${locale}.json`)).default,};});
Add navigation.ts
Here we set up:
TYPESCRIPT//src/navigation.tsimport {createSharedPathnamesNavigation,} from "next-intl/navigation";export const defaultLocale = "en";export const locales = ["en", "hu"] as const;export const { Link, redirect, usePathname, useRouter } =createSharedPathnamesNavigation({ locales });
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"}}
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.tsximport { 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.tsximport { 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.tsximport 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.
The modification is very simple, only two elements need to be changed: navigation.ts and middleware.ts
TYPESCRIPT//src/navigation.tsimport {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 supportedlocales,pathnames,defaultLocale,});...
In the new setup, the "about" page is now found under "rolunk" in Hungarian.
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.
The complete code can be found here:
https://github.com/balazsfaragodev/Next.js---next-intl---TypeScript-Simple-Internationalization
Share this article
Start your day right with the daily newsletter that entertains and informs. Subscribe now for free!