如何在 React Router v7 中维护导航链接的语言环境

在内部导航中维护语言环境

问题

当区域信息编码在 URL 路径中时,每个导航链接都必须保留该区域信息,以保持一致的用户体验。如果用户浏览您网站的法语版本并点击链接 /about,他们期望仍然是法语,并导航到 /fr/about。如果没有支持区域信息的链接,用户会在会话中途被切换到默认语言,破坏了他们的浏览上下文,并迫使他们再次手动切换语言。这会增加摩擦并削弱本地化体验。

将区域前缀硬编码到每个链接中容易出错,并使代码库变得脆弱。随着用户在应用程序中导航,活动区域可能会更改,手动更新数百个链接变得不可持续。

解决方案

创建一个自定义的 Link 组件,该组件会自动从 URL 中读取当前区域信息,并将其添加到所有内部导航路径的前面。通过封装 React Router 的 Link 组件,您可以将区域信息处理集中在一个地方。封装组件从当前路由中提取区域参数,并确保每个目标路径都包含它,从而在不需要手动干预的情况下,保持用户的语言选择。

这种方法使得整个应用程序中的链接定义保持简洁且与区域无关,同时保证区域上下文随每次点击而传递。

步骤

构建一个自定义组件,使用 useParams 从 URL 中提取当前区域信息,并封装 React Router 的 Link 组件,将区域信息添加到目标路径的前面。

import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";

export function LocaleLink({ to, ...props }: LinkProps) {
  const { locale } = useParams<{ locale: string }>();

  const localizedTo =
    typeof to === "string"
      ? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
      : {
          ...to,
          pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
        };

  return <Link to={localizedTo} {...props} />;
}

该组件从当前路由中读取区域参数,并自动将其前缀添加到传递给 to 属性的任何路径中,支持字符串和对象形式。

将标准的 Link 组件替换为 LocaleLink,以便在需要保留语言环境的导航时使用。

import { LocaleLink } from "./LocaleLink";

export function Navigation() {
  return (
    <nav>
      <LocaleLink to="/">首页</LocaleLink>
      <LocaleLink to="/about">关于</LocaleLink>
      <LocaleLink to="/products">产品</LocaleLink>
    </nav>
  );
}

当用户在 /fr/products 页面点击 About 链接时,他们会导航到 /fr/about。语言环境前缀会自动添加,而不会使链接定义变得复杂。

3. 处理绝对路径和外部链接的边界情况

扩展包装器以检测路径是否已包含语言环境或指向外部 URL,从而避免双重前缀或破坏外部导航。

import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";

export function LocaleLink({ to, ...props }: LinkProps) {
  const { locale } = useParams<{ locale: string }>();

  if (!locale) {
    return <Link to={to} {...props} />;
  }

  const isExternal =
    typeof to === "string" &&
    (to.startsWith("http://") || to.startsWith("https://"));
  const alreadyLocalized =
    typeof to === "string" && to.startsWith(`/${locale}/`);

  if (isExternal || alreadyLocalized) {
    return <Link to={to} {...props} />;
  }

  const localizedTo =
    typeof to === "string"
      ? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
      : {
          ...to,
          pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
        };

  return <Link to={localizedTo} {...props} />;
}

这可以防止路径已以语言环境开头时的双重前缀,并保持外部 URL 不变,确保组件在所有导航场景中可靠运行。