如何在 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. 创建一个帮助函数,根据语言环境确定文本方向
逻辑属性会根据文本方向进行调整,例如在 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. 使用逻辑属性进行定位
对于绝对定位或相对定位的元素,将 left 和 right 替换为 inset-inline-start 和 inset-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: 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 语言正确对齐内容。