如何在 TanStack Start v1 中跨会话记住语言选择

存储用户明确选择的语言

问题

当用户明确选择了一种语言时,这代表了他们的主动偏好,这一选择应当在当前浏览器会话之外持续生效。如果没有持久化,应用会在每次访问时遗忘该偏好,迫使用户反复重新选择语言。这会增加操作阻力,给用户传递出应用不尊重其选择的信号,降低整体体验,甚至可能导致用户在完成目标操作前就离开网站。

解决方案

当用户明确选择语言时,将其选择存储在持久化 cookie 中。后续访问时,优先检查该存储的偏好,再考虑如浏览器头部等自动检测方式。如果检测到有效的已存储语言,则应将用户从站点根路径重定向到该语言路径,确保其直接进入首选语言环境,无需额外操作。

步骤

1. 创建服务端函数用于存储语言偏好

服务端函数允许你定义仅在服务端运行的逻辑,并可在应用任意位置调用。请定义一个函数,将所选语言写入 cookie。

import { createServerFn } from "@tanstack/react-start";
import { setCookie } from "@tanstack/react-start/server";

const LOCALE_COOKIE = "user_locale";
const COOKIE_MAX_AGE = 60 * 60 * 24 * 365;

export const saveLocalePreference = createServerFn({ method: "POST" })
  .validator((locale: string) => locale)
  .handler(async ({ data }) => {
    setCookie(LOCALE_COOKIE, data, {
      maxAge: COOKIE_MAX_AGE,
      path: "/",
      sameSite: "lax",
    });
    return { success: true };
  });

该服务端函数使用 setCookie(来自 @tanstack/react-start/server)来存储语言偏好,使其在后续请求中可用。

2. 用户选择语言后调用保存函数

在你的语言切换组件中,用户做出选择后调用该服务端函数。

import { useNavigate } from "@tanstack/react-router";
import { saveLocalePreference } from "./locale-preference";

export function LanguageSwitcher({ currentLocale }: { currentLocale: string }) {
  const navigate = useNavigate();

  const handleLocaleChange = async (newLocale: string) => {
    await saveLocalePreference({ data: newLocale });
    navigate({ to: `/${newLocale}` });
  };

  return (
    <select
      value={currentLocale}
      onChange={(e) => handleLocaleChange(e.target.value)}
    >
      <option value="en">English</option>
      <option value="es">Español</option>
      <option value="fr">Français</option>
    </select>
  );
}

这样可以确保在跳转到新语言环境前,用户偏好已被保存。

3. 创建用于读取已存储偏好的服务器函数

定义一个函数,从 cookie 中获取已存储的语言区域。

import { createServerFn } from "@tanstack/react-start";
import { getCookie } from "@tanstack/react-start/server";

const LOCALE_COOKIE = "user_locale";

export const getStoredLocale = createServerFn({ method: "GET" }).handler(
  async () => {
    const stored = getCookie(LOCALE_COOKIE);
    return stored || null;
  },
);

来自 @tanstack/react-start/servergetCookie 函数会读取之前访问时设置的 cookie 值。

4. 在根路由检查已存储的偏好

在路由的 beforeLoad 回调中使用 redirect 函数,在检测到已存储偏好时触发重定向。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getStoredLocale } from "./locale-preference";

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

export const Route = createFileRoute("/")({
  beforeLoad: async () => {
    const stored = await getStoredLocale();

    if (stored && SUPPORTED_LOCALES.includes(stored)) {
      throw redirect({ to: `/${stored}` });
    }

    throw redirect({ to: `/${DEFAULT_LOCALE}` });
  },
});

这会优先检查已存储的偏好,如果有效则重定向到对应语言区域,否则回退到默认设置。

5. 校验已存储的语言区域是否受支持

添加校验,确保已存储的值是受支持的语言区域后再进行重定向。

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getStoredLocale } from "./locale-preference";

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

function isValidLocale(value: string | null): value is string {
  return value !== null && SUPPORTED_LOCALES.includes(value as any);
}

export const Route = createFileRoute("/")({
  beforeLoad: async () => {
    const stored = await getStoredLocale();
    const locale = isValidLocale(stored) ? stored : "en";
    throw redirect({ to: `/${locale}` });
  },
});

这样可以防止因 cookie 被篡改或值过期而重定向到无效或不受支持的语言区域。