如何在 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. 添加一个操作以存储语言选择
创建一个操作,用于处理语言选择表单的提交并将选择存储在 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. 在根加载器中检查存储的偏好
在根路由加载器中添加逻辑,检查存储的语言偏好并相应地重定向。
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;
}
当用户访问根路径时,此加载器会检查存储的语言偏好,并在存在偏好时将其重定向到所选语言的路由。
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 值被篡改或支持的语言自偏好存储后发生更改的情况下,重定向到无效或不支持的语言路由。