如何在 React Router v7 中支持从右到左(RTL)语言
为阿拉伯语和希伯来语镜像布局
问题
大多数设计系统默认文本从左到右流动。导航从左侧开始,侧边栏固定在左侧,内容也是从左到右阅读。但阿拉伯语和希伯来语是从右到左阅读的,它们的布局也应相应镜像——英文界面中出现在左侧的内容,在阿拉伯语界面中应出现在右侧。如果没有这种镜像,整个界面会显得颠倒。视觉流与阅读方向相悖,用户在阅读时不得不与布局“对抗”,体验非常不自然。
挑战不仅仅是文本对齐。边距、内边距、边框和定位都需要适配。例如,英文中按钮有左边距,阿拉伯语中应为右边距。指向右侧的图标应改为指向左侧。整个界面的空间逻辑都必须根据阅读方向进行翻转。
解决方案
在文档的 <html> 元素上设置 dir 属性,以指定当前语言的文本方向。对于英文等从左到右的语言,使用 ltr;对于阿拉伯语和希伯来语等从右到左的语言,使用 rtl。这个属性会让浏览器自动镜像许多布局行为。
使用 CSS 逻辑属性(如 margin-inline-start)设计布局,而不是物理属性(如 margin-left),这样在文本方向变化时,间距会自动适配。逻辑属性与方向无关——它们定义的是相对于文本流的间距,而不是固定的屏幕位置。当文档方向为 RTL 时,margin-inline-start 会变为 margin-right,布局会自动镜像,无需额外代码。
步骤
1. 检测当前 locale 的文本方向
React Router 7 要求在 app/root.tsx 处设置一个根路由,用于渲染 HTML 文档。请创建一个辅助函数,将 locale 代码映射到其文本方向。
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";
}
该函数会为每个支持的 locale 返回合适的文本方向,未知 locale 默认使用从左到右。
2. 设置 html 元素的 dir 属性
在根布局中,获取当前 locale,并将对应的文本方向应用到文档上。
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" 替换为基于 locale 检测机制的动态值。如果应用将当前 locale 存储在 loader 或 context 中,请在此读取并传递给 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" 将内容推到 inline-start 边缘,当文本方向变化时会从左侧切换到右侧。导航布局会自动适配 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 模式下水平翻转箭头图标,同时通过逻辑属性保持文本和间距的方向感知。并非所有图标都需要翻转——只需翻转那些指示方向或移动的图标。