Как поддерживать языки с письмом справа налево (RTL) в TanStack Start v1
Зеркальные макеты для арабского и иврита
Проблема
Большинство веб-макетов предполагают, что текст читается слева направо. Меню навигации закреплены слева, боковые панели тоже слева, а контент читается слева направо. Для таких языков, как арабский и иврит, где чтение идет справа налево, это создает дезориентирующий опыт: визуальный поток противоречит направлению чтения. Пользователи видят навигацию не с той стороны, иконки расположены неудобно, а макеты кажутся перевернутыми. Без правильной поддержки RTL интерфейс становится сложным для навигации и понимания для носителей этих языков.
Решение
Динамически задавайте направление текста документа в зависимости от текущей локали, чтобы браузер автоматически зеркалил макет для RTL-языков. Используйте логические CSS-свойства вместо физических направлений, чтобы отступы, позиционирование и выравнивание автоматически подстраивались под направление текста без дополнительного кода. Такой подход делает макеты независимыми от направления: то, что находится в "начале" контента на английском, окажется в "начале" (справа) на арабском, а браузер сам выполнит преобразование.
Шаги
1. Определите направление текста по локали
Создайте вспомогательную функцию, которая возвращает направление текста для заданной локали, используя встроенный API интернационализации браузера.
export function getTextDirection(locale: string): "ltr" | "rtl" {
try {
const localeObj = new Intl.Locale(locale);
if (
"getTextInfo" in localeObj &&
typeof localeObj.getTextInfo === "function"
) {
return localeObj.getTextInfo().direction;
}
} catch (e) {
console.warn(`Could not determine direction for locale: ${locale}`);
}
const rtlLocales = ["ar", "he", "fa", "ur"];
const lang = locale.split("-")[0];
return rtlLocales.includes(lang) ? "rtl" : "ltr";
}
Эта функция использует Intl.Locale.getTextInfo(), если он доступен, и в противном случае обращается к списку известных RTL-языков. Она возвращает либо 'ltr', либо 'rtl' в зависимости от локали.
2. Установите атрибут dir на элементе html
В вашем корневом маршруте получите текущую локаль и примените соответствующее направление к элементу <html> документа.
import {
createRootRoute,
Outlet,
Scripts,
HeadContent,
} from "@tanstack/react-router";
import { useIntl } from "react-intl";
import { getTextDirection } from "~/utils/text-direction";
export const Route = createRootRoute({
component: RootComponent,
});
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
);
}
function RootDocument({ children }: { children: React.ReactNode }) {
const intl = useIntl();
const dir = getTextDirection(intl.locale);
return (
<html lang={intl.locale} dir={dir}>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}
Атрибут dir на элементе <html> сообщает браузеру, что нужно инвертировать макет для RTL-языков. Flexbox, grid и встроенное содержимое автоматически зеркалируются.
3. Замените физические CSS-свойства на логические
Обновите ваши стили, чтобы использовать логические свойства, которые реагируют на направление текста, а не фиксированные физические направления.
.sidebar {
padding-inline-start: 1rem;
margin-inline-end: 2rem;
border-inline-start: 1px solid #ccc;
}
.icon {
margin-inline-end: 0.5rem;
}
.card {
inset-inline-start: 0;
text-align: start;
}
Логические свойства, такие как padding-inline-start, соответствуют padding-left в LTR и padding-right в RTL. Браузер применяет нужное физическое свойство на основе атрибута dir, поэтому ваши стили работают в обоих направлениях без дублирования.
4. Используйте значения выравнивания, не зависящие от направления
Замените физические ключевые слова выравнивания на логические в вашем CSS и встроенных стилях.
export function Header() {
return (
<header
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<nav style={{ display: "flex", gap: "1rem" }}>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div style={{ textAlign: "end" }}>
<button>Menu</button>
</div>
</header>
);
}
Использование textAlign: 'end' вместо 'right' гарантирует, что текст будет выравниваться по концу направления чтения. Свойства Flexbox, такие как justifyContent и alignItems, автоматически учитывают направление, заданное атрибутом dir.