如何在 React Router v7 中支持从右到左 (RTL) 的语言
为阿拉伯语和希伯来语镜像布局
问题
大多数设计系统假设文本是从左到右流动的。导航从左侧开始,侧边栏固定在左侧,内容从左到右阅读。但阿拉伯语和希伯来语是从右到左阅读的,它们的布局应相应地镜像——在英语中出现在左侧的内容,在阿拉伯语中应出现在右侧。如果没有这种镜像,整个界面会显得颠倒。视觉流动与阅读方向相矛盾,造成一种令人困惑的体验,用户必须与布局“对抗”才能自然阅读。
这一挑战不仅仅是简单的文本对齐问题。边距、内边距、边框和定位都需要适应。在英语中有左边距的按钮,在阿拉伯语中应有右边距。指向右侧的图标应指向左侧。整个界面的空间逻辑必须翻转,以匹配阅读方向。
解决方案
在文档的 <html> 元素上设置 dir 属性,以指定当前语言的文本方向。对于像英语这样的从左到右的语言,使用 ltr;对于像阿拉伯语和希伯来语这样的从右到左的语言,使用 rtl。这个单一属性会使浏览器自动镜像许多布局行为。
使用 CSS 逻辑属性(如 margin-inline-start)设计布局,而不是物理属性(如 margin-left),这样当文本方向改变时,间距会自动适应。逻辑属性是方向无关的——它们定义相对于文本流的间距,而不是固定的屏幕位置。当文档方向为 RTL 时,margin-inline-start 会变为 margin-right,布局会自动镜像,无需额外代码。
步骤
1. 检测当前语言环境的文本方向
React Router 7 需要在 app/root.tsx 中设置一个根路由来渲染 HTML 文档。创建一个辅助函数,将语言环境代码映射到其文本方向。
const locales = {
en: { dir: "ltr" },
ar: { dir: "rtl" },
he: { dir: "rtl" },
es: { dir: "ltr" },
};
function getTextDirection(locale: string): "ltr" | "rtl" {
return locales[locale as keyof typeof locales]?.dir || "ltr";
}
此函数为每个支持的语言环境返回适当的方向,默认情况下,对于未知语言环境返回从左到右。
2. 在 html 元素上设置 dir 属性
在根布局中,获取当前的语言环境并将相应的方向应用到文档中。
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" dir="ltr">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function Root() {
return <Outlet />;
}
将硬编码的 dir="ltr" 替换为基于语言环境检测机制的动态值。如果您的应用程序在加载器或上下文中存储当前语言环境,请在此处读取并传递给 getTextDirection。
3. 使用逻辑属性进行间距设置
在组件和样式表中用逻辑属性替换物理 CSS 属性。
export default function Card({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<div
style={{
paddingInlineStart: "1rem",
paddingInlineEnd: "1rem",
marginInlineStart: "auto",
borderInlineStart: "4px solid blue",
}}
>
<h2>{title}</h2>
{children}
</div>
);
}
当设置 dir="rtl" 时,paddingInlineStart 应用于右侧,paddingInlineEnd 应用于左侧,自动镜像布局。相同的组件在 LTR 和 RTL 环境中都能正确工作,无需条件逻辑。
4. 将逻辑属性应用于布局容器
对于常见的布局模式(如导航栏和内容网格),使用逻辑属性。
export default function Navigation() {
return (
<nav
style={{
display: "flex",
gap: "1rem",
paddingInline: "2rem",
borderBlockEnd: "1px solid #ccc",
}}
>
<a href="/" style={{ marginInlineEnd: "auto" }}>
Home
</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
);
}
paddingInline 在两侧设置内边距,而 marginInlineEnd: "auto" 将内容推到起始边缘。当方向改变时,这些属性会自动翻转。导航布局会自动镜像以适应 RTL 语言。
5. 处理图标和方向性图形
对于表示方向或流程的图标,根据文本方向有条件地翻转它们。
function BackButton() {
const dir = document.documentElement.dir;
const iconStyle = {
transform: dir === "rtl" ? "scaleX(-1)" : "none",
marginInlineEnd: "0.5rem",
};
return (
<button>
<span style={iconStyle}>←</span>
Back
</button>
);
}
此代码在 RTL 模式下水平翻转箭头图标,同时通过逻辑属性保持文本和间距的方向感知。并非所有图标都需要翻转——仅限于那些表示方向或移动的图标。