如何在React中结合react-router与i18next实现多语言路由切换
问题描述
我已经用i18next完成了React组件的翻译,现在希望切换语言时,路由能自动变为带对应语言前缀的形式(比如从localhost:3000/en切换为瑞典语时自动变为/se)。以下是我的相关代码,请求实现方案:
App.jsx 路由代码
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'> <SelectedContext.Provider value={{ selected, setSelected }}> <UserContext.Provider value={{ user, setUser }}> <Toaster /> <Elements stripe={stripePromise}> <Router> <div className='w-screen'> <LoadingBarComponent /> {/* Render the appropriate component based on the route */} <Navigation userProfile={userProfile}/> <Routes> <Route path='*' element={<ErrorPage />} /> <Route path="/" element={<Home userPlan={userPlan} stripeID={stripeID} />} /> <Route path='/terms' element={<Terms />} /> <Route path='/create' element={<Dashboard userPlan={userPlan} userProfile={userProfile} />} /> <Route path='/create/add' element={<Create userPlan={userPlan} />} /> <Route path='/create/tag/:category' element={<Dubs />} /> <Route path='/Signup' element={<Signup />} /> <Route path='/login' element={<Login />} /> <Route path='/reset-password' element={<ResetPass />} /> <Route path='/reset-password/reset' element={<NewPassword />} /> <Route path='/pricing' element={<Pricing stripeID={stripeID} userPlan={userPlan} />} /> <Route path='/credits' element={<Credits credits={credits} setCredits={setCredits} stripeID={stripeID} />} /> <Route path='/create/editor/:voiceName' element={<Editor user={user} credits={credits} setCredits={setCredits} userPlan={userPlan} />} /> <Route path='/success/confirm' element={<ConfirmEmail />} /> <Route path='/payment/success' element={<PaymentSuccess />} /> <Route path='/billing' element={<Billing user={user} userPlan={userPlan} stripeID={stripeID} />} /> <Route path='/create/search' element={<SearchMobile />} /> <Route path='/login-popup' element={<LoginPopup />} /> <Route path='/voices' element={<MyVoices user={user} userPlan={userPlan} />} /> <Route path='/profile/settings' element={<SettingsPage />} /> </Routes> <FooterComponent /> </div> </Router> </Elements> </UserContext.Provider> </SelectedContext.Provider> </ThemeProvider>
Footer.jsx 语言切换代码
const Footer = () => { const navigate = useNavigate(); const [language, setLanguage] = useState(navigator.language.slice(0,2) || "en"); const languageSum = { en: "English", sv: "Swedish" } const getLanguage = (language) => { return languageSum[language] } const openInNewTab = (url) => { window.open(url, "_blank", "noreferrer"); }; const handleLanguageClick = (language) => { i18next.changeLanguage(language); setLanguage(language); console.log("set language", language); }; return ( <div className="pt-[160px]"> <div className="pt-[160px]"> <Separator /> </div> <div id="footer-container" className="pt-[35px] flex flex-row space-x-2 justify-center"> <div className="flex flex-col space-y-1"> <div className="flex-row flex space-x-1"> <div id="footer-text" className="flex items-center"> <img id="footer-logo" src={Logo} className="w-[25px] h-[25px]" alt="logo"></img> <h2 id="footer-name" className="text-xl font-semibold">VoiceOvery</h2> <DropdownMenu> <DropdownMenuTrigger> <Button> <Globe className="mr-1" /> {getLanguage(language)} </Button> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Languages</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem onClick={() => handleLanguageClick("en")}>English</DropdownMenuItem> <DropdownMenuItem>German</DropdownMenuItem> <DropdownMenuItem onClick={() => handleLanguageClick("sv")}>Swedish</DropdownMenuItem> <DropdownMenuItem>Spanish</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div>
i18next 配置代码
const options = { order: ['querystring', 'navigator'], lookupQuerystring: 'lng' } i18next .use(initReactI18next) // Initialize the i18next instance. .init({ // Default langauge detection: options, lng: navigator.language || "en", // Fallback language fallbackLng: "en", debug: true, interpolation: { escapeValue: false, }, resources: { en: { translation: { ...landingEn, ...faqEn, ...pricingEn, ...loginEn, ...metaEn, ...categoriesEn } }, sv: { translation: { ...landingSe, ...faqSe, ...pricingSe, ...loginSe, ...metaSe, ...categoriesSe } } } });
我试过一些方法但没成功,求帮忙!
实现方案
1. 改造路由结构,添加语言前缀
先把所有路由嵌套在带语言参数的父路由下,同时处理根路径的重定向:
修改App.jsx中的<Routes>部分(记得导入Navigate组件):
import { Routes, Route, Navigate } from 'react-router-dom'; // ...其他代码 <Routes> {/* 根路径重定向到默认语言en */} <Route path="/" element={<Navigate to="/en" replace />} /> {/* 带语言前缀的父路由 */} <Route path="/:lang"> <Route index element={<Home userPlan={userPlan} stripeID={stripeID} />} /> <Route path='terms' element={<Terms />} /> <Route path='create' element={<Dashboard userPlan={userPlan} userProfile={userProfile} />} /> <Route path='create/add' element={<Create userPlan={userPlan} />} /> <Route path='create/tag/:category' element={<Dubs />} /> <Route path='Signup' element={<Signup />} /> <Route path='login' element={<Login />} /> <Route path='reset-password' element={<ResetPass />} /> <Route path='reset-password/reset' element={<NewPassword />} /> <Route path='pricing' element={<Pricing stripeID={stripeID} userPlan={userPlan} />} /> <Route path='credits' element={<Credits credits={credits} setCredits={setCredits} stripeID={stripeID} />} /> <Route path='create/editor/:voiceName' element={<Editor user={user} credits={credits} setCredits={setCredits} userPlan={userPlan} />} /> <Route path='success/confirm' element={<ConfirmEmail />} /> <Route path='payment/success' element={<PaymentSuccess />} /> <Route path='billing' element={<Billing user={user} userPlan={userPlan} stripeID={stripeID} />} /> <Route path='create/search' element={<SearchMobile />} /> <Route path='login-popup' element={<LoginPopup />} /> <Route path='voices' element={<MyVoices user={user} userPlan={userPlan} />} /> <Route path='profile/settings' element={<SettingsPage />} /> </Route> {/* 404页面 */} <Route path='*' element={<ErrorPage />} /> </Routes>
2. 调整i18next配置,从路由检测语言
先安装语言检测插件:
npm install i18next-browser-languagedetector
然后修改i18next配置,让它优先从路由路径检测语言:
import LanguageDetector from 'i18next-browser-languagedetector'; const options = { order: ['path', 'navigator'], // 优先从路径取语言,其次用浏览器默认 lookupPath: 'lng', checkWhitelist: true, whitelist: ['en', 'sv'] // 匹配你支持的语言列表 } i18next .use(initReactI18next) .use(LanguageDetector) // 添加语言检测插件 .init({ detection: options, fallbackLng: "en", debug: true, interpolation: { escapeValue: false, }, resources: { en: { translation: { ...landingEn, ...faqEn, ...pricingEn, ...loginEn, ...metaEn, ...categoriesEn } }, sv: { translation: { ...landingSe, ...faqSe, ...pricingSe, ...loginSe, ...metaSe, ...categoriesSe } } } });
3. 修改Footer的语言切换逻辑,同步路由
更新Footer.jsx,切换语言时自动替换路由前缀:
import { useNavigate, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; const Footer = () => { const navigate = useNavigate(); const location = useLocation(); const { i18n } = useTranslation(); // 直接从i18n实例取当前语言,避免本地state不一致 const language = i18n.language; const languageSum = { en: "English", sv: "Swedish", de: "German", es: "Spanish" } const getLanguage = (lang) => { return languageSum[lang] || lang; } const openInNewTab = (url) => { window.open(url, "_blank", "noreferrer"); }; const handleLanguageClick = (newLang) => { // 提取当前路径,替换开头的语言前缀 const currentPath = location.pathname; const newPath = currentPath.replace(/^\/[a-z]{2}/, `/${newLang}`); // 导航到新路径 navigate(newPath); // 切换i18n语言 i18n.changeLanguage(newLang); }; return ( <div className="pt-[160px]"> <div className="pt-[160px]"> <Separator /> </div> <div id="footer-container" className="pt-[35px] flex flex-row space-x-2 justify-center"> <div className="flex flex-col space-y-1"> <div className="flex-row flex space-x-1"> <div id="footer-text" className="flex items-center"> <img id="footer-logo" src={Logo} className="w-[25px] h-[25px]" alt="logo"></img> <h2 id="footer-name" className="text-xl font-semibold">VoiceOvery</h2> <DropdownMenu> <DropdownMenuTrigger> <Button> <Globe className="mr-1" /> {getLanguage(language)} </Button> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Languages</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem onClick={() => handleLanguageClick("en")}>English</DropdownMenuItem> <DropdownMenuItem onClick={() => handleLanguageClick("de")}>German</DropdownMenuItem> <DropdownMenuItem onClick={() => handleLanguageClick("sv")}>Swedish</DropdownMenuItem> <DropdownMenuItem onClick={() => handleLanguageClick("es")}>Spanish</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div>
4. 可选:添加语言白名单守卫
如果用户输入了不支持的语言前缀(比如/fr),可以添加守卫组件重定向到默认语言:
创建LanguageGuard.jsx:
import { useParams, Navigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; const LanguageGuard = ({ children }) => { const { lang } = useParams(); const { i18n } = useTranslation(); const supportedLangs = ['en', 'sv']; if (!supportedLangs.includes(lang)) { // 跳转到默认语言的当前路径 const cleanPath = window.location.pathname.replace(/^\/[a-z]{2}/, ''); return <Navigate to={`/en${cleanPath}`} replace />; } // 同步路由语言和i18n语言 if (i18n.language !== lang) { i18n.changeLanguage(lang); } return children; }; export default LanguageGuard;
然后在App.jsx的路由中嵌套使用:
<Route path="/:lang"> <Route element={<LanguageGuard />}> {/* 所有子路由放在这里 */} <Route index element={<Home userPlan={userPlan} stripeID={stripeID} />} /> {/* ...其他子路由 */} </Route> </Route>
内容的提问来源于stack exchange,提问作者anon
相关产品推荐
相关产品推荐

