如何在 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 模式下水平翻转箭头图标,同时通过逻辑属性保持文本和间距的方向感知。并非所有图标都需要翻转——仅限于那些表示方向或移动的图标。