如何在 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. 创建根路径的服务端路由处理器
在 index 路由中添加服务端 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 函数和语言常量放在共享模块中,以便在应用中复用。