如何在 Next.js(Pages Router)v16 中支持从右到左(RTL)语言
为阿拉伯语和希伯来语镜像布局
问题
大多数网页布局默认采用从左到右的文本流。导航菜单通常固定在左侧,内容从左到右阅读,间距通常通过 margin-left 或 padding-right 等属性设置。当应用程序被翻译为阿拉伯语或希伯来语时,这些语言是从右到左书写的,整个视觉布局应进行镜像以适应。如果不进行镜像,用户会遇到视觉层级与自然阅读方向相悖的界面,导致导航混乱,应用体验不佳。
挑战不仅仅在于文本对齐。像 left、right、margin-left 和 padding-right 这样的物理 CSS 属性是绑定在屏幕的固定位置,而不是内容流。当文本方向发生变化时,这些属性依然锚定在原有的物理边缘,导致布局无法自然适应。
解决方案
将文档的文本方向属性设置为当前语言环境,使浏览器能够自动反转 RTL 语言的布局流。用基于内容流的逻辑属性替换物理 CSS 属性。像 margin-inline-start 和 padding-inline-end 这样的逻辑属性会根据文本方向自动映射到正确的物理边缘,使布局无需额外代码或条件样式即可自动镜像。
这种方法可以创建与方向无关的布局,无需重复样式或增加复杂逻辑,即可同时支持 LTR 和 RTL 语言。
步骤
1. 创建一个根据语言环境判断文本方向的辅助函数
逻辑属性会根据文本方向自动适配,其中 margin-inline-start 在 LTR(从左到右)环境下等同于 margin-left,在 RTL(从右到左)环境下等同于 margin-right。请编写一个实用函数,将 locale 映射到其文本方向。
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 方法,以便为 HTML 元素自定义 lang 和 dir 等属性。请创建一个自定义 Document,根据当前 locale 在根 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 属性提供当前激活的 locale。文档会从 Next.js 路由数据中读取 locale,并将相应的方向应用到 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. 使用逻辑属性进行定位
对于绝对或相对定位的元素,请将 left 和 right 替换为 inset-inline-start 和 inset-inline-end。
export default function Sidebar() {
return (
<aside className="sidebar">
<button className="close-button">×</button>
<p>Sidebar content</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;
}
在逻辑属性方法中,width 属性被 inline-size 替代,height 属性被 block-size 替代。这些属性确保定位元素能够根据当前文本方向显示在正确的位置。
5. 应用逻辑文本对齐
将 text-align: left 和 text-align: right 替换为 text-align: start 和 text-align: end。
.content {
text-align: start;
}
.metadata {
text-align: end;
margin-inline-start: auto;
}
逻辑属性是指根据内容流动方向来描述盒子边缘,而不是物理视口尺寸。start 和 end 这类文本对齐值会根据文本方向自动适配,使内容在 LTR 和 RTL 语言下都能正确对齐。