Как поддерживать языки с направлением текста справа налево (RTL) в React Router v7
Зеркальное отображение макетов для арабского и иврита
Проблема
Большинство дизайн-систем предполагают, что текст читается слева направо. Навигация начинается слева, боковые панели закрепляются слева, а контент читается слева направо. Однако арабский и иврит читаются справа налево, и их макеты должны быть зеркальными — то, что находится слева в английском, должно быть справа в арабском. Без такого зеркального отображения весь интерфейс кажется перевернутым. Визуальный поток противоречит направлению чтения, создавая дезориентирующий опыт, при котором пользователи вынуждены бороться с макетом, чтобы читать естественно.
Эта проблема выходит за рамки простой выравнивания текста. Поля, отступы, границы и позиционирование также должны адаптироваться. Кнопка с левым полем в английском должна иметь правое поле в арабском. Иконки, указывающие вправо, должны указывать влево. Вся пространственная логика интерфейса должна быть перевернута, чтобы соответствовать направлению чтения.
Решение
Установите атрибут dir в элементе <html> документа, чтобы указать направление текста для текущего языка. Используйте ltr для языков с направлением текста слева направо, таких как английский, и rtl для языков с направлением текста справа налево, таких как арабский и иврит. Этот единственный атрибут заставляет браузеры автоматически зеркально отображать многие элементы макета.
Проектируйте макеты, используя логические свойства CSS, такие как margin-inline-start, вместо физических свойств, таких как margin-left, чтобы отступы адаптировались автоматически при изменении направления текста. Логические свойства не зависят от направления — они определяют отступы относительно потока текста, а не фиксированных позиций на экране. Когда направление документа становится RTL, margin-inline-start превращается в margin-right, и макет зеркально отображается без дополнительного кода.
Шаги
1. Определите направление текста для текущей локали
React Router 7 требует корневого маршрута в app/root.tsx, который рендерит HTML-документ. Создайте вспомогательную функцию, которая сопоставляет коды локалей с их направлением текста.
const locales = {
en: { dir: "ltr" },
ar: { dir: "rtl" },
he: { dir: "rtl" },
es: { dir: "ltr" },
};
function getTextDirection(locale: string): "ltr" | "rtl" {
return locales[locale as keyof typeof locales]?.dir || "ltr";
}
Эта функция возвращает соответствующее направление для каждой поддерживаемой локали, по умолчанию устанавливая направление слева направо для неизвестных локалей.
2. Установите атрибут dir на элементе html
В вашем корневом макете получите текущую локаль и примените соответствующее направление к документу.
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" dir="ltr">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function Root() {
return <Outlet />;
}
Замените жестко заданное значение dir="ltr" на динамическое значение, основанное на вашем механизме определения локали. Если ваше приложение сохраняет текущую локаль в загрузчике или контексте, прочитайте её здесь и передайте в getTextDirection.
3. Используйте логические свойства для отступов
Замените физические свойства CSS их логическими эквивалентами в ваших компонентах и стилях.
export default function Card({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<div
style={{
paddingInlineStart: "1rem",
paddingInlineEnd: "1rem",
marginInlineStart: "auto",
borderInlineStart: "4px solid blue",
}}
>
<h2>{title}</h2>
{children}
</div>
);
}
Когда установлено dir="rtl", paddingInlineStart применяется к правой стороне, а paddingInlineEnd — к левой, автоматически зеркаля макет. Один и тот же компонент корректно работает как в LTR, так и в RTL контекстах без условной логики.
4. Применяйте логические свойства к контейнерам макета
Используйте логические свойства для общих шаблонов макета, таких как панели навигации и сетки контента.
export default function Navigation() {
return (
<nav
style={{
display: "flex",
gap: "1rem",
paddingInline: "2rem",
borderBlockEnd: "1px solid #ccc",
}}
>
<a href="/" style={{ marginInlineEnd: "auto" }}>
Home
</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
);
}
paddingInline задает отступы с обеих сторон по линии текста, а marginInlineEnd: "auto" сдвигает контент к началу строки, что меняется с левого на правый край при изменении направления. Макет навигации автоматически зеркалится для языков с направлением RTL.
5. Обработка значков и графики, связанной с направлением
Для значков, которые обозначают направление или поток, условно переворачивайте их в зависимости от направления текста.
function BackButton() {
const dir = document.documentElement.dir;
const iconStyle = {
transform: dir === "rtl" ? "scaleX(-1)" : "none",
marginInlineEnd: "0.5rem",
};
return (
<button>
<span style={iconStyle}>←</span>
Назад
</button>
);
}
Этот код переворачивает значок стрелки по горизонтали в режиме RTL, при этом текст и отступы остаются ориентированными на направление с помощью логических свойств. Не все значки требуют переворота — только те, которые указывают направление или движение.