如何在 React Router v7 中构建语言切换组件
在同一页面切换语言
问题
用户期望语言切换器能够保留当前的浏览上下文。当浏览产品页面、帮助文档或账户设置时,从英文切换到西班牙文应显示同一页面的西班牙语版本。然而,许多实现方式将语言选择视为一次导航事件,直接将用户重定向到新语言的首页,用户不得不重新返回原来的页面。这会打断用户的操作流程,尤其是在内容较多、导航层级较深的应用中,用户体验会受到很大影响。
根本原因在于,语言切换器通常使用硬编码的目标 URL,而不是基于当前页面动态生成 URL。如果不读取并转换当前的 URL 结构,切换器就无法在切换语言时保持用户在应用中的位置。
解决方案
构建一个语言切换组件,能够读取当前 URL,提取当前激活的 locale 参数和剩余的路径片段。对于每种支持的语言,仅替换 locale 部分,保留其他路径和查询参数,动态生成新的 URL。将这些 URL 渲染为链接,用户即可在不丢失当前位置的情况下切换语言。
这种方法将 locale 作为 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}`;
}
此更新版本会接收 location 对象中的 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="Language switcher">
<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.search 和 location.hash 传递给辅助函数,确保在切换到 Spanish 时,像 /en/products?category=shoes#reviews 这样的 URL 会变为 /es/products?category=shoes#reviews。