如何在 React Router v7 中跨会话记住语言选择
存储用户明确选择的语言
问题
当用户明确选择了一种语言时,这一选择代表了他们的偏好,应当优先于任何自动检测。如果没有持久化机制,这一选择会在浏览器关闭或会话结束时丢失。下次访问时,应用会重新开始,用户不得不再次选择语言。这种重复操作表明应用没有尊重用户的偏好,增加了使用阻力并降低了信任感。
解决方案
在用户选择语言时,将其偏好存储在持久化位置(如 cookie)中。后续访问时,优先检查该存储的偏好,再考虑浏览器头信息或其他检测方式。如果找到有效的已存储语言,自动重定向到对应的语言路由。这样可以确保用户的明确选择优先,并在多个会话间持续生效。
步骤
1. 创建用于存储语言偏好的 cookie
定义一个 cookie,用于保存用户选择的语言,并设置较长的过期时间。
import { createCookie } from "react-router";
export const languagePreference = createCookie("language-preference", {
maxAge: 31536000,
httpOnly: false,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
});
该 cookie 有效期为一年,客户端代码可读取用户的语言偏好。
2. 添加用于存储语言选择的操作
创建一个 action,用于处理语言选择表单的提交,并将选择结果存储到 cookie 中。
import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const selectedLanguage = formData.get("language");
if (typeof selectedLanguage === "string") {
return redirect(`/${selectedLanguage}`, {
headers: {
"Set-Cookie": await languagePreference.serialize(selectedLanguage),
},
});
}
return redirect("/");
}
当用户提交语言选择后,该操作会将其存储到 cookie,并重定向到相应的语言路由。
3. 创建语言选择组件
构建一个表单组件,让用户选择他们偏好的语言。
import { Form } from "react-router";
export function LanguageSelector({
currentLanguage,
}: {
currentLanguage: string;
}) {
return (
<Form method="post">
<select
name="language"
defaultValue={currentLanguage}
onChange={(e) => e.currentTarget.form?.requestSubmit()}
>
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
</select>
</Form>
);
}
当用户更改选择时,此组件会自动提交,并触发用于存储偏好的操作。
4. 在根 loader 中检查已存储的偏好设置
在根路由的 loader 中添加逻辑,检查是否有已存储的语言偏好,并据此进行重定向。
import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const cookieHeader = request.headers.get("Cookie");
const storedLanguage = await languagePreference.parse(cookieHeader);
if (url.pathname === "/" && storedLanguage) {
return redirect(`/${storedLanguage}`);
}
return null;
}
当用户访问根路径时,此 loader 会检查是否有已存储的语言偏好,如果存在,则将其重定向到所选语言的路由。
5. 校验已存储的语言是否为支持的语言环境
在重定向前,确保已存储的偏好设置有效。
import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";
const SUPPORTED_LANGUAGES = ["en", "es", "fr", "de"];
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const cookieHeader = request.headers.get("Cookie");
const storedLanguage = await languagePreference.parse(cookieHeader);
if (
url.pathname === "/" &&
storedLanguage &&
SUPPORTED_LANGUAGES.includes(storedLanguage)
) {
return redirect(`/${storedLanguage}`);
}
return null;
}
此校验可防止在 cookie 值被篡改或支持的语言发生变化后,重定向到无效或不受支持的语言路由。