Sonal
Sonal

Reputation: 188

i18next + react router : My URL isn't changing correctly when language is switched

I am trying to change the app URL when language is switched. When I add the selected language manually in the URL (like "http://localhost:3001/es/forgot-password"), the components load correctly with selected language but when I switch the language using changeLanguage event, translations work correctly but the language doesn't change in the URL and components don't load properly.

I have been stuck on this since long, also have researched a lot, and tried different things; nothing has helped me to figure the issue out properly.

Here's my code:

i18n.tsx

import HttpApi from 'i18next-http-backend';
import XHR from "i18next-http-backend"
import { initReactI18next } from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector';
import i18n from 'i18next';

i18n.use(XHR)
  .use(LanguageDetector)
  .use(HttpApi)
  .use(initReactI18next)
  .init({
    supportedLngs: ['en', 'es', 'pt', 'fr', 'gr', 'pl'],
    fallbackLng: 'en',
    detection: {
        order: ['querystring','path','cookie','htmlTag','localStorage','sessionStorage','subdomain'],
        caches: ['cookie', 'localStorage'],
        lookupQuerystring: 'lng',
        lookupCookie: 'i18next',
        lookupLocalStorage: 'i18nextLng',
        lookupSessionStorage: 'i18nextLng'
    },
    backend: {
        loadPath: "/locales/{{lng}}/translation.json"
    },
    interpolation: {escapeValue: false},
    debug: true,
    react: {useSuspense: false}
  });

export default i18n;

LanguageSwitch.tsx

import './LanguageSwitch.scss';
import { useState } from 'react';
import { useTranslation } from "react-i18next";

type LanguateSwitchProps = {
    passLanguageChange: (str: string) => void
};

const LanguageSwitch = (props: LanguateSwitchProps) => {
    const { t, i18n } = useTranslation(['home']);

    const [lang, setLang] = useState('en');

    const onClickLanguageChange = (e: any) => {
        const language = e.target.value;
        i18n.changeLanguage(language); //change the language
        props.passLanguageChange(language); //<---Sending the change language event to App with chosen language
    }

    return (
        <div className="language-switch">
            <select className="custom-select" onChange={onClickLanguageChange}>
                <option value="en">English</option>
                <option value="es">Español</option>
            </select>
            
        </div> 
  );
}

export default LanguageSwitch;

App.tsx

import './App.scss';
import { useState } from 'react';
import { Routes, Route} from 'react-router-dom';
import { AuthProvider } from './auth';
import ChangePassword from './components/ChangePassword/ChangePassword';
import ForgotPassword from './components/ForgotPassword/ForgotPassword';
import Login from './components/Login/Login';
import ResetPassword from './components/ResetPassword/ResetPassword';
import Dashboard from './components/Dashboard/Dashboard';
import PrivateRoutes from './components/PrivateRoutes';
import LanguageSwitch from './components/LanguageSwitch/LanguageSwitch';
import  i18n from './i18n';

const App = () => {
  const [language, setLanguage] = useState(i18n.language);

  const handleLanguageChange = (lang: string) => {
    setLanguage(lang); //<---Capturing the switch language event and language from switch component
  }

  return (
    <AuthProvider>
      <div className="App"> 
        <div className="container">
        <LanguageSwitch passLanguageChange={handleLanguageChange}></LanguageSwitch>
          <div className="row">
            {language}
            <Routes>
              <Route path={`/${language}`} element={<Login/>}></Route> //<---passing the selected language in the path
              <Route path={`${language}/forgot-password`} element={<ForgotPassword/>}></Route>
              <Route element={<PrivateRoutes/>}>
                <Route path={`${language}/dashboard`} element={<Dashboard/>}></Route>
                <Route path={`${language}/change-password`} element={<ChangePassword/>}></Route>
              </Route> 
              <Route path="reset-password" element={<ResetPassword/>}></Route>
            </Routes>
          </div>
        </div>
      </div>
    </AuthProvider>
  );
}

export default App;

Upvotes: 3

Views: 1985

Answers (1)

Drew Reese
Drew Reese

Reputation: 203333

Along with updating the language that is stored into state, the code should also issue a navigation change to a route using the new language in order to also update the URL in the address bar. Since it's a bit impractical to know every/all route(s) the app is possibly rendering I suggest using a bit of string manipulation and a set of known language abbreviations to update just the one language path segment.

Example:

const languages = {
  en: "English",
  es: "Español"
};
type LanguageSwitchProps = {
  language: string;
  passLanguageChange: (str: string) => void;
};

const LanguageSwitch = (props: LanguageSwitchProps) => {
  const { t, i18n } = useTranslation(['home']);

  const onClickLanguageChange = (e: any) => {
    const language = e.target.value;
    i18n.changeLanguage(language); // change the language
    props.passLanguageChange(language); // Send the change to App
  };

  return (
    <div className="language-switch">
      <select
        className="custom-select"
        value={props.language}
        onChange={onClickLanguageChange}
      >
        {Object.entries(languages).map(([value, label]) => (
          <option key={value} value={value}>
            {label}
          </option>
        ))}
      </select>
    </div>
  );
};
...
import {
  Routes,
  Route,
  Navigate,
  useLocation,
  useNavigate
} from 'react-router-dom';
...

const App = () => {
  const location = useLocation();
  const navigate = useNavigate();

  const [language, setLanguage] = useState(i18n.language);

  const handleLanguageChange = (lang: string) => {
    setLanguage(lang);
    const [language, ...path] = location.pathname.slice(1).split("/");

    if (language in languages) {
      navigate(
        {
          ...location,
          pathname: `/${[lang, ...path].join("/")}`
        },
        { replace: true }
      );
    }
  };

  return (
    <AuthProvider>
      <div className="App"> 
        <div className="container">
          <LanguageSwitch
            language={language}
            passLanguageChange={handleLanguageChange}
          />
          <div className="row">
            {language}

            <Routes>
              <Route path=":language">
                <Route index element={<Login />} />
                <Route path="forgot-password" element={<ForgotPassword />} />
                <Route element={<PrivateRoutes />}>
                  <Route path="dashboard" element={<Dashboard />} />
                  <Route path="change-password" element={<ChangePassword />} />
                </Route>
              </Route>
              <Route path="reset-password" element={<ResetPassword />} />
            </Routes>
          </div>
        </div>
      </div>
    </AuthProvider>
  );
}

export default App;

Upvotes: 3

Related Questions