Next.js14 App Router配置国际化
- Published on
- Reading time
- 8 min read
- Likes
前言
使用到的插件
next-i18n-router
默认情况下,next-i18n-router 会解析请求的 Accept-Language 头部来确定用户偏好的语言环境,并根据设置重定向或重写 URL。
i18next + react-i18next
react-i18next 是基于 i18next 框架构建的一个库,专门用于在 React 应用中实现国际化和本地化功能。i18next 提供了核心的国际化功能,而 react-i18next 则为这些功能在 React 环境中的应用提供了便捷的接口和组件。
i18next-resources-to-backend
i18next-resources-to-backend 是一个用于 i18next 的插件,它允许开发者从远程资源(例如服务器或 API)动态加载翻译文件。
这个插件搭配 rsc ,比如在应用启动后从服务器拉取最新的翻译数据然后填充到 html 中,避免在初始加载时包含所有可能的翻译文件,从而减少应用的初始大小。
开始配置
安装依赖
npm install i18next react-i18next i18next-resources-to-backend next-i18n-router
配置路由
需要用到next-i18n-router这个插件
这个插件的原理是在nextjs的路由中间件中根据请求头的Accept-Language来判断用户的语言偏好,然后根据配置的语言列表和默认语言来重定向或重写URL。
比如用户请求的URL是 /zh/about ,如果用户的语言偏好是英文,那么会重定向到 /en/about
- 首先创建 config 配置 在项目根目录创建 i18nConfig.ts
const i18nConfig = {
locales: ['en', 'zh'], // 支持的语言列表
defaultLocale: 'en', // 默认语言
}
export default i18nConfig
- 配置路由动态 path 在 app 目录下创建 [locale] 目录,然后将之前 app 目录下的文件全部移动到 [locale] 目录下
└── app
└── [locale]
├── layout.js
└── page.js
- 创建路由中间件 在项目根目录创建 middleware.ts 文件,如果配置文件地址不同,请自行修改
import { i18nRouter } from 'next-i18n-router'
import i18nConfig from '../i18nConfig' // 引入配置文件
import { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return i18nRouter(request, i18nConfig)
}
// 配置只适用于中间件的路由
export const config = {
matcher: '/((?!api|static|.*\\..*|_next).*)',
}
配置翻译文件
在项目根目录下创建 locales 目录,然后在 locales 目录下创建 en 和 zh 目录,分别存放英文和中文的翻译文件, 对应的文件名 common 是命名空间,可以根据实际情况进行修改。
└── src
└── locales
├── en
│ └── common.json
└── zh
└── common.json
对应文件内容如下
en/common.json
{
"msg": "Hello, World!"
}
zh/common.json
{
"msg": "你好,世界!"
}
加载翻译文件
在项目根目录下创建 i18n.ts 文件,请根据实际情况修改对应路径
生成一个 i18next 实例,该实例将用于加载和管理翻译文件。然后使用 i18next-resources-to-backend 插件从远程资源加载翻译文件。
import { Resource, createInstance, i18n } from 'i18next'
import { initReactI18next } from 'react-i18next/initReactI18next'
import resourcesToBackend from 'i18next-resources-to-backend'
import i18nConfig from '../../i18nConfig' // 引入之前的配置文件,根据实际情况修改路径
export default async function initTranslations(
locale: string,
namespaces?: string[],
i18nInstance?: i18n,
resources?: Resource
) {
namespaces = namespaces || ['common']
// locale = locale || i18nConfig.defaultLocale;
i18nInstance = i18nInstance || createInstance()
i18nInstance.use(initReactI18next)
if (!resources) {
i18nInstance.use(
resourcesToBackend(
(language: string, namespace: string) => import(`@/locales/${language}/${namespace}.json`)
)
)
}
await i18nInstance.init({
lng: locale,
resources,
fallbackLng: i18nConfig.defaultLocale,
supportedLngs: i18nConfig.locales,
defaultNS: namespaces[0],
fallbackNS: namespaces[0],
ns: namespaces,
preload: resources ? [] : i18nConfig.locales,
})
return {
i18n: i18nInstance,
resources: i18nInstance.services.resourceStore.data,
t: i18nInstance.t,
}
}
在服务端加载翻译文件
导入上一步的配置文件,根据实际情况修改路径
import initTranslations from '@/i18n' // 引入之前的配置文件,根据实际情况修改路径
export default async function Home({ params: { locale } }) {
const { t } = await initTranslations(locale)
return (
<main>
<h1>{t('msg')}</h1>
</main>
)
}
在客户端加载翻译文件
单独新建一个Provider组件,用于客户端加载翻译文件
'use client'
import { I18nextProvider } from 'react-i18next'
import { ReactNode } from 'react'
import initTranslations from '@/i18n'
import { createInstance, Resource } from 'i18next'
export interface TranslationsProviderProps {
children: ReactNode
locale: string
namespaces?: string[]
resources?: Resource
}
export default function TranslationsProvider({
children,
locale,
namespaces,
resources,
}: TranslationsProviderProps) {
const i18n = createInstance()
initTranslations(locale, namespaces, i18n, resources)
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
}
引用组件加载翻译文件,尽管我们用这个客户端组件包裹了页面,但页面仍作为服务端组件进行渲染
import initTranslations from '../i18n'
import TranslationsProvider from '@/components/TranslationsProvider'
const i18nNamespaces = ['common']
export default async function Home({ params: { locale } }) {
const { t, resources } = await initTranslations(locale, i18nNamespaces)
return (
<TranslationsProvider namespaces={i18nNamespaces} locale={locale} resources={resources}>
<main>
<h1>{t('header')}</h1>
</main>
</TranslationsProvider>
)
}
在客户端组件中使用翻译文件
'use client'
import { useTranslation } from 'react-i18next'
export default function ExampleClientComponent() {
const { t } = useTranslation()
return <h3>{t('msg')}</h3>
}
配置 layout
最后为了更好的seo 和无障碍等,我们需要在 html标签上添加 lang 和 dir。
修改app/[locale]/layout.tsx
import { Inter } from 'next/font/google'
import '../globals.css'
import i18nConfig from '@/i18n/i18nConfig'
import { dir } from 'i18next'
const inter = Inter({ subsets: ['latin'] })
export function generateStaticParams() {
return i18nConfig.locales.map((locale) => ({ locale }))
}
export default function RootLayout({
children,
params: { locale },
}: Readonly<{
children: React.ReactNode
params: { locale: string }
}>) {
return (
<html lang={locale} dir={dir(locale)}>
<body className={inter.className}>{children}</body>
</html>
)
}
配置 WebStorm 支持
安装对应的 i18n 插件, 然后在 WebStorm 中配置对应的翻译文件路径
这样不仅可以在代码中直接使用翻译文件的 key,还可以在 WebStorm 中直接查看翻译文件的内容。
本文采用CC BY-NC-SA 4.0 - 非商业性使用 - 相同方式共享 4.0 国际进行许可。