如何在 TanStack Start v1 中构建语言切换组件

在同一页面切换语言

问题

在多语言应用中,用户切换语言时通常希望能够停留在当前页面,并在新语言下继续查看相同内容。如果语言切换器实现不佳,将语言选择视为跳转到另一个页面,往往会将用户重定向到首页,导致用户丢失当前位置。这会打断用户流程,尤其是在用户正处于某个操作流程或浏览特定内容时,容易引发挫败感。挑战在于构建一个只更改 URL 中语言片段,同时保留其余路径、查询参数和哈希值的切换器。

解决方案

构建一个语言切换组件,读取当前 URL 路径名,并通过替换路径中的语言片段为每种可用语言生成链接。先从 URL 结构中提取当前语言,然后为其他支持的语言生成新路径,仅替换语言部分,保留其他所有 URL 片段不变。使用这些路径渲染链接,让用户在切换语言时不会丢失当前页面上下文。

步骤

1. 定义支持的语言环境

创建一个配置文件,列出应用支持的所有语言,并指定默认语言环境。

export const locales = ["en", "fr", "es", "de"] as const;

export type Locale = (typeof locales)[number];

export const defaultLocale: Locale = "en";

该配置作为可用语言的唯一数据源,并用于生成切换器链接。

2. 创建辅助函数从路径名中提取当前语言环境

编写一个工具函数,解析路径名并提取其中的语言片段(如果存在)。

import { defaultLocale, locales, type Locale } from "./locales";

export function getLocaleFromPathname(pathname: string): Locale {
  const segments = pathname.split("/").filter(Boolean);
  const firstSegment = segments[0];

  if (firstSegment && locales.includes(firstSegment as Locale)) {
    return firstSegment as Locale;
  }

  return defaultLocale;
}

该函数会检查第一个路径段,如果与支持的语言环境匹配则返回,否则回退到默认语言环境。

3. 创建用于构建本地化路径的辅助函数

编写一个函数,接收当前路径名和目标语言环境,然后构建一个用目标语言环境替换路径段的新路径。

import { defaultLocale, locales, type Locale } from "./locales";

export function getLocalizedPath(
  pathname: string,
  targetLocale: Locale,
): string {
  const segments = pathname.split("/").filter(Boolean);
  const firstSegment = segments[0];

  const hasLocalePrefix =
    firstSegment && locales.includes(firstSegment as Locale);

  if (hasLocalePrefix) {
    segments[0] = targetLocale;
  } else {
    segments.unshift(targetLocale);
  }

  return "/" + segments.join("/");
}

该函数会替换已有的语言环境路径段,或在路径前添加目标语言环境,确保新 URL 指向同一页面的不同语言版本。

4. 构建语言切换组件

创建一个组件,利用当前位置为所有支持的语言生成链接。

import { Link } from "@tanstack/react-router";
import { useLocation } from "@tanstack/react-router";
import { locales, type Locale } from "./locales";
import { getLocaleFromPathname, getLocalizedPath } from "./locale-helpers";

export function LanguageSwitcher() {
  const location = useLocation();
  const currentLocale = getLocaleFromPathname(location.pathname);

  return (
    <nav>
      <ul>
        {locales.map((locale) => {
          const isActive = locale === currentLocale;
          const localizedPath = getLocalizedPath(location.pathname, locale);

          return (
            <li key={locale}>
              <Link
                to={localizedPath}
                search={location.search}
                hash={location.hash}
                aria-current={isActive ? "page" : undefined}
              >
                {locale.toUpperCase()}
              </Link>
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

该组件会读取当前路径名,确定当前激活的语言环境,并为每个支持的语言渲染一个链接,保留当前页面结构、查询参数和哈希片段。用户可以在保持同一逻辑页面的情况下切换语言。