如何在 React Router v7 中构建语言切换器组件

在同一页面上切换语言

问题

用户期望语言切换器能够保留他们当前的上下文。当浏览产品页面、帮助文章或账户设置时,从英语切换到西班牙语应该显示相同页面的西班牙语版本。然而,许多实现将语言选择视为导航事件,将用户重定向到新语言的主页,迫使他们重新导航回原来的位置。这破坏了用户的流程,尤其是在内容繁多的应用程序中,用户可能已经深入导航层级。

问题的根本原因在于语言切换器通常使用硬编码的目标 URL,而不是基于当前页面动态构建 URL。如果不读取和转换当前的 URL 结构,切换器无法在语言更改时保持用户在应用程序中的位置。

解决方案

构建一个语言切换器组件,该组件能够读取当前 URL 并提取活动的语言参数和剩余的路径段。对于每种支持的语言,生成一个新 URL,仅替换语言段,同时保留所有其他路径段和查询参数。将这些 URL 渲染为链接,以便用户在切换语言时不会丢失他们在应用程序中的位置。

这种方法将语言视为 URL 结构中的可替换参数,而不是导航目标,确保从 /en/products/shoes 切换到 /es/products/shoes 时能够保留用户的上下文。

步骤

1. 创建一个构建支持语言的 URL 的辅助函数

定义一个函数,该函数接收当前路径名和目标语言,然后通过替换语言段构建一个新路径。

export function buildLocalePath(
  currentPath: string,
  newLocale: string,
): string {
  const segments = currentPath.split("/").filter(Boolean);

  if (segments.length === 0) {
    return `/${newLocale}`;
  }

  segments[0] = newLocale;
  return `/${segments.join("/")}`;
}

此函数将路径名分割为段,使用目标语言替换第一个段,并重新构建路径。它处理了根路径等边界情况,并确保语言始终是第一个段。

2. 定义支持的语言区域

创建一个配置对象,列出您的应用程序支持的所有语言。

export const locales = [
  { code: "en", label: "English" },
  { code: "es", label: "Español" },
  { code: "fr", label: "Français" },
  { code: "de", label: "Deutsch" },
];

此配置是显示在切换器中的语言以及为每个语言区域提供用户友好标签的真实来源。

3. 构建语言切换组件

创建一个组件,该组件读取当前位置,确定活动语言区域,并为所有其他支持的语言渲染链接。

import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";

export function LanguageSwitcher() {
  const location = useLocation();
  const params = useParams();
  const currentLocale = params.locale || "en";

  return (
    <nav aria-label="Language switcher">
      <ul>
        {locales.map((locale) => {
          const isActive = locale.code === currentLocale;
          const newPath = buildLocalePath(location.pathname, locale.code);

          return (
            <li key={locale.code}>
              {isActive ? (
                <span aria-current="true">{locale.label}</span>
              ) : (
                <Link to={newPath}>{locale.label}</Link>
              )}
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

该组件使用 useLocation 访问当前路径名,并使用 useParams 从 URL 中提取活动语言区域。对于每个支持的语言区域,它使用辅助函数生成一个新路径,并为当前语言渲染一个链接或非交互元素。

4. 保留查询参数和哈希片段

扩展辅助函数以在切换语言时保留查询字符串和 URL 片段。

export function buildLocalePath(
  currentPath: string,
  search: string,
  hash: string,
  newLocale: string,
): string {
  const segments = currentPath.split("/").filter(Boolean);

  if (segments.length === 0) {
    return `/${newLocale}${search}${hash}`;
  }

  segments[0] = newLocale;
  return `/${segments.join("/")}${search}${hash}`;
}

此更新版本接受来自位置对象的搜索和哈希属性,并将它们附加到生成的路径中,确保过滤器、排序参数和锚点链接在语言切换时得以保留。

5. 更新组件以使用增强的辅助函数

修改切换器以将完整的位置信息传递给辅助函数。

import { Link, useLocation, useParams } from "react-router";
import { locales, buildLocalePath } from "./i18n-config";

export function LanguageSwitcher() {
  const location = useLocation();
  const params = useParams();
  const currentLocale = params.locale || "en";

  return (
    <nav aria-label="语言切换器">
      <ul>
        {locales.map((locale) => {
          const isActive = locale.code === currentLocale;
          const newPath = buildLocalePath(
            location.pathname,
            location.search,
            location.hash,
            locale.code,
          );

          return (
            <li key={locale.code}>
              {isActive ? (
                <span aria-current="true">{locale.label}</span>
              ) : (
                <Link to={newPath}>{locale.label}</Link>
              )}
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

该组件现在将 location.searchlocation.hash 传递给辅助函数,确保像 /en/products?category=shoes#reviews 这样的 URL 在切换到西班牙语时变为 /es/products?category=shoes#reviews