You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 01:53:11