Nextjs+I18n多语言国际化最佳实践(搜索引擎友好)
注:这个最佳实践是基于next的pages路由。并不适合app路由。 目录 基本思路 使用next-i18ne […]
注:这个最佳实践是基于next的pages路由。并不适合app路由。
目录
基本思路
使用next-i18next。
在“pages”目录中增加[locales]目录,将主要业务逻辑放到这个目录中。
pages目录中的index等页面用于判断语言,然后调用相应本地化页面逻辑
public目录中增加locales目录,用于翻译文件。
安装
pnpm add next-i18next react-i18next i18nex
配置
文件目录
|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" },
],
},
}
对于locales的配置,考虑到对用户展示语言选择的场景,我没有按照网上大多数的配置方式。
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),
});
这是用来获取语言翻译文件和在服务端翻译的逻辑。
_app.js
import { appWithTranslation } from "next-i18next";
import Layout from '../components/layout'
import { NextPage } from 'next';
const MyApp = ({ Component, pageProps }) => <Layout> <Component {...pageProps} /> </Layout>;
export default appWithTranslation(MyApp);
注意:如果不使用<Layout>可以直接将这个标签去掉。
_docoument.js
import i18nextConfig from "@/next-i18next.config";
// 其他代码
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]文件夹
用来实现类似www.xxx.com/es的国际化路由
public/locales文件夹
用来存放翻译文件。
组件
语言选择器:LangSwitcher.js
代码
'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
使用方法:
引入组件,直接使用<LangSwitcher>,并且lang参数设置为当前的语言。
import LangSwitcher from './LangSwitcher'
const { i18n } = useTranslation('home');
<LangSwitcher lang={i18n.language}></LangSwitcher>
本地化Link组件: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;
本地化Markdown文件获取和渲染组件: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 (
<article className="prose lg:prose-xl">
{a && (<ReactMarkdown>{a}
</ReactMarkdown>)}
</article>)
}
使用方法
t函数
- 引入“useTranslation”
- 定义t
- 使用{t(‘xxx’)},即可
import { useTranslation } from "next-i18next";
const { t } = useTranslation(["common"]);//common.json为翻译文件
{t("xxx")}
如何获取当前的语言
import { useTranslation } from "next-i18next";
const { i18n } = useTranslation('home');
console.log(i18n.language)
如何循环获取语言列表
获取语言列表,可以自定义语言导航。
import i18nextConfig from '@/next-i18next.config'
{i18nextConfig.i18n.locales.map(locale => {
if (locale.code === i18nextConfig.i18n.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>)
})}
内容段落的翻译
如果内容是从后端获取的,那么,这个就不需要了。
但,如果内容存在前端,存在json里非常麻烦,所以,可以存在markdown中,然后在需要展示这个内容的地方获取相应语言版本的markdown文件,然后解析展示出来。
import MdRenderer from '../../components/MdRenderer'
<MdRenderer path={"locales/"+i18n.language+ "/index.md"} />
总结
多语言,可以让一个网站获取更多的流量。这个nextjs的多语言最佳实践,充分考虑了SEO的需要。搜索引擎友好。
适合做工具站、关键词站、新闻和内容站。