如何在 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(`Could not determine direction for locale: ${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>
  );
}

dir 属性设置在 <html> 元素上时,会告知浏览器针对 RTL 语言反转布局。Flexbox、Grid 和内联内容会自动镜像。

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 属性设置的方向。