У меня есть приложение React, которому необходимо иметь локализованные URL-адреса. Мне удалось перевести URL-адреса и реализовать логику определения нового URL-адреса после смены языка, но я столкнулся с проблемой, с которой просто не могу справиться: после смены языка отображается только последняя часть. Переводится часть URL-адреса, а не та его часть, которая является языком /en/...
.
Вот демо-версия моего кода и ссылка на прототип: https://stackblitz.com/edit/vitejs-vite-mxzpnq
Обновлено: просто хочу отметить, что фактическое воспроизведение проблемы таково: когда приложение впервые загружается и вы наводите курсор на ссылку, оно отображает ссылку правильно, например: «../en/route-1-en». Измените язык на fr и наведите курсор на ссылку, это будет «.../en/route-1-fr». Я понятия не имею, почему здесь остается "en". Я думаю, это связано с тем, как NavLink разрешает поддержку «to».
function App() {
const { i18n, t } = useTranslation();
// For the case where the user navigates to the root path without language part
const rootRoute = {
path: '/',
loader: () => redirect(`/${i18n.language}`),
};
const unexpectedRoute = {
path: '*',
element: (
<div>
Not found, <NavLink to = "/">Go back to home</NavLink>
</div>
),
};
const routes = ['en', 'es', 'fr'].map((lng) => ({
lang: lng,
routes: [
{
id: lng,
path: '/:lng',
element: <Root />,
children: [
{
index: true,
element: <Home />,
},
{
path: t(`routes.route-1`, { lng }),
element: <TestPage />,
},
{
path: t(`routes.route-2`, { lng }),
element: <TestPage />,
},
{
path: t(`routes.route-3`, { lng }),
element: <TestPage />,
},
],
},
],
}));
const langRoutes: RouteObject[] = [...routes.flatMap((r) => r.routes)];
const router = createBrowserRouter([
...langRoutes,
rootRoute,
unexpectedRoute,
]);
return (
<>
<select
onChange = {async (e) => {
console.info(e.target.value);
await i18n.changeLanguage(e.target.value);
// Navigate to new path
}}
value = {i18n.language}
>
<option value = "en">En</option>
<option value = "es">Es</option>
<option value = "fr">Fr</option>
</select>
<div>
<RouterProvider router = {router} />
</div>
</>
);
}
export const Home = () => {
const { t, i18n } = useTranslation();
return (
<>
<h1>Home sweet home</h1>
<h2>Current language is: {i18n.language}</h2>
<ul>
<li>
<NavLink to = {t('routes.route-1')}>Route 1</NavLink>
</li>
<li>
<NavLink to = {t('routes.route-2')}>Route 2</NavLink>
</li>
<li>
<NavLink to = {t('routes.route-3')}>Route 3</NavLink>
</li>
</ul>
</>
);
};
🤔 А знаете ли вы, что...
React предлагает виртуальное DOM для оптимизации производительности при обновлении интерфейса.
Я понятия не имею, почему здесь остается "en".
Это связано с тем, что ваши ссылки используют относительные пути, а родительский путь — "en"
, пока вы вручную не обновите его в соответствии с языком. Другими словами, текущий путь "/en"
отображает компонент Home
, который отображает ссылки с использованием относительных путей, которые по сути являются чем-то вроде "./route-2-es"
, поэтому весь вычисленный целевой путь представляет собой текущий маршрут + путь => "/en/route-2-es"
.
Тем временем вы можете вычислить более полный абсолютный (начинающийся с "/"
) целевой путь, используя служебную функцию generatePath и текущий выбранный язык.
Пример:
Home.tsx
import { useTranslation } from 'react-i18next';
import { NavLink, generatePath } from 'react-router-dom';
export const Home = () => {
const { t, i18n } = useTranslation();
return (
<>
<h1>Home sweet home</h1>
<h2>Current language is: {i18n.language}</h2>
<ul>
<li>
<NavLink
to = {generatePath(`/:lang/${t('routes.route-1')}`, {
lang: i18n.language,
})}
>
Route 1
</NavLink>
</li>
<li>
<NavLink
to = {generatePath(`/:lang/${t('routes.route-2')}`, {
lang: i18n.language,
})}
>
Route 2
</NavLink>
</li>
<li>
<NavLink
to = {generatePath(`/:lang/${t('routes.route-3')}`, {
lang: i18n.language,
})}
>
Route 3
</NavLink>
</li>
</ul>
</>
);
};
Чтобы обработать обновление URL-адреса до правильного пути, вы можете применить некоторую логику в Root
, чтобы проверить соответствие текущего пути и языка и перенаправить на тот же маршрут, но с заменой сегмента нового языкового пути.
Пример:
Корень.tsx
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Outlet, useNavigate, useMatch, generatePath } from 'react-router-dom';
export const Root = () => {
const { i18n } = useTranslation();
const navigate = useNavigate();
const match = useMatch("/:lang/*");
const lang = i18n.language;
useEffect(() => {
if (match) {
navigate(generatePath("/:lang/*", {
"*": match.params["*"] ?? "",
lang
}), { replace: true })
}
}, [match, lang]);
return <Outlet />;
};
Благодаря этому изменению вам не понадобится предложение, приведенное выше в Home
, поскольку относительные пути по-прежнему будут работать, как и раньше, из обновленного пути родительского маршрута.