本文将介绍如何 nextjs 的 pages router 和 app router 范式中如何通过最少步骤完成 i18n 的配置,实现项目的国际化。本文国际化方法均采用子路径策略,且都是通过监听请求头来改变路径的。
首先我们需要清楚 i18n 是什么,以及如何去检验我们的 i18n 配置是否生效。i18n 即 internationalization 的缩写,译为国际化。通过检查请求头 Accept-Language
字段的值,实现在不同的浏览器语言下显示对应语言文字内容的网页。(文末有国家语言代码文档链接)
下面拿 Edge 浏览器为例来查看配置是否生效(chrome 也是类似的)。打开设置 > 语言,我们可以用'sv'、'cn'等语言标识来添加语言,还可以将语言移到顶部,作为首选语言。如果修改了浏览器的首选语言,就可以看到页面的文字语言发生了变化。
Page Router
在 Page Router 中,我们可以通过安装 next-i8next
(next-i18next - npm)来实现国际化。
第一步,安装依赖,pnpm install next-i18next
。
第二步,在项目根目录创建 next-i18next 的配置文件 next-i18next.config.mjs
(文件名和文件位置可任意)。文件内容参考如下:
javascript
import path from "path";
/** @type {import("next-i18next").UserConfig} */
const config = {
debug: process.env.NODE_ENV === "development",
reloadOnPrerender: process.env.NODE_ENV === "development",
i18n: {
locales: ["en", "it"],
defaultLocale: "en",
},
localePath: path.resolve("./public/locales"),
};
export default config;
第三步,配置 next.config.js,我们可以按照 Nextjs 的官方文档配置 i18n 选项,当然,这里我们直接引入 next-i8next 的配置文件中的 i18n 字段,这样只需要维护 next-i18next 的配置就可以了。
JavaScript
import nextI18nConfig from './next-i18next.config.mjs'
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
i18n: nextI18nConfig.i18n
};
export default nextConfig;
第四步,在_app. tsx 中使用 appWithTranslation 将配置传入 App 中。
javascript
import type { AppProps } from "next/app";
import { appWithTranslation } from "next-i18next";
import nextI18nConfig from '../next-i18next.config.mjs'
const App = ({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />;
}
const I18nApp = appWithTranslation(App, nextI18nConfig);
export default I18nApp;
第五步,创建对应的语言文件。位置任意,这里我在上面第二步中设置了 localePath 为./public/locales,所以我创建了以下两个文件。 英语,./public/locales/en/common. json:
json
{
"title": "hello",
"main": {
"text": "Implementing internationalization in page router"
}
}
意大利语,./public/locales/it/common. json:
json
{
"title": "ciao",
"main": {
"text": "Implementare l'internazionalizzazione nel router di pagine"
}
}
使用示例:在 index. tsx 使用 t 函数实现 i18n。
javascript
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import nextI18nConfig from '../next-i18next.config.mjs'
export const getServerSideProps = async ({ locale }: { locale: string }) => ({
props: {
...(await serverSideTranslations(locale, ["common"], nextI18nConfig, nextI18nConfig.i18n.locales)),
},
});
export default function Home() {
const { t } = useTranslation('common')
return (
<>
<div>{t('title')}</div>
<div>{t('main.text')}</div>
</>
);
}
运行项目,修改浏览器的首选语言,就可以看到语言和路径发生了变化。
App Router
App Router 的配置相对 Pages Router 其实比较麻烦,但是只要按照本文的步骤来,就可以少踩很多坑!
第一步,安装依赖,pnpm install i18next accept-language i18next-resources-to-backend react-i18next
。
第二步,改变项目路径,将 page. tsx、layout. tsx 放在 app/[lng]下。
第三步,创建配置文件 app/lib/i18n/setting.js
。
JavaScript
export const fallbackLng = 'en'
export const languages = [fallbackLng, 'it']
export const defaultNS = 'translation'
export function getOptions(lng = fallbackLng, ns = defaultNS) {
return {
// debug: true,
supportedLngs: languages,
fallbackLng,
lng,
fallbackNS: defaultNS,
defaultNS,
ns
}
}
第四步,创建文件 app/lib/i18n/index.js
定义钩子,该文件定义了我们从哪里读取翻译文本文件。
JavaScript
import { createInstance } from 'i18next'
import resourcesToBackend from 'i18next-resources-to-backend'
import { initReactI18next } from 'react-i18next/initReactI18next'
import { getOptions } from './settings'
const initI18next = async (lng, ns) => {
const i18nInstance = createInstance()
await i18nInstance
.use(initReactI18next)
.use(resourcesToBackend((language, namespace) => import(`../../../public/locales/${language}/${namespace}.json`)))
.init(getOptions(lng, ns))
return i18nInstance
}
export async function useTranslation(lng, ns, options = {}) {
const i18nextInstance = await initI18next(lng, ns)
return {
t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix),
i18n: i18nextInstance
}
}
第五步,实现根据请求头进行跳转。直接在根目录下创建 middleware.js
。
JavaScript
import { NextResponse } from 'next/server'
import acceptLanguage from 'accept-language'
import { fallbackLng, languages, cookieName } from './settings'
acceptLanguage.languages(languages)
export const config = {
// matcher: '/:lng*'
matcher: ['/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js).*)']
}
export function middleware(req) {
const lng = acceptLanguage.get(req.headers.get('Accept-Language')) || fallbackLng
// Redirect if lng in path is not supported
if (
!languages.some(loc => req.nextUrl.pathname.startsWith(`/${loc}`)) &&
!req.nextUrl.pathname.startsWith('/_next')
) {
return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url))
}
return NextResponse.next()
}
第六步,在 layout. tsx 的html上注入 lng 属性。
JavaScript
import { dir } from 'i18next'
import { languages } from '../lib/i18n/settings'
export async function generateStaticParams() {
return languages.map((lng) => ({ lng }))
}
export default function RootLayout({
children,
params: {
lng
}
}: Readonly<{
children: React.ReactNode,
params: {
lng: string
}
}>) {
return (
<html lang={lng} dir={dir(lng)}>
<head />
<body>
{children}
</body>
</html>
)
}
使用示例: 和 pages router 方案类似,翻译文件我放在 public/locales 下面。
JavaScript
// page.tsx
import { useTranslation } from '../lib/i18n/index'
export default async function Page({ params: { lng } }: { params: { lng: string } }) {
const { t } = await useTranslation(lng, 'common')
return (
<>
<div>{t('title')}</div>
<div>{t('main.text')}</div>
</>
);
}
语言选择器
上面的方法都是通过监听请求头来改变路径的,如果在你的项目中需要选择器,你可以在前面的基础上,通过设置 cookie 的方法来实现:使用中间件拦截页面路由,如果是以国家语言标识开头,则保存到 cookies 中,如果不是,则从 cookies 或 Accept-Language 中读取目标语言。
只需修改中间件(App Router 写法):
JavaScript
import { fallbackLng, languages, cookieName } from './settings'
......
export async function middleware(req) {
const cookieName='i18next'
if (req.nextUrl.pathname.startsWith('/_next')) return NextResponse.next()
let lng
if (req.cookies.has(cookieName)) lng = acceptLanguage.get(req.cookies.get(cookieName).value)
if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'))
if (!lng) lng = fallbackLng
if (!languages.some(loc => req.nextUrl.pathname.startsWith(`/${loc}`))) {
return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url))
}
const lngInReferer = languages.find((l) => req.nextUrl.pathname.startsWith(`/${l}`))
const response = NextResponse.next()
if (lngInReferer) response.cookies.set(cookieName, lngInReferer)
return response
}
- 这里我在 settings 中又定义了一个变量 cookieName="i18next",代表存入 cookies 的键名。
- 如果路径自带语言,就保存这个语言。只需要一个 Link 跳转页面就可以实现语言切换。
- 如果路径不带语言,就从 cookies 或者 Accept-Language 中读取,然后进行跳转。
语言切换器示例:
JavaScript
// page.tsx
import { useTranslation } from '../lib/i18n/index'
import Link from 'next/link';
export default async function Page({ params: { lng } }: { params: { lng: string } }) {
const { t } = await useTranslation(lng, 'common')
return (
<>
<div>{t('title')}</div>
<div>{t('main.text')}</div>
<div style={{ 'display': 'flex', 'flexDirection': "column", 'gap': '10px' }}>
<Link href='/en' >en</Link>
<Link href='/it' >it</Link>
<Link href='/' >/</Link>
</div>
</>
);
}
如果是 pages router,使用 useTranslation 返回的 i18n 对象,调用对象的 changeLanguage 方法就可以了。
javascript
import { useTranslation } from "next-i18next";
const { t, i18n } = useTranslation('common')
i18n.changeLanguage('en')
示例:
JavaScript
...
export default function Home() {
const { t, i18n } = useTranslation('common')
return (
<>
<div>{t('title')}</div>
<div>{t('main.text')}</div>
<div style={{ 'display': 'flex', 'flexDirection': "column", 'gap': '10px' }}>
<div onClick={() => i18n.changeLanguage('en')}>en</div>
<div onClick={() => i18n.changeLanguage('it')}>it</div>
<Link href='/'>/</Link>
</div>
</>
);
}
总结
本文介绍了如何在 Next. js 中通过 pages router 和 app router 范式实现国际化配置,以便根据用户的浏览器语言显示对应的网页内容。文中所述的国际化方法均采用子路径策略,通过监听请求头来改变路径,实现项目的国际化。文章首先对 i18n(国际化)进行了解释,并指导读者如何验证 i18n 配置是否生效。接着分别介绍了在 Page Router 和 App Router 中实现国际化的步骤。
在 Page Router 中,通过安装 next-i18next
插件,配置 i18n 选项,并创建对应的语言文件,实现了国际化。同时提供了使用示例,展示了如何在页面中使用 i18n 功能。
在 App Router 部分,通过改变项目路径、创建配置文件、定义钩子和实现根据请求头进行跳转等步骤,介绍了如何在 App Router 中配置国际化。此外,还展示了如何实现语言选择器,并提供了相应的示例代码。
通过本文的介绍,相信你可以清晰地了解如何在 Next. js 中实现国际化配置,以及如何根据具体需求选择合适的国际化方案!
仓库地址:
GitHub - tangqiang0605/i18n-pages
GitHub - tangqiang0605/i18n-app
参考
Pages Router 国际化:Site Unreachable
App Router 国际化: i18n with Next.js 13/14 and app directory / App Router (an i18next guide)