Nextjs+I18n multi-language internationalization best practices (search engine friendly)
Note: This best practice is based on next pages routing. Not suitable for app routing.
Table of contents
The basic idea
Use next-i18next.
Add the [locales] directory to the "pages" directory and place the main business logic in this directory.
Pages such as index in the pages directory are used to determine the language, and then call the corresponding localized page logic.
Add the locales directory to the public directory for translation files.
Install
pnpm add next-i18next react-i18next i18nex
Configuration
File Directory
|components ..LangSwitcher.js ..LocLink.js ..MdRenderer.js |lib ..getStatic.js |pages ..[locales] ....index.js .._app.js .._document.js .. index.js |public ..locales ....en ...common.json ...index.md ....es ...common.json ..... .index.md |next-i18next.config.js |next.config.js
next.config.js
const { i18n } = require('./next-i18next.config'); const nextConfig = { i18n }
next-i18next.config.js
module.exports = { 'development', i18n: { defaultLocale: 'en', // locales: [ 'en', 'de', 'es',"cn"], locales: [ { 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" }, ], }, }
For the configuration of locales, considering the scenario of displaying language selection to users, I did not follow most configuration methods on the Internet.
lib/getStatic.js
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import i18nextConfig from "@/next-i18next.config"; export const getI18nPaths = () => i18nextConfig.i18n.locales.map((lng) => ({ params : { locale: lng.code,//add code }, })); export const getStaticPaths = () => ({ fallback: false, paths: getI18nPaths(), }); export const getI18nProps = async (ctx, ns = ["common"]) => { const locale = ctx?.params?.locale || i18nextConfig.i18n.defaultLocale; let props = { ...(await serverSideTranslations(locale, ns)), }; return props; }; export const makeStaticProps = (ns = []) => async (ctx) => ({ props: await getI18nProps(ctx, ns), });
This is the logic used to obtain the language translation file and translate it on the server side.
_app.js
import { appWithTranslation } from "next-i18next"; import Layout from '../components/layout' import { NextPage } from 'next'; const MyApp = ({ Component, pageProps }) => ; export default appWithTranslation(MyApp);
NOTE: If not using This label can be removed directly.
_docoument.js
import i18nextConfig from "@/next-i18next.config"; // Other code import Document, { Html, Head, Main, NextScript, } from 'next/document' class MyDocument extends Document { render() { const currentLocale = this. props.__NEXT_DATA__.query.locale || i18nextConfig.i18n.defaultLocale; console.log(currentLocale) return <html lang="{currentLocale}">
<head>
<link href="/app.css" />
<link rel="shortcut icon" href="/icon.png" />
</head>
<body>
<main />
<nextscript />
</body>
</html>; } } export default MyDocument
index.js
import Home, { getStaticProps } from "./[locale]/index"; export default Home; export { getStaticProps };
[locales] folder
Used to implement international routing similar to www.xxx.com/es
public/locales folder
Used to store translation files.
components
Language selector: LangSwitcher.js
code
'use client'
import React from "react";
import { useRouter } from "next/router"
import i18nextConfig from '@/next-i18next.config'
console.log(i18nextConfig.i18n.defaultLocale)
const LangSwitcher = ({ lang, ...rest}) => {
const router = useRouter();
const GetHref=(locale)=> {
console.log(locale)
let href = rest.href || router.asPath
let pName = router.pathname
Object.keys(router.query).forEach(k => {
if (k === 'locale') {
pName = pName.replace(`[${k}]`, locale)
return
}
pName = pName.replace(`[${k}]`, router.query[k])
})
if (locale == i18nextConfig.i18n.defaultLocale) {
if (pName == '/' + i18nextConfig.i18n.defaultLocale) {
href = '/'
} else {
href = `${href}`
}
} else {
if (locale) {
href = rest.href ? `/${locale}${rest.href}` : pName
}
if (href.indexOf(`/${locale}`) < 0) {
href = `/${locale}${href}`
}
}
return href
}
const LocalChanged = (value) => {
const thehref = GetHref(value)
router.push(thehref)
}
return (
<div>
🌐
<select onChange={(e) => LocalChanged(e.target.value)} defaultValue={lang} className="h-8 m-2 p-1 rounded border-current">
<option value={lang} > {GetLangData(lang).name}</option>
{i18nextConfig.i18n.locales.map(locale => {
if (locale.code === lang) return null
return (<option value={locale.code} key={locale.code}> {locale.name}</option>)
})}
</select>
</div>
)
}
//export
export function GetLangData(lang) {
var res = {}
{
i18nextConfig.i18n.locales.map(locale => {
if (locale.code === lang) {
console.log(locale)
res = locale
}
})
}
return res
}
export default LangSwitcher
Instructions:
Introduce components and use them directly , and the lang parameter is set to the current language.
import LangSwitcher from './LangSwitcher' const { i18n } = useTranslation('home');
Localized Link component: LocLink.js
import React from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import i18nextConfig from '@/next-i18next.config' const LinkComponent = ({ children, skipLocaleHandling, .. .rest }) => { const router = useRouter(); const locale = rest.locale || router.query.locale || ""; console.log(router) let href = rest.href || router.asPath; console.log(href) if (href.indexOf("http") === 0) skipLocaleHandling = true; if (locale && !skipLocaleHandling) { // href = href console.log(i18nextConfig.i18n.defaultLocale) console. log(locale) locale==i18nextConfig.i18n.defaultLocale ? href : router.pathname.replace("[locale]", locale); } console.log(href) return (
<>
<link href="{href}" legacybehavior>
<a {...rest}>{children}</a>
</Link>
</>
); }; export default LinkComponent;
Localized Markdown file acquisition and rendering components: MdRenderer.js
import ReactMarkdown from 'react-markdown' import { useState, useEffect } from "react"; import { useRouter } from "next/router" export default function MdRenderer({ path }) { const [a, setA] = useState("" ); const router = useRouter() fetch(path, { next: { revalidate: 0 } }) .then(response => response.text()) .then(data => { setA(data) } ).catch( err=>{console.log(err)}) return ( {a && ( {a} )} ) }
Instructions
t function
- Introducing "useTranslation"
- define t
- Use {t('xxx')}, that's it
import { useTranslation } from "next-i18next"; const { t } = useTranslation(["common"]);//common.json is the translation file {t("xxx")}
How to get the current language
import { useTranslation } from "next-i18next"; const { i18n } = useTranslation('home'); console.log(i18n.language)
How to loop through a list of languages
Get the language list and customize the language navigation.
import i18nextConfig from '@/next-i18next.config' {i18nextConfig.i18n.locales.map(locale => { if (locale.code === i18nextConfig.i18n.defaultLocale) return( {locale.name} ) const thehref="/"+locale.code return ( {locale.name} ) })}
Translation of content paragraphs
If the content is obtained from the backend, then this is not needed.
However, if the content exists on the front end, it is very troublesome to store it in json. Therefore, it can be stored in markdown, and then obtain the markdown file of the corresponding language version where the content needs to be displayed, and then parse and display it.
import MdRenderer from '../../components/MdRenderer'
Summarize
Multi-language allows a website to get more traffic. This nextjs multi-language best practice fully considers the needs of SEO. Search engine friendly.
Suitable for tool stations, keyword stations, news and content stations.