# General Translation Knowledge Base: Как поддержать RTL-языки (right-to-left) в CSS?
URL: https://generaltranslation.com/ru/kb/right-to-left-css.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Укажите dir="rtl" и lang на элементе html — сам по себе lang не меняет направление письма, — а затем замените физические CSS-свойства вроде margin-left логическими, например margin-inline-start. Flex-строки, grid-столбцы и выравнивание текста по умолчанию зеркалятся автоматически, а translateX(), left и right — нет.
Category: Форматирование с учётом локали
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

Арабский, иврит, персидский и урду читаются справа налево. Как только в документе задано это направление письма, браузер сам разворачивает flex-строки, grid-столбцы и выравнивание текста по умолчанию. Большинство RTL-ошибок возникает из-за CSS, привязанного к физической стороне: `left`, `right`, `margin-left`, `translateX`.

## Задайте направление письма в HTML

Укажите направление письма и язык в корневом элементе:

```html
<html lang="ar" dir="rtl">
```

[Рекомендации W3C по направлению текста](https://www.w3.org/International/questions/qa-html-dir) предписывают задавать базовое направление в разметке, а не через CSS-свойство `direction`, чтобы оно сохранялось, даже если стили не загрузятся; [справочник MDN по `direction`](https://developer.mozilla.org/en-US/docs/Web/CSS/direction) придерживается того же мнения. Атрибут `lang` не задаёт направление письма, даже если содержит подтег письменности, например `ar-Arab`, поэтому нужны оба атрибута. Добавляйте `dir` во вложенные элементы страницы, только если в блоке меняется направление, — например, для английской цитаты внутри арабской статьи.

Чтобы определить `dir` по коду локали в JavaScript, используйте [метод `getTextInfo()` объекта `Intl.Locale`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo) — он возвращает направление письма: `new Intl.Locale('ar').getTextInfo().direction` возвращает `'rtl'`. Метод поддерживается в Chrome 130, Safari 17 и Firefox 153, а в Chrome с 99 по 129 те же данные доступны через свойство `textInfo`, поэтому для старых браузеров предусмотрите резервный вариант.

## Какой подход выбрать для зеркального отражения макета?

Когда направление письма задано, остаётся сделать так, чтобы таблица стилей ему следовала. Для этого есть четыре распространённых способа:

| Подход                                                                                                       | Как отражается макет                                         | Сопровождение                                                   | Основной риск                                                                 |
| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | --------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| CSS `direction: rtl` вместо `dir`                                                                            | Текст и логические свойства отражаются, физические — нет     | Простое                                                         | `:dir()` его игнорирует, а без CSS направление пропадает                      |
| Отдельная RTL-таблица стилей, написанная вручную или сгенерированная с помощью [RTLCSS](https://rtlcss.com/) | Вторая таблица стилей меняет физические стороны местами      | Две таблицы стилей, которые нужно поставлять и синхронизировать | Переопределения расходятся с LTR-стилями по мере их изменения                 |
| Атрибут `dir` плюс логические свойства CSS                                                                   | Каждое свойство разрешается в нужную сторону на основе `dir` | Одна таблица стилей                                             | Физические свойства, которые снова проникают в код при последующих изменениях |
| Логические утилиты Tailwind CSS плюс варианты `rtl:` и `ltr:`                                                | Утилиты компилируются в логические свойства                  | Один набор классов                                              | Варианты применяются там, где хватило бы логической утилиты                   |

Используйте логические свойства (напрямую или через Tailwind) в новом коде и в каждом компоненте, который вы правите: одна таблица стилей обслуживает оба направления. Сгенерированная RTL-таблица стилей подойдёт, если большая устаревшая кодовая база пестрит `left` и `right`, а поддержка RTL нужна раньше, чем вы успеете провести рефакторинг.

## Вёрстка на логических свойствах

[Логические свойства CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) обозначают стороны по направлению текста: inline-start — это левая сторона в LTR и правая в RTL. Они имеют статус Baseline widely available: например, `inset-inline-start` поддерживается всеми браузерами с апреля 2021 года. Вот карточка до и после:

```css
/* До: физические стороны в RTL не меняются */
.card {
  margin-left: 16px;
  padding-right: 24px;
  text-align: left;
  border-left: 4px solid var(--accent);
  border-top-left-radius: 8px;
}
.card .badge {
  position: absolute;
  top: 8px;
  right: 8px;
}

/* После: логические стороны меняются местами в зависимости от dir */
.card {
  margin-inline-start: 16px;
  padding-inline-end: 24px;
  text-align: start;
  border-inline-start: 4px solid var(--accent);
  border-start-start-radius: 8px;
}
.card .badge {
  position: absolute;
  inset-block-start: 8px;
  inset-inline-end: 8px;
}
```

На странице с `dir="rtl"` свойство `margin-inline-start: 16px` превращается в правый внешний отступ 16px, а `border-start-start-radius` скругляет правый верхний угол. Чтобы задать обе стороны по строчной оси сразу, используйте `margin-inline` и `padding-inline`.

### Что отражается автоматически

* **Строки Flexbox:** при `flex-direction: row` первый элемент располагается у правого края.
* **Столбцы Grid:** первый столбец — крайний правый.
* **Выравнивание текста:** начальное значение `text-align` — `start`, поэтому текст без явно заданных стилей выравнивается по правому краю.
* **Встроенный контент и столбцы таблиц:** и то и другое подчиняется базовому направлению письма.

### Что не отражается

* `left` и `right` у позиционированных элементов: `left: 0` остаётся слева.
* `margin-left`, `padding-right`, `border-left`, `float: left` и `text-align: left`.
* `transform: translateX()`: положительное значение всегда сдвигает элемент вправо, поэтому выдвижные панели и карусели движутся не в ту сторону.
* `background-position: left`, `linear-gradient(to right, …)` и горизонтальное смещение в `box-shadow`. У них нет логических аналогов, поэтому переопределяйте их в `:dir(rtl)`.

Для трансформаций меняйте знак с помощью пользовательского свойства:

```css
:root { --inline-sign: 1; }
:root:dir(rtl) { --inline-sign: -1; }

/* Выдвижная панель, которая уходит за свой край inline-start */
.drawer[data-state="closed"] {
  transform: translateX(calc(-100% * var(--inline-sign)));
}
```

### Tailwind CSS

В Tailwind CSS v4.3 логические утилиты — это `ms-*` и `me-*` (внешние отступы), `ps-*` и `pe-*` (внутренние отступы), `inset-s-*` и `inset-e-*`, `text-start`, `border-s-*` и `rounded-s-*`; они перечислены на странице каждой утилиты, например [margin](https://tailwindcss.com/docs/margin). [Варианты `rtl:` и `ltr:`](https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support) компилируются в селекторы с `:dir()`, поэтому `ltr:` срабатывает и на страницах, где `dir="ltr"` вообще не задан. Используйте `ms-3` вместо `ltr:ml-3 rtl:mr-3`, а варианты приберегите для трансформаций и иконок, например `rtl:-scale-x-100`.

## Работа с иконками, шрифтами и текстом со смешанным направлением письма

### Отражайте только значки, указывающие направление

Чтобы отразить значок по горизонтали при RTL:

```css
.icon-directional:dir(rtl) {
  transform: scaleX(-1);
}
```

[Псевдокласс `:dir()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir) учитывает направление письма, унаследованное элементом, а `[dir="rtl"]` срабатывает только для того элемента, у которого задан этот атрибут. `:dir()` поддерживается всеми браузерами с декабря 2023 года и теперь имеет статус Baseline widely available. Если вам нужна поддержка старых браузеров, используйте вместо него `[dir="rtl"] .icon-directional`.

[Рекомендации Apple для языков с направлением right-to-left](https://developer.apple.com/design/human-interface-guidelines/right-to-left) — хороший набор правил и для веба:

* **Отражайте:** стрелки «назад» и «вперёд», кнопки «далее» и «назад», индикаторы прогресса и ползунки, значки со строками текста и значки, изображающие движение вперёд.
* **Не отражайте:** логотипы, универсальные знаки вроде галочки, физические объекты вроде часов, инструменты, нарисованные для правшей, а также фотографии и иллюстрации.
* **Никогда не меняйте порядок цифр:** номер телефона или число 541 читаются одинаково на любом языке.

### Изолируйте двунаправленный текст

Когда RTL- и LTR-текст находятся в одной строке, нейтральные символы — пробелы, дефисы и цифры — могут оказаться не на той стороне; в [руководстве W3C по встроенной bidi-разметке](https://www.w3.org/International/articles/inline-bidi-markup/) это явление называется «перетеканием» (spillover). В [примере `<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi) на MDN при рендеринге арабского имени, за которым следует &quot;- 1st place&quot;, цифра &quot;1&quot; оказывается не на том конце. Оборачивайте каждое значение, направление письма которого отличается от направления окружающего текста:

```html
<!-- Имя из вашей базы данных, направление письма неизвестно -->
<li><bdi>اَلأَعْشَى</bdi> - 1st place</li>

<!-- Значение, которое внутри арабского текста всегда пишется слева направо (LTR) -->
<p>اتصل بنا على <span dir="ltr">+1 (555) 010-2030</span></p>

<!-- Текст, который вводит пользователь, направление письма неизвестно -->
<textarea dir="auto"></textarea>
```

`<bdi>` работает как `dir="auto"`: направление письма определяется по первому сильному символу. Для значений, которые всегда пишутся left-to-right, — номеров телефонов, адресов электронной почты, URL и номеров заказов — используйте `dir="ltr"`. CSS-свойство `unicode-bidi: isolate` обеспечивает такую же изоляцию через таблицу стилей, однако MDN [не рекомендует авторам переопределять `unicode-bidi`](https://developer.mozilla.org/en-US/docs/Web/CSS/unicode-bidi), поэтому лучше задавать `dir` и `<bdi>` в разметке.

### Шрифты

* **Обеспечьте поддержку письменности.** Назначайте шрифт с арабскими или ивритскими глифами в зависимости от языка: `:lang(ar) { font-family: "Noto Sans Arabic", system-ui, sans-serif; }`.
* **Не задавайте межбуквенный интервал для арабского.** Арабские буквы соединяются между собой, а [спецификация CSS Text](https://www.w3.org/TR/css-text-3/) рекомендует избегать `letter-spacing` для письменностей со слитным начертанием, поскольку результат в разных браузерах может отличаться.
* **Учитывайте соседство с заглавными буквами.** В арабском и иврите нет заглавных букв, поэтому латиница в верхнем регистре рядом с ними выглядит крупнее. Apple рекомендует увеличивать размер RTL-шрифта примерно на 2 пункта.

## Типичные ошибки RTL и способы их исправления

### Страница переведена, но текст по-прежнему идёт слева направо

**Причина:** на переведённой странице задан `lang="ar"`, но не задан `dir`, либо `direction: rtl` установлен на внутренней обёртке. **Решение:** задавайте `dir="rtl"` для `<html>` в зависимости от активной локали при каждом рендеринге, включая HTML, отрисованный на сервере.

### Отступ оказывается не с той стороны от иконок и подписей

**Причина:** `margin-left` или `padding-right` между иконкой и её текстом. **Решение:** замените их на `margin-inline-end` или `me-*` из Tailwind либо используйте `gap` во flex-контейнере — у него вообще нет стороны.

Замеры в Chrome 155 на кнопке с inline-flex и иконкой 16px перед подписью показывают: в тексте right-to-left физический отступ не оставляет зазора между иконкой и подписью и уходит своими 8px к внешнему краю, а логический отступ переходит на сторону, обращённую к подписи:

```text title="Output"
.icon { margin-right: 8px }        ltr: 8px gap   rtl: 0px gap
.icon { margin-inline-end: 8px }   ltr: 8px gap   rtl: 8px gap (computed as margin-left)
```

### Бейджи, всплывающие подсказки и раскрывающиеся списки открываются не с того края

**Причина:** `position: absolute` с `left` или `right` либо JavaScript-библиотека позиционирования, которая рассчитывает положение от левого края. **Решение:** используйте `inset-inline-start` и `inset-inline-end`, а также передавайте направление письма в библиотеку позиционирования, если она это поддерживает.

### Выдвижные панели и карусели сдвигаются не в ту сторону

**Причина:** `translateX()` работает с физическими координатами. **Решение:** умножайте значение на знак направления письма, как в примере с выдвижной панелью выше, и проверяйте жесты свайпа в режиме RTL.

### Стрелка «назад» указывает вперёд или логотип отзеркален

**Причина:** иконки отзеркаливаются по принципу «всё или ничего». **Решение:** отзеркаливайте только направленные иконки.

### Знаки препинания или числа перескакивают на другой конец строки

**Причина:** вставленное значение, направление письма которого отличается от направления окружающего текста, например имя пользователя, название продукта или номер телефона. **Решение:** оберните его в `<bdi>` или в элемент с `dir="ltr"`, если заранее известно, что у него направление left-to-right.

### Арабские буквы выглядят несоединёнными

**Причина:** глобальный `letter-spacing`, предназначенный для латинских заголовков. **Решение:** сбросьте `letter-spacing` до 0 для `:lang(ar)` и других письменностей со слитным начертанием.

Чтобы выявить такие ошибки ещё до появления переводов, пройдитесь по приложению в right-to-left [псевдолокали](/kb/pseudo-localization), а перед релизом выполните проверку right-to-left из [чек-листа запуска многоязычного приложения](/kb/multilingual-app-launch-checklist).

## Установка dir на основе активной локали с помощью General Translation

В приложении Next.js с App Router пакет `gt-next` предоставляет функцию [`getLocaleDirection`](/docs/react/nextjs/reference/functions/get-locale-direction) из `gt-next/server`, благодаря чему корневой макет может задавать `lang` и `dir` на сервере. В этом примере предполагается, что корневым макетом является `app/[locale]/layout.tsx`: по требованиям Next.js он [должен рендерить и `<html>`, и `<body>`](https://nextjs.org/docs/app/api-reference/file-conventions/layout#root-layout):

```tsx title="app/[locale]/layout.tsx"
import { getLocale, getLocaleDirection } from 'gt-next/server';
import type { ReactNode } from 'react';

export default async function LocaleLayout({ children }: { children: ReactNode }) {
  const [locale, direction] = await Promise.all([
    getLocale(),
    getLocaleDirection(),
  ]);
  return (
    <html lang={locale} dir={direction}>
      <body>{children}</body>
    </html>
  );
}
```

В компонентах `gt-react`, `gt-next` и `gt-react-native` хук [`useLocaleDirection`](/docs/react/reference/hooks/use-locale-direction) возвращает `'ltr'` или `'rtl'` для активной локали. В вебе присвойте это значение `document.documentElement.dir` в любом компоненте вашего дерева. В React Native нет `document`: нативное направление макета определяется через [`I18nManager`](https://reactnative.dev/docs/i18nmanager) из React Native, а изменения через `allowRTL` или `forceRTL` вступают в силу только при следующем запуске приложения. Для других стеков, включая Vue, `gt-tanstack-start` и серверные шаблоны, подойдёт функция [`getLocaleDirection`](/docs/platform/core/reference/utility-functions/locales/get-locale-direction) из основной библиотеки `generaltranslation`: она работает локально, не требует ключа API и возвращает `'rtl'` для таких кодов, как `ar-SA`, `he-IL` и `fa`. В любом случае в CSS всё равно понадобятся логические свойства и правила для иконок, описанные выше.

## Ключевые выводы

* Указывайте `lang` и `dir="rtl"` в `<html>`: сам по себе `lang` не меняет направление письма, а задавать его через CSS-свойство `direction` не следует.
* Flex-строки, столбцы Grid и выравнивание текста по умолчанию отражаются автоматически, а `left`, `right`, `margin-left`, `translateX()`, `background-position` и смещения теней — нет.
* Заменяйте физические свойства логическими (`margin-inline-start`, `inset-inline-end`, `text-align: start`, `border-start-start-radius`) или классами Tailwind `ms-*`, `ps-*` и `inset-s-*`.
* Отражайте стрелки и индикаторы прогресса с помощью `:dir(rtl)`, но никогда не отражайте логотипы, галочки и часы; значения со смешанным направлением письма оборачивайте в `<bdi>` или `dir="ltr"`.
* В SDK General Translation задавайте `dir` с помощью [`getLocaleDirection`](/docs/react/nextjs/reference/functions/get-locale-direction) (в серверном коде Next.js или в основной библиотеке) или `useLocaleDirection` (в React).

## Related questions

- [Чек-лист тестирования локализации для многоязычных приложений](https://generaltranslation.com/ru/kb/multilingual-app-launch-checklist.mdx): Проверьте девять аспектов в каждой поддерживаемой локали на сборке для рабочей среды: пользовательские сценарии целиком, включая состояния ошибок и письма; переключение языка; формы множественного числа; валюту и часовые пояса; увеличение длины текста; вёрстку для языков right-to-left; доступность; доставку переводов; индексируемые языковые URL. Задайте для каждой проверки явный критерий успешного прохождения.
- [Что такое псевдолокализация?](https://generaltranslation.com/ru/kb/pseudo-localization.mdx): Псевдолокализация — это тест, при котором исходный текст заменяется изменённой версией с диакритикой, удлинённой примерно на 40% и заключённой в скобки. Так жёстко закодированные строки, усечение, проблемы со шрифтами и склеенные из фрагментов предложения выявляются ещё до появления перевода. В Android для этого предусмотрены псевдолокали en-XA и ar-XB.
- [Как работать с формами множественного числа в React?](https://generaltranslation.com/ru/kb/how-to-handle-plurals-in-react.mdx): Чтобы работать с формами множественного числа в React, задайте для каждого языка по одному предложению на каждую форму множественного числа и выбирайте нужное по правилам множественного числа этого языка — ведь в английском 2 формы, в русском 4, а в арабском 6. Компонент `<Plural>` от General Translation в gt-react и gt-next принимает формы на исходном языке, а General Translation генерирует формы, нужные каждому языку перевода.
- [Можно ли делать i18n без файлов перевода?](https://generaltranslation.com/ru/kb/i18n-without-translation-files.mdx): Да. Библиотеки gt-react и gt-next от General Translation обеспечивают i18n для React и Next.js без ключей перевода и без отдельного JSON-файла для каждого языка, который приходится поддерживать. Тексты остаются встроенными в JSX внутри компонента `<T>`, а команда gt translate генерирует переводы для каждого языка во время сборки, учитывая контекст из вашего кода. Locadex может поддерживать их в актуальном состоянии через пул-реквесты.

## Previous and next questions

- Previous question: [Как перевести субтитры SRT, не нарушив тайминг?](https://generaltranslation.com/ru/kb/translate-srt-subtitles.mdx)
- Next question: [Как сделать сайт многоязычным?](https://generaltranslation.com/ru/kb/make-website-multilingual.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/ru/kb/sitemap.md) for every article.
