如何在 Next.js (Pages Router) v16 中支持从右到左 (RTL) 的语言

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

问题

大多数网页布局假设文本流向是从左到右。导航菜单固定在左侧,内容从左到右阅读,间距通过诸如 margin-leftpadding-right 之类的属性来设置。当应用程序被翻译成阿拉伯语或希伯来语时,这些语言是从右到左阅读的,整个视觉布局需要镜像以匹配阅读方向。如果不进行镜像,用户会体验到一种令人困惑的界面,视觉层次与他们的自然阅读方向相矛盾,导致导航变得混乱,应用程序显得不够完善。

这一挑战不仅限于文本对齐。像 leftrightmargin-leftpadding-right 这样的物理 CSS 属性是绑定到固定屏幕位置的,而不是内容流向。当文本方向改变时,这些属性仍然固定在相同的物理边缘,阻止布局自然适应。

解决方案

将文档的文本方向属性设置为匹配当前的语言环境,允许浏览器自动反转 RTL 语言的布局流向。用引用内容流向而非屏幕位置的逻辑属性替换物理 CSS 属性。像 margin-inline-startpadding-inline-end 这样的逻辑属性会根据文本方向自动映射到正确的物理边缘,使布局能够在不需要额外代码或条件样式的情况下自动镜像。

这种方法创建了方向无关的布局,能够在 LTR 和 RTL 语言中正确工作,无需重复样式或添加复杂逻辑。

步骤

1. 创建一个帮助函数,根据语言环境确定文本方向

逻辑属性会根据文本方向进行调整,例如在 LTR 环境中,margin-inline-start 等同于 margin-left,而在 RTL 环境中等同于 margin-right。构建一个实用函数,将语言环境映射到其文本方向。

export function getDirection(locale: string): "ltr" | "rtl" {
  const rtlLocales = ["ar", "he", "fa", "ur"];
  return rtlLocales.includes(locale) ? "rtl" : "ltr";
}

此函数识别 RTL 语言并返回适合在 HTML 和 CSS 中使用的方向值。

2. 设置文档方向属性

自定义 Document 类可以重写 render 方法,以通过 lang 和 dir 等属性自定义 HTML 元素。创建一个自定义 Document,根据当前语言环境在根 HTML 元素上设置 dir 属性。

import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from "next/document";
import { getDirection } from "../utils/direction";

class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    const locale = this.props.__NEXT_DATA__.locale || "en";
    const dir = getDirection(locale);

    return (
      <Html lang={locale} dir={dir}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Next.js 路由器通过 locale 属性提供当前活动的语言环境。文档从 Next.js 路由数据中读取语言环境,并将相应的方向应用于 HTML 元素,从而启用浏览器级别的 RTL 支持。

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

逻辑属性(如 margin-inline-start、padding-inline-start 及其简写形式 margin-inline 和 padding-inline)会根据书写模式和方向自动映射到物理属性。更新组件样式以使用与流相关的属性。

export default function Navigation() {
  return (
    <nav className="nav">
      <ul className="nav-list">
        <li className="nav-item">Home</li>
        <li className="nav-item">About</li>
        <li className="nav-item">Contact</li>
      </ul>
    </nav>
  );
}
.nav {
  padding-inline: 1rem;
  border-inline-start: 4px solid blue;
}

.nav-list {
  display: flex;
  gap: 1rem;
  margin-block: 0;
  padding-inline-start: 0;
}

.nav-item {
  margin-inline-end: 1.5rem;
}

用于 margin、padding 和 inset 的逻辑属性使得在不同书写模式下定位元素更加简单高效。这些属性在 RTL 环境中会自动反转,无需额外的样式或媒体查询。

4. 使用逻辑属性进行定位

对于绝对定位或相对定位的元素,将 leftright 替换为 inset-inline-startinset-inline-end

export default function Sidebar() {
  return (
    <aside className="sidebar">
      <button className="close-button">×</button>
      <p>侧边栏内容</p>
    </aside>
  );
}
.sidebar {
  position: fixed;
  inset-inline-start: 0;
  inset-block-start: 0;
  inline-size: 250px;
  block-size: 100vh;
  padding-inline: 1rem;
}

.close-button {
  position: absolute;
  inset-inline-end: 0.5rem;
  inset-block-start: 0.5rem;
}

在逻辑属性方法中,宽度属性被替换为 inline-size,高度属性被替换为 block-size。这些属性确保定位的元素能够根据当前文本方向出现在正确的一侧。

5. 应用逻辑文本对齐

text-align: lefttext-align: right 替换为 text-align: starttext-align: end

.content {
  text-align: start;
}

.metadata {
  text-align: end;
  margin-inline-start: auto;
}

逻辑属性是指根据内容流动方向而非物理视口尺寸来定义盒子边缘。startend 的文本对齐值会自动适应文本方向,为 LTR 和 RTL 语言正确对齐内容。