【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和国家代码对照表 - 河畔一角 - 博客园

相关推荐
mldong4 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60615 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角6 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!6 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi8 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马9 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒9 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端