# General Translation Knowledge Base: 如何在 CSS 中支持 RTL（从右到左）语言？
URL: https://generaltranslation.com/zh/kb/right-to-left-css.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 在 html 元素上同时设置 dir="rtl" 和 lang，因为仅设置 lang 并不会改变文本方向；然后将 margin-left 等物理 CSS 属性替换为 margin-inline-start 等逻辑属性。Flex 行、Grid 列和默认文本对齐会自动翻转，而 translateX()、left 和 right 不会。
Category: 按区域设置格式化
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

阿拉伯语、希伯来语、波斯语和乌尔都语都是从右往左阅读的。只要文档声明了文本方向，浏览器就会自动反转 flex 行、Grid 列和默认文本对齐。大多数 RTL 问题都出在那些写死了物理方位的 CSS 上：`left`、`right`、`margin-left`、`translateX`。

## 在 HTML 中设置文本方向

在根元素上设置文本方向和语言：

```html
<html lang="ar" dir="rtl">
```

W3C 的[文本方向指南](https://www.w3.org/International/questions/qa-html-dir)建议在标记中声明基础方向，而不要使用 CSS `direction` 属性，这样即使样式未能加载，方向设置依然有效；MDN 的 [`direction` 参考](https://developer.mozilla.org/en-US/docs/Web/CSS/direction)也持相同观点。`lang` 属性不会设置方向，即便带有 `ar-Arab` 这样的文字体系子标签也不例外，因此两者缺一不可。只有当某个区块的方向发生变化时 (例如阿拉伯语文章中的一段英文引文) ，才需要在页面更深的层级添加 `dir`。

要在 JavaScript 中根据区域设置代码推导出 `dir`，可以使用 [`Intl.Locale` 的 `getTextInfo()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo)，它会返回文本方向：`new Intl.Locale('ar').getTextInfo().direction` 的值为 `'rtl'`。Chrome 130、Safari 17 和 Firefox 153 均支持该方法，而 Chrome 99 至 129 则通过 `textInfo` 属性提供相同的数据，因此请为旧版浏览器保留后备方案。

## 应该采用哪种方式来翻转布局？

设置好文本方向后，接下来要做的就是让样式表随之调整。常见的做法有四种：

| 方式                                                | 布局如何翻转                | 维护成本           | 主要风险                             |
| ------------------------------------------------- | --------------------- | -------------- | -------------------------------- |
| 用 CSS `direction: rtl` 代替 `dir`                   | 文本和逻辑属性会翻转，物理属性不会     | 低              | `:dir()` 会忽略它，且一旦缺少 CSS，方向信息就会丢失 |
| 单独的 RTL 样式表，手写或用 [RTLCSS](https://rtlcss.com/) 生成 | 由第二份样式表对调物理方向         | 需要发布两份样式表并保持同步 | LTR 样式变动后，覆盖项会逐渐与之脱节             |
| `dir` 属性配合 CSS 逻辑属性                               | 每个属性根据 `dir` 解析到正确的一侧 | 一份样式表          | 后续修改中又混入物理属性                     |
| Tailwind CSS 逻辑工具类配合 `rtl:` 和 `ltr:` 变体           | 工具类编译为逻辑属性            | 一套类名           | 本可用逻辑工具类的地方却用了变体                 |

新代码以及你改动到的任何组件，都应使用逻辑属性 (直接使用或通过 Tailwind) ，这样一份样式表就能同时支持两种方向。如果大型遗留代码库中充斥着 `left` 和 `right`，而你又需要在重构之前就支持 RTL，可以使用生成的 RTL 样式表。

## 使用逻辑属性编写布局

[CSS 逻辑属性](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values)根据文本流向来命名各个边：inline-start 在 LTR 中指左侧，在 RTL 中指右侧。这些属性已达到 Baseline“广泛可用”级别；例如，`inset-inline-start` 自 2021 年 4 月起就已获得各主流浏览器支持。下面是一张卡片改写前后的对比：

```css
/* 修改前：物理方向在 RTL 下保持不变 */
.card {
  margin-left: 16px;
  padding-right: 24px;
  text-align: left;
  border-left: 4px solid var(--accent);
  border-top-left-radius: 8px;
}
.card .badge {
  position: absolute;
  top: 8px;
  right: 8px;
}

/* 修改后：逻辑方向随 dir 自动翻转 */
.card {
  margin-inline-start: 16px;
  padding-inline-end: 24px;
  text-align: start;
  border-inline-start: 4px solid var(--accent);
  border-start-start-radius: 8px;
}
.card .badge {
  position: absolute;
  inset-block-start: 8px;
  inset-inline-end: 8px;
}
```

在设置了 `dir="rtl"` 的页面中，`margin-inline-start: 16px` 的计算结果为 16px 的右外边距，而 `border-start-start-radius` 作用于右上角的圆角。使用 `margin-inline` 和 `padding-inline` 可以一次性设置内联方向的两侧。

### 哪些会自动翻转

* **Flexbox 行：**&#x4F7F;用 `flex-direction: row` 时，第一个项目位于最右侧。
* **Grid 列：**&#x7B2C; 1 列位于最右侧。
* **文本对齐：**`text-align` 的初始值为 `start`，因此未设置样式的文本会右对齐。
* **内联内容和表格列：**&#x4E24;者都遵循基础文本方向。

### 哪些不会翻转

* 定位元素上的 `left` 和 `right`：`left: 0` 始终位于左侧。
* `margin-left`、`padding-right`、`border-left`、`float: left` 和 `text-align: left`。
* `transform: translateX()`：正值始终向右移动，因此滑入式抽屉和轮播图会朝反方向移动。
* `background-position: left`、`linear-gradient(to right, …)` 以及 `box-shadow` 的水平偏移量。这些属性没有对应的逻辑写法，因此需要在 `:dir(rtl)` 下覆盖它们。

对于变换，可以借助 CSS 自定义属性来翻转正负号：

```css
:root { --inline-sign: 1; }
:root:dir(rtl) { --inline-sign: -1; }

/* 一个从行内起始边滑出隐藏的抽屉 */
.drawer[data-state="closed"] {
  transform: translateX(calc(-100% * var(--inline-sign)));
}
```

### Tailwind CSS

在 Tailwind CSS v4.3 中，逻辑工具类包括 `ms-*` 和 `me-*` (外边距) 、`ps-*` 和 `pe-*` (内边距) 、`inset-s-*` 和 `inset-e-*`、`text-start`、`border-s-*` 以及 `rounded-s-*`；各工具类的文档页面 (例如 [margin](https://tailwindcss.com/docs/margin)) 中均有列出。[`rtl:` 和 `ltr:` 变体](https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support)会编译为包含 `:dir()` 的选择器，因此即使页面从未设置 `dir="ltr"`，`ltr:` 也会匹配。建议优先使用 `ms-3`，而非 `ltr:ml-3 rtl:mr-3`；变体则留给变换和图标等场景，例如 `rtl:-scale-x-100`。

## 处理图标、混合文本方向的文本以及字体

### 仅镜像具有方向性的图标

在 RTL 布局下水平翻转图标：

```css
.icon-directional:dir(rtl) {
  transform: scaleX(-1);
}
```

[`:dir()` 伪类](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir)匹配元素继承的文本方向，而 `[dir="rtl"]` 只匹配带有该属性的元素本身。`:dir()` 自 2023 年 12 月起已获得各主流浏览器支持，目前已达到 Baseline 广泛可用状态。如果需要支持旧版浏览器，请改用 `[dir="rtl"] .icon-directional`。

Apple 的[从右到左设计指南](https://developer.apple.com/design/human-interface-guidelines/right-to-left)同样适用于 Web，是一套很好的参考规则：

* **需要镜像：** 后退和前进箭头、下一项和上一项控件、进度条和滑块、表示文本行的图标，以及表示向前移动的图标。
* **无需镜像：** 标志、通用符号 (如对勾) 、实物 (如时钟) 、按右手习惯绘制的工具，以及照片或插图。
* **切勿颠倒数字：** 无论哪种语言，电话号码或数字 541 的阅读顺序都保持不变。

### 隔离双向文本

当 RTL 和 LTR 文本出现在同一行时，空格、连字符、数字等中性字符可能会归到错误的一侧；W3C 的 [内联 bidi 标记指南](https://www.w3.org/International/articles/inline-bidi-markup/) 将这种现象称为“溢出” (spillover) 。在 MDN 的 [`<bdi>` 示例](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi) 中，阿拉伯语名字后接 &quot;- 1st place&quot; 时，&quot;1&quot; 会被渲染到错误的一端。凡是文本方向与周围文本不同的值，都应单独包裹起来：

```html
<!-- 来自数据库的名称，文本方向未知 -->
<li><bdi>اَلأَعْشَى</bdi> - 1st place</li>

<!-- 在阿拉伯语文本中始终从左到右（LTR）显示的值 -->
<p>اتصل بنا على <span dir="ltr">+1 (555) 010-2030</span></p>

<!-- 用户输入的文本，文本方向未知 -->
<textarea dir="auto"></textarea>
```

`<bdi>` 的作用类似于 `dir="auto"`，会根据第一个强方向性字符确定文本方向。对于始终从左到右显示的值，例如电话号码、电子邮件地址、URL 和订单 ID，请使用 `dir="ltr"`。CSS 属性 `unicode-bidi: isolate` 也能在样式表中实现同样的隔离效果，但 MDN [提醒开发者不要覆盖 `unicode-bidi`](https://developer.mozilla.org/en-US/docs/Web/CSS/unicode-bidi)，因此应优先在标记中使用 `dir` 和 `<bdi>`。

### 字体

* **确保字体覆盖相应文字体系。** 按语言为包含阿拉伯文或希伯来文字形的字体限定作用范围：`:lang(ar) { font-family: "Noto Sans Arabic", system-ui, sans-serif; }`。
* **不要为阿拉伯文设置字间距。** 阿拉伯文字母彼此相连，[CSS Text 规范](https://www.w3.org/TR/css-text-3/)建议开发者避免对连写文字使用 `letter-spacing`，因为不同实现的渲染效果不一致。
* **平衡与大写字母的视觉大小。** 阿拉伯文和希伯来文没有大写形式，因此与之并排的全大写拉丁文会显得更大；Apple 建议将 RTL 字体的字号调大约 2 磅。

## 常见 RTL 问题及修复方法

### 页面已翻译，但文字仍从左到右排列

**原因：**&#x7FFB;译后的页面设置了 `lang="ar"`，但没有设置 `dir`，或者只在内部包裹元素上设置了 `direction: rtl`。**解决方法：**&#x6BCF;次渲染时 (包括服务器端渲染的 HTML) ，都根据当前区域设置在 `<html>` 上设置 `dir="rtl"`。

### 间距出现在图标和标签的错误一侧

**原因：** 图标与文本之间使用了 `margin-left` 或 `padding-right`。**修复：** 改用 `margin-inline-end` 或 Tailwind 的 `me-*`，或者在 flex 容器中使用 `gap`，它根本没有方向之分。

在 Chrome 155 中测量一个 inline-flex 按钮 (标签前有一个 16px 图标) ：在从右到左的文本中，物理外边距会导致图标与标签之间没有间距，那 8px 反而被推到了外侧边缘；而逻辑外边距则会移到朝向标签的一侧：

```text title="Output"
.icon { margin-right: 8px }        ltr: 8px gap   rtl: 0px gap
.icon { margin-inline-end: 8px }   ltr: 8px gap   rtl: 8px gap (computed as margin-left)
```

### 徽章、工具提示和下拉菜单从错误的一侧弹出

**原因：** 使用了 `position: absolute` 搭配 `left` 或 `right`，或者 JavaScript 定位库从左侧开始计算位置。**修复：** 改用 `inset-inline-start` 和 `inset-inline-end`；如果定位库支持设置文本方向，请将文本方向传给它。

### 抽屉和轮播图滑动方向错误

**原因：** `translateX()` 基于物理方向。**修复：** 参照上文的抽屉示例，将位移值乘以文本方向符号，并在 RTL 布局下测试滑动手势。

### 返回箭头指向前方，或标志被镜像翻转

**原因：** 图标被一刀切地全部翻转或全部不翻转。**解决方法：** 仅对具有方向性的图标进行镜像翻转。

### 标点或数字跳到行的另一端

**原因：**&#x63D2;入的值 (如用户名、产品名称或电话号码) 与周围文本的文本方向不同。**解决方法：**&#x7528; `<bdi>` 包裹该值；如果确定它是从左到右的，也可以用带有 `dir="ltr"` 的元素包裹。

### 阿拉伯字母看起来没有连写

**原因：**&#x4E3A;拉丁文标题设置的全局 `letter-spacing` 影响了阿拉伯文。**修复：**&#x9488;对 `:lang(ar)` 及其他连写文字体系，将 `letter-spacing` 重置为 0。

要在译文尚未就绪时提前发现这类问题，可以在从右到左的[伪区域设置](/kb/pseudo-localization)下逐页点击浏览应用，并在发布前运行[多语言应用发布清单](/kb/multilingual-app-launch-checklist)中的从右到左检查项。

## 使用 General Translation 根据当前区域设置设定 dir

在 Next.js App Router 应用中，`gt-next` 通过 `gt-next/server` 提供 [`getLocaleDirection`](/docs/react/nextjs/reference/functions/get-locale-direction)，因此根布局可以在服务端设置 `lang` 和 `dir`。本示例假设 `app/[locale]/layout.tsx` 为根布局，而 Next.js [要求根布局同时渲染 `<html>` 和 `<body>`](https://nextjs.org/docs/app/api-reference/file-conventions/layout#root-layout)：

```tsx title="app/[locale]/layout.tsx"
import { getLocale, getLocaleDirection } from 'gt-next/server';
import type { ReactNode } from 'react';

export default async function LocaleLayout({ children }: { children: ReactNode }) {
  const [locale, direction] = await Promise.all([
    getLocale(),
    getLocaleDirection(),
  ]);
  return (
    <html lang={locale} dir={direction}>
      <body>{children}</body>
    </html>
  );
}
```

在 `gt-react`、`gt-next` 和 `gt-react-native` 组件中，[`useLocaleDirection`](/docs/react/reference/hooks/use-locale-direction) 钩子会根据当前区域设置返回 `'ltr'` 或 `'rtl'`。在 Web 端，可在组件树中的某个组件里将该值赋给 `document.documentElement.dir`。React Native 中没有 `document`：原生布局方向由 React Native 的 [`I18nManager`](https://reactnative.dev/docs/i18nmanager) 决定，且通过 `allowRTL` 或 `forceRTL` 所做的更改要到应用下次启动时才会生效。对于其他技术栈 (包括 Vue、`gt-tanstack-start` 和服务端模板) ，可使用 `generaltranslation` 中 Core 库提供的 [`getLocaleDirection`](/docs/platform/core/reference/utility-functions/locales/get-locale-direction)：它在本地运行，无需 API 密钥，对于 `ar-SA`、`he-IL`、`fa` 等代码会返回 `'rtl'`。无论采用哪种方式，你的 CSS 仍需遵循上文所述的逻辑属性和图标规则。

## 要点总结

* 在 `<html>` 上同时设置 `lang` 和 `dir="rtl"`；仅设置 `lang` 不会改变文本方向，也不应借助 CSS 的 `direction` 属性来指定文本方向。
* Flex 行、grid 列和默认文本对齐会自动翻转；而 `left`、`right`、`margin-left`、`translateX()`、`background-position` 以及阴影偏移不会。
* 将物理属性替换为逻辑属性 (`margin-inline-start`、`inset-inline-end`、`text-align: start`、`border-start-start-radius`) ，或改用 Tailwind 的 `ms-*`、`ps-*` 和 `inset-s-*`。
* 使用 `:dir(rtl)` 镜像箭头和进度指示器，但切勿镜像标志、对勾或时钟；混合方向的值应包裹在 `<bdi>` 中或设置 `dir="ltr"`。
* 在 General Translation 的 SDK 中，可通过 [`getLocaleDirection`](/docs/react/nextjs/reference/functions/get-locale-direction) (Next.js 服务端代码或 Core 库) 或 `useLocaleDirection` (React) 获取文本方向并设置 `dir`。

## Related questions

- [多语言应用本地化测试清单](https://generaltranslation.com/zh/kb/multilingual-app-launch-checklist.mdx): 在生产环境构建中，于每个支持的区域设置下测试九项内容：包括错误状态和邮件在内的完整流程、语言切换、复数形式、货币与时区、文本扩展、从右到左布局、无障碍访问、译文交付，以及可抓取的语言 URL。为每项检查设定明确的通过标准。
- [什么是伪本地化？](https://generaltranslation.com/zh/kb/pseudo-localization.mdx): 伪本地化是一种测试方法：将源文本替换为改写后的版本——加上重音符号、长度扩充约 40%，并用方括号包裹——这样在还没有任何翻译之前，硬编码字符串、截断、字体显示异常和句子拼接等问题就会暴露出来。Android 为此内置了 en-XA 和 ar-XB 两个伪区域设置。
- [如何在 React 中处理复数形式？](https://generaltranslation.com/zh/kb/how-to-handle-plurals-in-react.mdx): 在 React 中处理复数形式时，应为每种语言的每个复数形式各准备一个句子，并按该语言的复数规则选出对应句子——英语有 2 种形式，俄语有 4 种，阿拉伯语有 6 种。gt-react 和 gt-next 提供的 General Translation `<Plural>` 组件只需接收你的源语言形式，General Translation 便会生成每种目标语言所需的形式。
- [不使用翻译文件能实现 i18n 吗？](https://generaltranslation.com/zh/kb/i18n-without-translation-files.mdx): 可以。借助 General Translation 的 gt-react 和 gt-next 库，你可以为 React 和 Next.js 实现 i18n，既无需翻译键，也不用为每种语言维护 JSON 文件。文案以内联方式写在 JSX 的 `<T>` 组件中，gt translate 命令会在构建时结合代码中的上下文，为每种语言生成译文。Locadex 还能通过拉取请求让译文保持最新。

## Previous and next questions

- Previous question: [如何使用 String Catalogs 本地化 iOS 应用？](https://generaltranslation.com/zh/kb/localize-ios-app.mdx)
- Next question: [如何在不破坏时间轴的情况下翻译 SRT 字幕？](https://generaltranslation.com/zh/kb/translate-srt-subtitles.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/zh/kb/sitemap.md) for every article.
