如何在 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 };
  });

此服务器函数使用 @tanstack/react-start/server 中的 setCookie 来存储语言环境偏好,使其在未来的请求中可用。

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/server 中的 getCookie 函数读取了在之前访问中设置的 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 篡改或过期值的影响。