Маршрутизатор React с локализованными URL-адресами i18next не обновляет весь URL-адрес при смене языка

У меня есть приложение 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 для оптимизации производительности при обновлении интерфейса.


1
55
1

Ответ:

Решено

Я понятия не имею, почему здесь остается "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, поскольку относительные пути по-прежнему будут работать, как и раньше, из обновленного пути родительского маршрута.