Nextjs+next-intl multi-language internationalization best practice (APP router)
Nextjs has two routers: APP router and Page router.
Some time ago, under the Page router, nextjs was internationalized and summarized:
But the Page router will be the router gradually phased out by Nextjs. Therefore, recently, Nextjs has been internationalized based on APP router.
1. Selection of Nextjs internationalization solution
1. i18next + react-i18next + i18next-resources-to-backend + next-i18n-router
It seems that most people are using this. I tried it and it was quite complicated. I didn't succeed yet, so I gave up.
2. next-i18n-router react-intl
It looked quite complicated, but because of the shadow of 1, I gave up.
3. next-intl
next-intl is a complete internationalization solution for nextjs and does not require other software packages. And the configuration is relatively simple. More importantly, I configured it successfully and encountered no inexplicable problems.
So, this is it.
2. Directory structure
|app ..[locale] ...layout.js ...page.js |components ..LangSwitcher.js |public ..locales ...en ....common.json ...es .... common.json |i18n.js |navigation.js |middleware.js |next.config.js
3. International routing
1. navigation.js
This is a configuration file, and the items configured in this file will be called in some other files.
import { createSharedPathnamesNavigation } from 'next-intl/navigation'; export const locales = ['en', 'de', 'es', 'ja','ko','it','pt']; export const localePrefix = 'as-needed'; export const defaultLocale= "en"; export const localeItems= [ { name: "English",code: "en", iso: "en-US", dir: "ltr" }, { name : "español",code: "es", iso: "es-ES", dir: "ltr" }, { name: "中文",code: "zh_cn", iso: "zh-CN", dir: " ltr" }, { name: "Deutsch",code: "de", iso: "de-DE", dir: "ltr" }, { name: "Italiano",code: "it", iso: "it- IT", dir: "ltr" }, { name: "日本语",code: "ja", iso: "ja-JP", dir: "ltr" }, { name: "한국인",code: "ko ", iso: "ko-KR", dir: "ltr" }, { name: "Português",code: "pt", iso: "pt-PT", dir: "ltr" }, ] export const { Link , redirect, usePathname, useRouter } = createSharedPathnamesNavigation({ locales, localePrefix });
2. [locale] directory
All page files in the app directory are placed in the [locale] directory.
3. Middleware: middleware.js
illustrate:
Implement the following style of URL
Default language URL:
The homepage is: www.xxx.com
The inner page is: www.xxx.com/about
URLs in other languages: (Take Spanish as an example)
The homepage is: www.xxx.com/es
The inner page is: www.xxx.com/es/about
In addition, if the default language is entered in the URL, for example, the default language is English, the user enters the URL: www.xxx.com/en
Then, it will automatically go to www.xxx.com
Code:
import createMiddleware from "next-intl/middleware"; import { defaultLocale, localePrefix, locales } from '@/navigation'; export default createMiddleware({ locales, localePrefix , defaultLocale, localeDetection: false, }); export const config = { / / Skip all paths that should not be internationalized. // This skips the folders "api", "_next" and all files // with an extension (eg favicon.ico) matcher: ["/((?!api|_next| .*\\..*).*)"], };
4. Load translation files
1. Translate files
illustrate
My translation files are placed in the "public" directory, but in fact, the translation files can also be placed in other directories, as long as the corresponding path is configured in the i18n.js file.
The translation file is a json file, and the json file can be in a nested format or not. The two, ultimately, will be referenced slightly differently.
Code (not nested)
{ "aaa": "hi", }
Code (nested)
{ "bbb":{ "aaa":"hi", } }
2. i18n.js file
illustrate:
This file is used to import translation files. The key is to configure the path of the translation file, which must be consistent with the path where your translation file is located.
${locale} in the path indicates the language.
code
import { getRequestConfig } from "next-intl/server"; // Create this configuration once per request and // make it available to all Server Components. export default getRequestConfig(async ({ locale }) => ({ // Load translations for the active locale. messages: (await import(`./public/locales/${locale}/common.json`)).default, }));
3. Configuration of next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = {} const withNextIntl = require("next-intl/plugin")("./i18n.js"); module.exports =withNextIntl (nextConfig)
5. Implement translation
Code in layout.js:
import "./globals.css"; import { NextIntlClientProvider, useMessages } from 'next-intl'; import { locales } from '@/navigation'; import Header from '@/components/Header' import Footer from '@/components /Footer' export default function RootLayout({ children, params: { locale } }) { if (!locales.includes(locale)) { notFound(); } const messages = useMessages(); return (
<html lang="{locale}">
<nextintlclientprovider locale="{locale}" messages="{messages}">
<body><header />{children}<footer /></body>
</nextintlclientprovider>
</html>
);
}
Code in page.js:
The parameter locale represents the current language.
If the translation file is not nested, then const t = useTranslations();
If the translation file is nested, use something like: const t = useTranslations("bbb"); based on the file structure and actual needs.
import { useTranslations } from 'next-intl'; import { Link } from '@/navigation'; export default function Home({ params: { locale } }) { const t = useTranslations(); return (<>
<link href="{"/"}"><h1>{t("aaa")}</h1></Link>
</>)
6. Language switcher
1. Effect
Pull down to select a language, and the current page switches to the page in the selected language.
2. Code
"use client";
import { useLocale } from "next-intl";
import { localeItems, useRouter, usePathname, defaultLocale } from '@/navigation';
export default function LangSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
console.log(locale)
const handleChange = (e) => {
router.push(pathname, { locale: e.target.value });
};
return (
<div>
<select
value={locale}
onChange={handleChange}
className="h-8 m-2 p-1 rounded border-current"
>
<option value={locale} > {GetLangData(locale).name}</option>
{localeItems.map((item) => {
if (item.code === locale) return null
return (<option key={item.code} value={item.code}>
{item.name}
</option>)
})}
</select>
</div>
);
}
export function GetLangData(defaultLocale) {
var res = {}
{
localeItems.map(locale => {
if (locale.code === defaultLocale) {
console.log(locale)
res = locale
}
})
}
return res
}
7. SEO and search engine friendly
1. Metadata
Nextjs APP router generates the title and Meta data of the page. I tried three methods:
The first way: use it directly in layout.js: expert const metadata={}
The second way: use dynamically generated Meta data in page.js
For reference:Optimizing: Metadata | Next.js (nextjs.org)
In these two ways, I failed for multi-language title and Meta data. Finally, the third method was used, which is also the simplest and most straightforward method.
The third way: write the title tag and Meta tag directly in page.js.
Therefore, the code of page.js was changed to this:
import { useTranslations } from 'next-intl'; import { Link } from '@/navigation'; export default function Home({ params: { locale } }) { const t = useTranslations(); return (<>
<title>{t("site-name")}</title>
<meta name="keyword" content="{t("site-name")}"></meta>
<meta name="description" content="{t("site-description")}"></meta>
<meta property="og:type" content="website"></meta>
<meta property="og:title" content="{t("site-name")}"></meta>
<meta property="og:description" content="{t("site-description")}"></meta>
<meta property="og:site_name" content="{t("site-name")}"></meta>
<meta property="og:image" content="/images/xxx.png"></meta>
<meta property="og:image:width" content="512"></meta>
<meta property="og:image:height" content="512"></meta>
<meta property="og:image:alt" content=""></meta>
<link href="{"/"}"><h1>{t("aaa")}</h1></Link>
</>)
2. Multilingual navigation: Make it easier for search engines to discover multilingual URLs
illustrate
The language switcher is front-end rendered, so I added back-end rendered multi-language navigation at the bottom of the page, which is the component Footer.js. Clicking on the corresponding language will lead to the homepage of the website in this language.
Footer.js code is as follows:
import Link from 'next/link'
import { localeItems,defaultLocale } from '@/navigation';
export default function Footer(){
return (<>
<div className="divider"></div>
<footer className="footer footer-center p-10 bg-base-200 text-base-content rounded">
<nav className="flex flex-wrap gap-4">
{localeItems.map(locale => {
if (locale.code === defaultLocale) return(<Link className="link link-hover" href="/" key={locale.code}>{locale.name}</Link>)
const thehref="/"+locale.code
return (<Link className="link link-hover" href={thehref} key={locale.code}> {locale.name}</Link>)
})}
</nav>
<aside className='p-6 text-center'>
<p>Copyright © 2024 - All right reserved by xxx.com</p>
</aside>
</footer>
</>
)
}
8. Summary
International routing, structure and introduction of translation files, and implementation of translation. No matter what kind of internationalization plan, these three aspects will be involved. Although it is not troublesome, in general, next-intl is relatively simple.
9. Reference
A Deep Dive into Next.js App Router Localization with next-intl
A complete guide for setting up next-intl with the App Router