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;
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;