Next.js 14 集成i18next语言切换后旧路由语言回退问题
问题描述
使用Next.js 14结合i18next实现多语言切换时,遇到路由导航语言回退问题:
- 框架与依赖版本:
"next": "14.2.13", "i18next": "^23.15.1", "i18next-resources-to-backend": "^1.2.1", "next-i18n-router": "^5.5.1", "react-i18next": "^15.0.2" - 问题表现:当语言设置为
en时访问/dashboard/path1、/dashboard/path2等路由,切换至其他语言后,新路由(如/dashboard/path3)可正常显示新语言,但返回之前访问过的旧路由时,语言会自动回退至en。
相关代码片段
i18n-config.ts
import { Config } from 'next-i18n-router/dist/types'; const i18nConfig: Config = { locales: [ 'en', 'fa' ], defaultLocale: 'fa' } as const; export type TLocale = (typeof i18nConfig)['locales'][number]; export default i18nConfig;
i18n.ts
import { createInstance, i18n, Resource } from 'i18next'; import { initReactI18next } from 'react-i18next/initReactI18next'; import resourcesToBackend from 'i18next-resources-to-backend'; import i18nConfig from '@/i18n-config'; export default async function initTranslations( locale: string, namespaces: string[], i18nInstance?: i18n, resources?: Resource ) { 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 }; }
middleware.ts
import { withAuth } from 'next-auth/middleware'; import { i18nRouter } from 'next-i18n-router'; import i18nConfig from './i18n-config'; export default withAuth( async function middleware(request) { return i18nRouter(request, i18nConfig); }, { callbacks: { authorized: ({ req, token }) => { return !((req.nextUrl.pathname.includes('/dashboard') || req.nextUrl.pathname.includes( '/auth/create-app')) && token === null); } } } ); export const config = { matcher: [ '/((?!api|_next/static|_next/image|assets|.*\\..*|favicon.ico|sw.js).*)' ] };
layout.tsx
export const generateStaticParams = async () => i18nConfig?.locales?.map(locale => ({ locale })); const namespaces = [ 'common', 'auth', 'rulesMessage', 'dashboard', 'applications', 'providers', 'apis', 'industries', 'services', 'reports', 'requestMessages' ]; const RootLayout: FC<PropsWithChildren & TPropsWithLocaleParam> = async ({ children, params: { locale } }) => { const session = await getServerSession(authOptions); const { resources, i18n: { dir } } = await initTranslations(locale, namespaces); return ( <html lang={ locale } dir={ dir() }> <body className="!font-dynamicFont overflow-x-hidden"> <AntdRegistry> <NextTopLoader /> <TranslationsProvider locale={ locale } namespaces={ namespaces } resources={ resources }> <NextAuth session={ session }> { children } </NextAuth> </TranslationsProvider> </AntdRegistry> </body> </html> ); }; export default RootLayout;
translations-provider.tsx
'use client'; import { I18nextProvider } from 'react-i18next'; import { ReactNode } from 'react'; import { createInstance, Resource } from 'i18next'; import initTranslations from '@/i18n'; export default function TranslationsProvider({ children, locale, namespaces, resources }: { children: ReactNode; locale: string; namespaces: string[]; resources: Resource; }) { const i18n = createInstance(); initTranslations(locale, namespaces, i18n, resources); return <I18nextProvider i18n={ i18n }>{ children }</I18nextProvider>; }
language-switcher.tsx
'use client'; import { useTranslation } from 'react-i18next'; import { usePathname, useRouter } from 'next/navigation'; import i18nConfig from '@/i18n-config'; export const LanguageSwitcher = () => { const { i18n } = useTranslation(); const currentLocale = i18n.language; const router = useRouter(); const currentPathname = usePathname(); const handleChangeLanguage = () => { const newLocale = currentLocale === 'en' ? 'fa' : 'en'; // set cookie for next-i18n-router const days = 30; const date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); const expires = date.toUTCString(); document.cookie = `NEXT_LOCALE=${ newLocale };expires=${ expires };path=/`; // Set the new locale in the i18n instance i18n.changeLanguage(newLocale); // Update the URL if (currentLocale === i18nConfig.defaultLocale && !i18nConfig.prefixDefault) { router.replace('/' + newLocale + currentPathname); } else { router.push(currentPathname.replace(`/${ currentLocale }`, `/${ newLocale }`)); } }; return ( <div className="--headerItems --rounded text-primary-0 text-captionMd capitalize cursor-pointer" onClick={ handleChangeLanguage } > { currentLocale === 'fa' ? 'en' : 'fa' } </div> ); };
原因分析
- Next.js客户端路由缓存:App Router默认缓存已访问页面的客户端状态,返回旧路由时直接读取缓存,未重新获取最新语言设置。
- i18n实例未全局同步:
TranslationsProvider每次创建新的i18n实例,切换语言时仅更新当前页面实例,缓存页面的旧实例未同步更新。 - 路由跳转未触发刷新:语言切换时的路由跳转未强制刷新页面,缓存的旧页面组件未重新渲染。
解决方案
1. 禁用客户端缓存
在[locale]/layout.tsx中添加动态渲染配置,强制每次访问都重新获取语言状态:
// [locale]/layout.tsx export const dynamic = "force-dynamic";
2. 全局共享i18n实例
修改TranslationsProvider,使用单例实例确保语言状态全局同步:
'use client'; import { I18nextProvider } from 'react-i18next'; import { ReactNode, useEffect, useState } from 'react'; import { createInstance, i18n, Resource } from 'i18next'; import initTranslations from '@/i18n'; // 全局单例i18n实例 let i18nInstance: i18n | null = null; export default function TranslationsProvider({ children, locale, namespaces, resources }: { children: ReactNode; locale: string; namespaces: string[]; resources: Resource; }) { const [i18n, setI18n] = useState<i18n | null>(null); useEffect(() => { if (!i18nInstance) { i18nInstance = createInstance(); initTranslations(locale, namespaces, i18nInstance, resources).then(setI18n); } else { // 同步更新语言和资源 i18nInstance.changeLanguage(locale); namespaces.forEach(ns => { if (resources[locale]?.[ns]) { i18nInstance!.addResources(locale, ns, resources[locale][ns]); } }); setI18n(i18nInstance); } }, [locale, namespaces, resources]); if (!i18n) return null; return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>; }
3. 优化语言切换的路由逻辑
添加router.refresh()强制刷新页面,清除客户端缓存:
// language-switcher.tsx const handleChangeLanguage = () => { const newLocale = currentLocale === 'en' ? 'fa' : 'en'; // 设置cookie const days = 30; const date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); const expires = date.toUTCString(); document.cookie = `NEXT_LOCALE=${newLocale};expires=${expires};path=/`; // 构造新路径 let newPathname = currentPathname.replace(`/${currentLocale}`, `/${newLocale}`); if (currentLocale === i18nConfig.defaultLocale && !i18nConfig.prefixDefault) { newPathname = `/${newLocale}${currentPathname}`; } // 跳转并强制刷新 router.replace(newPathname); router.refresh(); };
4. 明确middleware的cookie配置
确保i18nRouter优先读取NEXT_LOCALE cookie:
// middleware.ts export default withAuth( async function middleware(request) { return i18nRouter(request, { ...i18nConfig, localeCookie: 'NEXT_LOCALE' }); }, // ...其他配置 );
内容的提问来源于stack exchange,提问作者Masoud Tesna
相关产品推荐
相关产品推荐

