I have a question about React Router V6 nested with i18n.
This is my first multi-language service.
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path={`/`} element={<Home />}>
<Route path={`${lang}`}>
<Route path={`service`}>
<Route path={'slack'} element={<Slack />} />
</Route>
</Route>
{/* <Route path={`service/dooray`}element={<Dooray />} /> */}
{/* <Route path={`contact`} element={<Contact />} /> */}
{/* <Route path={`app/sign-in`} element={<SignIn />} /> */}
{/* <Route path={`app/sign-up`} element={<SignUp />} /> */}
{/* <Route path={`app/mail-code`} element={<MailCode />} /> */}
{/* <Route path={`app/password/reset`} element={<PwdReset />} /> */}
{/* <Route path={`policies/privac`} element={<Privacy />} /> */}
{/* <Route path={`policies/terms`} element={<Terms />} /> */}
</Route>
{/* <Route path={`*`} element={<>NOT FOUND</>} /> */}
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
If I enter localhost:3000/en, there is an error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.'
How can I fix it..
I want /en => go to english page, /jp => go to japanese page
const MainPage:React.FC =() => {
...
<Route path={`/`} element={<Home />}>
<Route path={`/${lang}/*`}>
<Route path={`service`}>
<Route path="slack" element={<Slack />} />
</Route>
</Route>
</Route>
}
const Home:React.FC = () => {
return (
<>
... UI, JSX
<Outlet />
</>
)
}
I add a <Outlet />
. But if I entered '/ko/service/slack', render <Home />
now
<Route path={`/`} element={<Home />}>
<Route path="service">
<Route path="slack" element={<Slack />} />
<Route path="dooray" element={<Dooray />} />
</Route>
</Route>
nested-routes doesn't work.. :(
I have a question about React Router V6 nested with i18n.
This is my first multi-language service.
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path={`/`} element={<Home />}>
<Route path={`${lang}`}>
<Route path={`service`}>
<Route path={'slack'} element={<Slack />} />
</Route>
</Route>
{/* <Route path={`service/dooray`}element={<Dooray />} /> */}
{/* <Route path={`contact`} element={<Contact />} /> */}
{/* <Route path={`app/sign-in`} element={<SignIn />} /> */}
{/* <Route path={`app/sign-up`} element={<SignUp />} /> */}
{/* <Route path={`app/mail-code`} element={<MailCode />} /> */}
{/* <Route path={`app/password/reset`} element={<PwdReset />} /> */}
{/* <Route path={`policies/privac`} element={<Privacy />} /> */}
{/* <Route path={`policies/terms`} element={<Terms />} /> */}
</Route>
{/* <Route path={`*`} element={<>NOT FOUND</>} /> */}
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
If I enter localhost:3000/en, there is an error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.'
How can I fix it..
I want /en => go to english page, /jp => go to japanese page
const MainPage:React.FC =() => {
...
<Route path={`/`} element={<Home />}>
<Route path={`/${lang}/*`}>
<Route path={`service`}>
<Route path="slack" element={<Slack />} />
</Route>
</Route>
</Route>
}
const Home:React.FC = () => {
return (
<>
... UI, JSX
<Outlet />
</>
)
}
I add a <Outlet />
. But if I entered '/ko/service/slack', render <Home />
now
<Route path={`/`} element={<Home />}>
<Route path="service">
<Route path="slack" element={<Slack />} />
<Route path="dooray" element={<Dooray />} />
</Route>
</Route>
nested-routes doesn't work.. :(
Share Improve this question edited Apr 7, 2022 at 3:03 Jerry asked Apr 6, 2022 at 15:23 JerryJerry 411 silver badge6 bronze badges 1-
now, I fix my code ``` <Route path={
/
} element={<Home />} /> <Route path={/${lang}
} element={<Home><Outlet /></Home>}> <Route path={service
} element={<Outlet />}> <Route path={'slack'} element={<Slack />} /> </Route> </Route>``` – Jerry Commented Apr 6, 2022 at 15:31
3 Answers
Reset to default 4You can create a Route with an Outlet that detects the language and assigns it to i18next directly:
<Routes>
<Route path="/" element={<Home />}>
<Route path=":lang" element={<LanguagePath />}>
<Route path="/" element={<Home />} />
<Route path="service/dooray" element={<Dooray />} />
<Route path="contact" element={<Contact />} />
<Route path="app/sign-in" element={<SignIn />
<Route path="app/sign-up" element={<SignUp />
<Route path="*" element={<>NOT FOUND</>} />
</Route>
</Route>
</Routes>
Then inside LanguagePath.js
you can detect the lang and either set it in i18n.changeLanguage
or redirect the user if it is missing.
export default function LanguagePath() {
const { i18n } = useTranslation();
const { lang } = useParams();
const navigate = useNavigate();
const curPath = location.pathname;
useEffect(() => {
if (lang && i18n.resolvedLanguage !== lang) {
if (i18n.options.fallbackLng.includes(lang)) {
i18n.changeLanguage(lang);
} else {
navigate("/" + i18n.resolvedLanguage + curPath, {replace: true});
}
}
}, [lang]);
return <Outlet />;
}
Now if a user arrives at:
/ => /
/contact => /en/contact
/en/contact => /en/contact
/fr/contact => /fr/contact
I had the exact same useCase (localize react router v6) and came up with the following LangRouter repository link:
const LangRouter = () => {
const { i18n } = useTranslation(),
{ pathname, search, hash } = useLocation(),
navigate = useNavigate(),
availableLocales = ['en', 'ar'],
defaultLocale = (
getDefaultLanguage() === 'en' || getDefaultLanguage() === 'ar' ? getDefaultLanguage() : 'en'
) as string,
pathnameLocale = pathname.substring(1, 3).toLowerCase(),
[locale, setLocale] = useState(defaultLocale),
loaderTimerRef = useRef<any>(),
[isLoading, setIsLoading] = useState(true);
useEffect(() => {
loaderTimerRef.current = setTimeout(() => {
setIsLoading(false);
clearTimeout(loaderTimerRef.current);
}, 300);
}, []);
useEffect(() => {
if (availableLocales.includes(pathnameLocale)) {
updateLocale(pathnameLocale);
} else if (pathname === '/') {
updateLocale(defaultLocale);
}
// eslint-disable-next-line
}, [pathname]);
useEffect(() => {
let lang = defaultLocale;
if (availableLocales.includes(pathnameLocale)) {
lang = pathnameLocale;
setLanguageHandler(lang);
} else if (pathname === '/') {
setLanguageHandler(lang);
}
// eslint-disable-next-line
}, [locale]);
const setLanguageHandler = (lang: string) => {
if (lang === 'en') {
i18n.changeLanguage('en-US');
} else {
i18n.changeLanguage('ar-SA');
}
};
const updateLocale = (newLocale: string) => {
const newPath = `/${newLocale}` + pathname.substring(3);
if (locale !== newLocale) {
if (newPath === `/${newLocale}/` || newPath === `/${newLocale}` || pathname === '/') {
navigate(getHomePageUrl(newLocale));
} else {
navigate(`${newPath}${hash}${search}`);
}
setLocale(newLocale);
} else if (newPath === `/${newLocale}/` || newPath === `/${newLocale}` || pathname === '/') {
if (isAuthenticated()) {
navigate(getHomePageUrl(newLocale));
} else {
navigate(getLoginPageUrl(newLocale));
}
}
};
if (isLoading) {
return (
<div className="loader-wrapper">
<LoadingIcon />
</div>
);
}
return (
<LocaleContext.Provider value={{ locale, setLocale: updateLocale }}>
<Routes>
<Route path={`/${locale}`} element={<App />}>
{publicRoutes.map((el, i) => (
<Route
key={i}
path={el.path(locale)}
element={
<PublicRouteGuard
restricted={el.restricted}
redirect={el.redirect ? el.redirect(locale) : undefined}
>
{el.element}
</PublicRouteGuard>
}
/>
))}
{privateRoutes.map((el, i) => (
<Route
key={i}
path={el.path(locale)}
element={
el.permissions ? (
<RestrictedRouteGuard requiredPermissions={el.permissions}>
{el.element}
</RestrictedRouteGuard>
) : (
<PrivateRouteGuard>{el.element}</PrivateRouteGuard>
)
}
>
{el.children &&
el.children.map((innerEl, innerI) => (
<Route key={innerI} path={innerEl.path(locale)} element={innerEl.element} />
))}
</Route>
))}
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</LocaleContext.Provider>
);
};
export default LangRouter;
Issue
The error 'This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.'
means the parent route isn't rendering an Outlet
ponent for the nested routes to be rendered into. The route rendering the Home
ponent doesn't appear to be rendering an Outlet
.
Solution
Update the Home
ponent to render an Outlet
. Note that Route
ponents without an element
prop will render an Outlet
by default.
Example:
import { Outlet } from 'react-router-dom';
const Home = () => {
...
return (
<>
... home page UI/JSX ...
<Outlet />
</>
);
};
...
const MainPage:React.FC = () => {
const lang = i18n.language;
return (
<>
<Wrapper>
<Routes>
{/* Main */}
<Route path="/" element={<Home />}>
<Route path={lang}> // <-- renders Outlet by default
<Route path="service"> // <-- renders Outlet by default
<Route path="slack" element={<Slack />} />
</Route>
</Route>
...
</Route>
...
{/* test */}
</Routes>
</Wrapper>
<ParentModal />
</>
);
};
Update
If the Home
and Slack
ponents are separate and independent, then move the Home
ponent into an index route and simplify the routing to the Slack
ponent.
<Routes>
<Route path="/">
<Route index element={<Home />} />
<Route path={`${lang}/service/slack`} element={<Slack />} />
</Route>
</Routes>