Как поддерживать языки с направлением справа налево (RTL) в Next.js (Pages Router) v16

Зеркальные макеты для арабского и иврита

Проблема

Большинство веб-макетов рассчитаны на поток текста слева направо. Меню навигации закреплены слева, контент читается слева направо, а отступы задаются с помощью свойств вроде margin-left или padding-right. Когда приложение переводится на арабский или иврит, эти языки читаются справа налево, и весь визуальный макет должен зеркально отражаться. Без зеркалирования пользователи сталкиваются с дезориентирующим интерфейсом, где визуальная иерархия противоречит их привычному направлению чтения, что делает навигацию запутанной, а само приложение — неаккуратным.

Проблема выходит за рамки выравнивания текста. Физические CSS-свойства, такие как left, right, margin-left и padding-right, привязаны к фиксированным краям экрана, а не к потоку контента. Когда направление текста меняется, эти свойства остаются привязанными к тем же физическим границам, из-за чего макет не может адаптироваться естественным образом.

Решение

Установите атрибут направления текста документа в соответствии с текущей локалью — это позволит браузеру автоматически менять направление макета для RTL-языков. Замените физические CSS-свойства на логические, которые ориентируются на поток контента, а не на положение на экране. Логические свойства, такие как margin-inline-start и padding-inline-end, автоматически соответствуют нужному краю в зависимости от направления текста, позволяя макетам зеркалироваться без дополнительного кода или условий в стилях.

Такой подход позволяет создавать макеты, не зависящие от направления текста, которые корректно работают и для LTR, и для RTL-языков без дублирования стилей и сложной логики.

Шаги

1. Создайте хелпер для определения направления текста по локали

Логические свойства адаптируются в зависимости от направления текста: margin-inline-start эквивалентно margin-left в LTR-контексте и margin-right в RTL-контексте. Напишите утилиту, которая сопоставляет локали с их направлением текста.

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, чтобы установить атрибут dir на корневом элементе HTML в зависимости от текущей локали.

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, автоматически сопоставляются с физическими свойствами в зависимости от режима письма и направления. Обновите стили компонентов, чтобы использовать flow-relative свойства.

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 автоматически подстраиваются под направление текста, корректно выравнивая контент для языков с направлением слева направо и справа налево.