如何在 TanStack Start v1 中检测用户的语言偏好

基于浏览器偏好自动重定向

问题

Web 浏览器在每次 HTTP 请求中都会发送一个 Accept-Language 头,指示用户按优先级排列的首选语言。这个头提供了关于用户最舒适使用的语言的重要信息,但许多应用程序完全忽略了它。相反,它们向所有访问者显示默认语言——通常是英语,而不考虑用户的实际偏好。用户必须手动寻找语言切换器,这在应用程序已经拥有所需信息以做出更好初始选择的情况下,增加了不必要的摩擦。

这种错失的个性化机会对那些明确配置了浏览器语言偏好的非英语用户来说尤其令人沮丧。结果是糟糕的第一印象以及在用户能够以其首选语言访问内容之前需要额外的步骤。

解决方案

拦截对根路径的请求,并检查 Accept-Language 头以确定用户的首选语言。解析该头以提取应用程序支持的最高优先级语言。如果找到匹配项,将用户重定向到相应的本地化路径。如果在头中未找到支持的语言,则重定向到默认语言回退。

这种方法自动尊重用户偏好,同时保持清晰的 URL 结构,其中每种语言都有自己的路径前缀。重定向在页面渲染之前的服务器端发生,确保用户直接进入其首选语言的内容,而不会看到错误语言的闪现。

步骤

1. 创建一个解析 Accept-Language 头的辅助工具

Accept-Language 头包含带有可选质量值的语言代码,这些值指示优先级顺序。构建一个解析器,提取这些语言,按优先级排序,并从支持的语言列表中找到第一个匹配项。

export function parseAcceptLanguage(
  header: string | null,
  supportedLocales: string[],
): string | null {
  if (!header) {
    return null;
  }

  const languages = header
    .split(",")
    .map((lang) => {
      const [code, qValue] = lang.trim().split(";q=");
      const quality = qValue ? parseFloat(qValue) : 1.0;
      return { code: code.toLowerCase(), quality };
    })
    .sort((a, b) => b.quality - a.quality);

  for (const { code } of languages) {
    const exactMatch = supportedLocales.find(
      (locale) => locale.toLowerCase() === code,
    );
    if (exactMatch) {
      return exactMatch;
    }

    const baseCode = code.split("-")[0];
    const baseMatch = supportedLocales.find((locale) =>
      locale.toLowerCase().startsWith(baseCode),
    );
    if (baseMatch) {
      return baseMatch;
    }
  }

  return null;
}

此函数通过逗号分割头,提取质量值,按优先级排序,并检查支持的语言列表中的精确匹配和基础语言匹配。

2. 定义支持的语言区域

创建一个配置,列出您的应用程序支持的所有语言,并指定默认回退语言。

export const SUPPORTED_LOCALES = ["en", "fr", "de", "es", "ja"];
export const DEFAULT_LOCALE = "en";

这些常量集中管理您的语言配置,使得在应用程序扩展时添加或移除支持的语言变得更加容易。

3. 为根路径创建服务器路由处理程序

在索引路由中添加一个服务器端的 GET 处理程序,用于读取 Accept-Language 头部,确定最佳语言区域,并将用户重定向到相应的语言路径。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getRequestHeaders } from "@tanstack/react-start/server";

export const Route = createFileRoute("/")({
  server: {
    handlers: {
      GET: async () => {
        const headers = getRequestHeaders();
        const acceptLanguage = headers.get("accept-language");

        const preferredLocale = parseAcceptLanguage(
          acceptLanguage,
          SUPPORTED_LOCALES,
        );

        const targetLocale = preferredLocale || DEFAULT_LOCALE;

        throw redirect({
          to: `/${targetLocale}`,
          statusCode: 302,
        });
      },
    },
  },
});

此处理程序在用户访问根路径时运行于服务器端,检查用户的语言偏好,并在任何客户端代码执行之前立即将其重定向到最佳匹配的语言区域路径。

4. 导入辅助函数

确保在路由文件顶部导入解析辅助函数以及语言区域配置。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getRequestHeaders } from "@tanstack/react-start/server";
import {
  parseAcceptLanguage,
  SUPPORTED_LOCALES,
  DEFAULT_LOCALE,
} from "../lib/locale";

parseAcceptLanguage 函数和语言区域常量放置在一个共享模块中,以便在整个应用程序中重用(如果需要)。