如何在 TanStack Start v1 中支持从右到左 (RTL) 的语言

为阿拉伯语和希伯来语镜像布局

问题

大多数网页布局假设文本是从左到右流动的。导航菜单固定在左侧,侧边栏出现在左侧,内容从左到右阅读。对于像阿拉伯语和希伯来语这样从右到左阅读的语言,这会造成一种视觉流向与阅读方向相矛盾的迷惑体验。用户会看到导航在错误的一侧,图标位置显得别扭,布局看起来反向。如果没有适当的 RTL(从右到左)支持,界面对 RTL 语言的使用者来说会变得难以导航和理解。

解决方案

根据当前的语言环境动态设置文档的文本方向,允许浏览器自动为 RTL 语言镜像布局。使用 CSS 逻辑属性而不是物理方向属性,这样间距、定位和对齐可以根据文本方向自动适应,无需额外的代码。这种方法使布局保持方向无关性:在英语中出现在内容“开始”的部分会正确地出现在阿拉伯语中的“开始”(右侧),由浏览器处理转换。

步骤

1. 根据语言环境确定文本方向

创建一个辅助函数,使用浏览器内置的国际化 API,根据给定的语言环境返回文本方向。

export function getTextDirection(locale: string): "ltr" | "rtl" {
  try {
    const localeObj = new Intl.Locale(locale);
    if (
      "getTextInfo" in localeObj &&
      typeof localeObj.getTextInfo === "function"
    ) {
      return localeObj.getTextInfo().direction;
    }
  } catch (e) {
    console.warn(`无法确定语言环境的方向:${locale}`);
  }

  const rtlLocales = ["ar", "he", "fa", "ur"];
  const lang = locale.split("-")[0];
  return rtlLocales.includes(lang) ? "rtl" : "ltr";
}

此函数在可用时使用 Intl.Locale.getTextInfo(),并回退到已知的 RTL 语言列表。它根据语言环境返回 'ltr''rtl'

2. 在 html 元素上设置 dir 属性

在根路由中,获取当前的语言环境(locale),并将相应的文本方向应用到文档的 <html> 元素。

import {
  createRootRoute,
  Outlet,
  Scripts,
  HeadContent,
} from "@tanstack/react-router";
import { useIntl } from "react-intl";
import { getTextDirection } from "~/utils/text-direction";

export const Route = createRootRoute({
  component: RootComponent,
});

function RootComponent() {
  return (
    <RootDocument>
      <Outlet />
    </RootDocument>
  );
}

function RootDocument({ children }: { children: React.ReactNode }) {
  const intl = useIntl();
  const dir = getTextDirection(intl.locale);

  return (
    <html lang={intl.locale} dir={dir}>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  );
}

<html> 元素上的 dir 属性告诉浏览器为 RTL(从右到左)语言反转布局。Flexbox、网格和内联内容会自动镜像。

3. 用逻辑属性替换物理 CSS 属性

更新样式表,使用响应文本方向的逻辑属性,而不是固定的物理方向。

.sidebar {
  padding-inline-start: 1rem;
  margin-inline-end: 2rem;
  border-inline-start: 1px solid #ccc;
}

.icon {
  margin-inline-end: 0.5rem;
}

.card {
  inset-inline-start: 0;
  text-align: start;
}

padding-inline-start 这样的逻辑属性在 LTR(从左到右)中映射为 padding-left,在 RTL 中映射为 padding-right。浏览器根据 dir 属性应用正确的物理属性,因此您的样式可以在两种方向中正常工作,无需重复。

4. 使用方向无关的对齐值

在 CSS 和内联样式中,用逻辑对齐关键字替换物理对齐关键字。

export function Header() {
  return (
    <header
      style={{
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
      }}
    >
      <nav style={{ display: "flex", gap: "1rem" }}>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
      <div style={{ textAlign: "end" }}>
        <button>Menu</button>
      </div>
    </header>
  );
}

使用 textAlign: 'end' 而不是 'right' 确保文本对齐到阅读方向的末端。像 justifyContentalignItems 这样的 Flexbox 属性会自动遵循由 dir 属性设置的方向。