【Nextjs】2024年Nextjs国际化配置方案(包括Pages Router和App Router方法)

本文将介绍如何 nextjs 的 pages router 和 app router 范式中如何通过最少步骤完成 i18n 的配置,实现项目的国际化。本文国际化方法均采用子路径策略,且都是通过监听请求头来改变路径的。

首先我们需要清楚 i18n 是什么,以及如何去检验我们的 i18n 配置是否生效。i18n 即 internationalization 的缩写,译为国际化。通过检查请求头 Accept-Language 字段的值,实现在不同的浏览器语言下显示对应语言文字内容的网页。(文末有国家语言代码文档链接)

下面拿 Edge 浏览器为例来查看配置是否生效(chrome 也是类似的)。打开设置 > 语言,我们可以用'sv'、'cn'等语言标识来添加语言,还可以将语言移到顶部,作为首选语言。如果修改了浏览器的首选语言,就可以看到页面的文字语言发生了变化。

Page Router

在 Page Router 中,我们可以通过安装 next-i8nextnext-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
}
  1. 这里我在 settings 中又定义了一个变量 cookieName="i18next",代表存入 cookies 的键名。
  2. 如果路径自带语言,就保存这个语言。只需要一个 Link 跳转页面就可以实现语言切换。
  3. 如果路径不带语言,就从 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)

国家语言代码:每个国家对应的语言Locale和国家代码对照表 - 河畔一角 - 博客园

相关推荐
正小安25 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr3 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript