Перевод «RTL Styling 101 — An extensive guide on how to style for RTL in CSS» Ахмада Шадида.

Более 292 миллионов людей во всём мире говорят на арабском, как на родном языке. К ним отношусь и я, поэтому иногда разрабатываю сайты, которые должны поддерживать оба направления написания текста: слева направо (LTR – Left To Right) и справа налево (Right To Left).

Введение в RTL-стилизацию


В CSS по умолчанию используется направление LTR. Если вы проверите стили браузера для элемента html, увидите, что для свойства dir (или «direction») значение ltr является значением по умолчанию. Ниже приведён базовый пример для демонстрации различия между LTR и RTL разметкой.



Обратите внимание на блок RTL – в отличие от LTR, текст в нём читается справа налево. В этом простом примере браузер отобразил его правильно. Для смены направления языка документа, необходимо к корневому элементу добавить атрибут dir.

<html dir="rtl">...</html>

Когда значение dir меняется, следом автоматически должны переключаться все вложенные элементы: заголовки, абзацы, ссылки, изображения и формы.

Стоит также упомянуть, что атрибуту dir можно задать значение «auto», которое автоматически будет меняет направление для элемента на основании анализа его содержимого. Согласно HTML-спецификации:
Авторы призывают использовать это значение только в крайнем случае, когда направление текста действительно неизвестно и нет возможности эффективно определить это на стороне сервера.


В дополнение к установке для элемента HTML атрибута dir=rtl, мы можем также добавить direction: rtl как CSS-стиль.

.element { direction: rtl; }

Тем не менее, CSSWG (рабочая группа CSS) рекомендует определять направление именно в виде атрибута для корневого элемента HTML, чтобы обеспечить корректную разметку, независимо от того, присутствует и сработал ли CSS.

Пример изменения направления разметки


Давайте рассмотрим более детальный пример, чтобы лучше понять, как менять направление разметки с LTR на RTL.



<article class="media">
  <img class="media__photo" src="blueberry-cheesecake.jpg" alt="">
  <div class="media__content">
    <h2>Blueberry Cheesecake</h2>
    <p>...</p>
    <p><a href="#" class="link">View Recipe</a></p>
  </div>
</article>

Изначально я использовал старый добрый float, чтобы выровнять изображение по левому краю в LTR разметке и, конечно же, применил clearfix.

.media:after {
  content: "";
  display: block;
  clear: both;
}
 
.media img {
  float: left;
  width: 200px;
  margin-right: 16px;
}

Затем мы добавляем dir=«rtl» для элемента, содержащего арабский текст. Получается следующий результат:



Все элементы выровнялись по правую сторону, кроме изображения. Это произошло из-за того, что для изображения задано свойство float: left и margin-right: 16px. Чтобы исправить это, нужно переопределить следующие стили

.media[dir="rtl"] img {
  float: right;
  margin-right: 0;
  margin-left: 16px;
}


Английский и арабский контент в LTR макете


Что произойдёт, если некоторый текст будет иметь смесь английских и арабских слов, а разметка – направление LTR? Результат получится странным



Браузер будет показывать заголовок неправильно. Говорящих на арабском языке это будет сбивать с толку. Слова данного заголовка должны находиться в порядке, указанном на изображении ниже. Только начиная справа.



Чтобы избежать этой проблемы, устанавливайте соответствующее направление языка всякий раз, когда это возможно. Если элементу установить атрибут dir=«rtl», он отобразится так, как ожидается.




Ситуация может еще более усложниться, если заголовок длинный. Ниже я немного дополнил его и результат оказался неожиданным. Я указал цифрами правильную последовательность. Снова-таки, начиная справа.



Когда элементу задан атрибут dir=«rtl», заголовок становится корректным. То есть, предложение выглядит грамматически правильным, а слова расположены в правильной последовательности.




Flexbox


Flexbox в основе своей работы учитывает режим написания, установленный в документе. Этот фактор используется для определения того, каким образом блоки будут выстраиваться на странице по умолчанию. Например, на англоязычном сайте они будут идти слева направо, а на китайском – сверху вниз. Для английского и арабского языков для свойства writing-mode значением по умолчанию служит horizontal-tb.

Согласно Mozilla Developer Network (MDN), значение horizontal-tb значит следующее:

Содержимое располагается горизонтально слева направо, вертикально сверху вниз. Последующая горизонтальная линия находится ниже предыдущей.

Когда направление страницы меняется на RTL, flexbox соответствующим образом разворачивает поток своих элементов. Это огромное преимущество! На изображении ниже показано, как ось flexbox разворачивается, в зависимости от значения свойства direction.



В приведённом ниже примере я создал три элемента и пронумеровал их, чтобы продемонстрировать разницу при изменении направления страницы.

<div class="element">
  <div class="item"^_^gtlt^_^/div>
  <div class="item"^_^gtlt^_^/div>
  <div class="item"^_^gtlt^_^/div>
</div>

.element {
  display: flex;
  flex-direction: row; /* Default value, added for clarity */
}





CSS Grid


Как и flexbox, css grid зависит от режима написания документа, что даёт нам те же преимущества, что и при использовании flexbox.

В приведённом ниже примере для направления LTR блок sidebar должен быть слева, а main справа. Для направления RTL – наоборот. Когда мы используем CSS Grid, это перестраивание будет производиться автоматически в соответствии с установленным на данной странице направлением.

<div class="element">
  <div class="side">Side</div>
  <div class="main">Main</div>
</div>

.element {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-gap: 1rem;
}


Основные ошибки при переключении на RTL направление


Разработчики, не владеющие арабским, допускают распространённые ошибки, которые сразу бросаются в глаза носителям языка.

1. Межбуквенный интервал (letter-spacing)


В английском языке распространено использование свойства letter-spacing для регулирования межбуквенного интервала внутри слова. Рассмотрим следующий пример с содержимым на английском. Выглядит вполне нормально.



Однако, если такой же межбуквенный интервал добавить к тексту на арабском языке, это будет выглядеть очень странно. Рассмотрим следующий пример из реальной жизни.



Обратите внимание, что в тексте, к которому применено свойство letter-spacing буквы каждого слова отделены друг от друга. Это неправильно. Арабские буквы должны выглядеть связанными, а сохранение такого же межбуквенного интервала, как и в английском, препятствует этому. Убедитесь, что при работе с многоязычным сайтом не забыли в нужном месте установить свойство letter-spacing: 0.



2. Прозрачность текста


В дизайне сайтов текст нередко делают полупрозрачным. Например, чтобы отметить его второстепенную значимость. Это работает для английского. Однако, если содержимое на арабском, может возникнуть проблема из-за странного способа рендеринга текста.



Появляются места, в которых цвет начертания символов меняется из-за их наложения. В этом примере свойство letter-spacing не было установлено, поэтому на проблему оно не влияет. Решение проблемы – задавать цвета без полупрозрачности (которая обычно выставляется через RGBa или opacity).


3. Различия размера слов в разных языках


Иногда, когда сайт переводится на арабский язык, размер элементов меняется из-за того, что некоторые слова после перевода становятся больше или меньше. Рассмотрим следующий пример, в котором я сымитировал навигацию сайта Smashing Magazine.



В арабской версии некоторые из слов имеют почти такой же размер, как их английские версии, некоторые равны в точности, а некоторые – больше. Чтобы было понятнее, вот сравнение каждого слова и его арабского перевода.



Вы можете удивиться, почему я говорю о различии размера слов в разных языках, поскольку это нормально и ожидаемо. Рассмотрим следующий реальный пример с LinkedIn.



Кнопка «Done» переведена на арабский как “??”, из-за чего становится достаточно маленькой и вообще выглядит странно. Специально для таких случаев, было бы лучше задать для кнопки свойство min-width. Я добавил его через панель разработчика, чтобы показать, как это должно выглядеть:



А вот очень похожий пример из Twitter:



Обратите внимание, что указанные выше проблемы у LinkedIn и Twitter были обнаружены на момент написания статьи (13 декабря 2019 года).

4. Усечение текста


Однажды я работал над многоязычным проектом и столкнулся с проблемой, относящейся к усечению текста в неправильном направлении. Рассмотрим следующий пример.



Усечение для английского текста неправильное. Это должно происходить в конце элемента, а не в его начале. Чтобы решить эту проблему, необходимо задать для этого элемента атрибут dir=«auto», и тогда браузер автоматически проанализирует содержимое и примет решение, какое значение dir применять.

<p dir="auto">????? ?????? ??? ?? ?????? ???? ????? ?? ????? ????? ????? ???? ???????</p>
<p dir="auto">Welcome to the article that explains how to design for RTL pages.</p>





5. Выбор плохого RTL шрифта


Наличие RTL версии сайта не значит, что вы можете просто ограничиться указанием системного шрифта, установленного у пользователя по умолчанию. Нужно серьёзно отнестись к этому, чтобы обеспечить хорошую читаемость. Примером выбора неудачного шрифта является Twitter:



С точки зрения арабоговорящих, слово “?????” трудно читать по нескольким причинам:
  • не очень хороший шрифт
  • жирность ухудшает удобочитаемость
  • точки слова очень маленькие и слишком близко расположены к буквам


Я привёл несколько примеров, которые выглядят гораздо понятнее:



6. Смешивание хинди и арабских цифр


В арабском языке есть два варианта записи чисел:
  • Хинди: ? ? ? ? ? ? ? ? ?
  • Арабский: 0 1 2 3 4 5 6 7 8 9

Числа, используемые в английском, унаследованы от арабских: “0, 1, 2, 3, 4, 5, 6, 7, 8, 9”. Текст, содержащий числа, должен соответствовать только одному из двух вариантов: либо хинди, либо арабскому.

Согласно Википедии:
Причина, по которой в Европе и Америке цифры называют «арабскими» в том, что они были введены в Европе в 10 веке арабоговорящими выходцами из Северной Африки.

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



Моменты, которые могут не работать при RTL


1. Высота строки (line-height)


Обычно для RTL разметки устанавливается отдельный шрифт. В этом случае проверяйте, как содержимое выглядит на одной и нескольких строках. В следующем примере пространства между линиями для арабского текста меньше, чем для английского, даже при том, что они имеют одинаковое значение свойства line-height.



Важно учитывать это и задать правильное значение для line-height для арабского текста.



В Twitter, например, есть кнопка, с обрезанным содержимым. Как раз, из-за неправильно указанного значения line-height.



Обратите внимание, что на первом изображении арабский диакритик обрезан. Он называется «касра» и является очень важным для правильного прочтения этого слова. На соседнем изображении я исправил высоту строки и теперь символы отображаются полностью, не обрезаясь.

2. Подчёркнутые ссылки


С арабскими словами стандартное подчеркивание ссылок из CSS выглядит плохо. Это связано с особенностями написания слов и букв в арабском языке и продемонстрировано на следующем изображении:



Я отметил проблемные области красным кругом. Подчёркивание перекрывает точки букв. Вот крупный план:



Точки, отмеченные красным кругом, перекрываются линией подчеркивания, что усложняет чтение. Решением является настройка кастомного подчёркивания с помощью CSS.

2.1. Оформление текста (text-decoration)


Существует возможность изменить стиль и цвет подчеркивания с помощью новых свойств text-decoration-style и text-decoration-color. Однако, нет гарантии, что это будет корректно работать со всеми семействами и размерами шрифтов. На момент написания статьи, Firefox является браузером с наилучшей поддержкой этих свойств.

.link-2 {
  text-decoration-color: rgba(21, 132, 196, 0.2);
  text-decoration-style: normal;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
}



2.2 Тень (box-shadow)


Браузерная поддержка свойства box-shadow гораздо лучше, чем у text-decoration. Можно проверить наличие поддержки одного из новых свойств text-decoration, и если браузер не поддерживает его, применить фолбэк с помощью свойства box-shadow.

.link-3 {
  color: #000;
  text-decoration-color: rgba(21, 132, 196, 0.2);
  text-decoration-style: normal;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
  box-shadow: inset 0 -5px 0 0 rgba(#1584c4, 0.2);
}
 
@supports (text-decoration-color: red){
  .link-3 {
    box-shadow: none;
  }
}


3. Перенос строки


Если вы используете свойство word-break, нужно проверять корректность его работы. Посмотрите на следующий пример:



Обведённые области – это разорванные переносом на новую строку арабские слова из-за применения word-break. А в арабском языке перенос слов не используется, потому что буквы каждого слова должны быть соединены друг с другом.

4. Сокращения


В английском языке нередко используют сокращения например, для дней недели. Таким образом, «Saturday» становится «Sat».



В арабском это невозможно в принципе, так как буквы слова должны быть связаны.



Двунаправленные значки


Симметричные значки не нуждаются в переворачивании при переходе между LTR и RTL макетами. Вот несколько примеров:



В то же время, для некоторых значков важно изменить направление в RTL версии сайта, чтобы они были правильно поняты пользователем.



Переворачивание элементов


Работая над некоторыми компонентами, мне нужен способ быстро перевернуть их. В Sketch я скопирую компонент и переверну его соответствующей командой. Такой же функционал доступен в Adobe XD и Figma



Чтобы увидеть, что я имею в виду, вот анимация, демонстрирующая, что я делаю после переворачивания компонента.



Особенности дизайна для RTL


В этом разделе я рассмотрю наиболее распространённые компоненты и покажу, как они должны выглядеть в режиме RTL.

Иконки кнопок


Встречаются ситуации, когда кнопка имеет значок, который открывает меню дополнительных действий. В этом случае сторона расположения иконки должна меняться на противоположную в RTL макете.



Поля ввода


Некоторые поля ввода форм должны оставаться выровненными по левую сторону даже в RTL режиме. Например, поля ввода email или номера телефона.



Хлебные крошки


Стрелочки между разделами в хлебных крошках также должны быть повёрнуты.



Заголовок страницы


Компонент заголовка страницы содержит начальную и конечную секции. Обе они должны быть повёрнуты в RTL



Таблицы


Таблицы также должны быть повёрнуты



Закладки


Если в закладках в LTR режиме иконка находится слева от названия, в RTL этот компонент нужно развернуть.



Карточка


Для горизонтальной карточки изображение и текст нужно менять местами в RTL



Всплывающие уведомления


Как вы и могли ожидать, иконки «закрыть» и «предупреждение» следует поменять местами



Логические свойства CSS


Согласно MDN:
Логические свойства и значения CSS – это модуль, представляющий логические свойства и значения, которые обеспечивают способность управлять макетом с помощью логических, а не физических направлений и измерений.

Давайте возьмем простой пример. Предположим, нам нужно выровнять строку текста по правому краю. Добавляем следующее:
.page-header {
  text-align: right;
}

А для RTL:
[dir="rtl"] .nav-item {
  text-align: left;
}

Что если бы был способ добавить только одно значение для свойства text-align, которое изменяло бы сторону выравнивания, учитывая направление страницы? На выручку приходят логические свойства CSS!
.page-header {
  text-align: end;
}


При его использовании, сторона, задаваемая свойством text-align, будет основываться на направлении страницы. Демо

Чтобы было легче увидеть разницу между start и end, я подготовил схему ниже. Значение start равнозначно значению left в LRT и значению right в RTL. То же с end, только наоборот.



Теперь, когда вы получили общее представление о том, как это работает, давайте рассмотрим дополнительные примеры и способы применения логических свойств CSS.

Логический Padding




Предположим, у нас есть поле ввода с иконкой поиска справа. Мы должны задать padding с обеих сторон. Padding справа будет немного больше, чтобы избежать наложения иконки на текст.

.input--search {  
  padding-inline-start: 1rem;
  padding-inline-end: 2.5rem;
}

Логический Margin



Margin справа от иконки должен быть логическим, поэтому мы используем для этого margin-inline-end.
.page-header__avatar {  
  margin-inline-end: 1rem;
}

Логические рамки (border)



Часто рамку используют для обозначения активного элемента навигации. На примере выше задана рамка с левой стороны каждого элемента. Как это реализовать с помощью логических свойств?

.nav__item {  
  border-inline-start: 3px solid transparent;
}
 
.nav__item.is-active {
  border-color: #1e9ada;
}

Логическое скругление углов (border-radius)




На изображении выше фон элемента навигации имеет скругление только для правого верхнего и левого углов. Это можно реализовать с помощью логических свойств:

.nav__item {  
  border-start-end-radius: 30px;
  border-end-end-radius: 30px;
  background-color: transparent;
}
 
.nav__item.is-active {
  background-color: #ecf6fb;
}


Шпаргалка логических свойств


Если возникают сомнения насчёт выбора логического аналога свойства, определяющего сторону, можете подглядывать в шпаргалку ниже. Обратите внимание, что в ней приведены только те логические свойства, которые могут быть полезны при работе с LTR и RTL режимами. Я составил эту шпаргалку на основании замечательной статьи Адриана Роселли.

https://codepen.io/shadeed/pen/2981e62691e67452d9f282a5351d7c79

Кроме того, Адриан создал демо, которое облегчает понимание разницы между логическими и направленными свойствами.



Браузерная поддержка


Поддержка браузерами довольно хорошая для свойств padding, margin и text-align. Однако, еще недостаточная для свойств border-radius. Ниже приведена таблица поддержки с сайта Can I Use:




Даже при том, что поддержка еще не идеальная (и никогда таковой не будет), советую уже сейчас начинать использовать логические свойства с фолбэками. Например,

.input--search {  
  padding-left: 1rem;
  padding-right: 2.5rem;
  padding-inline-start: 1rem;
  padding-inline-end: 2.5rem;
}

Также, вы можете использовать плагин PostCSS Logical, который добавляет фолбэк для каждого используемого логического свойства.

Соглашения об именовании в CSS


Избегайте давать CSS-классам имена, которые привязаны к их элементам. Используйте имена, которые могут быть извлечены в повторно используемые компоненты. Рассмотрим следующий пример:
<div class="c-section">
  <p><a href="#" class="see-link">See more</a></p>
</div>
 
<div class="c-section">
  <p><a href="#" class="see-link">Learn more</a></p>
</div>


В обоих блоках ссылки одинаковые, но их названия разные. Во втором блоке класс see-link не имеет смысла. Хорошим именем может быть c-link. Приставка c– подставляется для компонентов, этот способ я перенял из ITCSS фреймворка.

Теперь, когда вы поняли идею, мы также можем применить это к RTL-стилизации. В примере ниже представлена секция с двумя дочерними элементами



Вместо того, чтобы давать элементам имена типа .c-page-header__left и .c-page-header__right, я называю их .c-page-header__start и .c-page-header__end. Это больше ориентировано на будущее и не предполагает, что сайт создаётся только в LTR или RTL режиме.

Инструменты автоматизации


Существуют отличные инструменты, облегчающие работу с LTR и RTL разметкой.

1. Bi-App-Sass


Bi-App-Sass от Анаса Накава позволяют писать одну версию стилей, которая компилируются в две разные таблицы стилей: одна для LRT, другая для RTL разметки.

Этот инструмент может быть полезен для больших проектов. В результате может быть множество таблиц стилей для каждого направления написания. Рассмотрим следующий пример:
.elem {
  display: flex;
  @include margin-left(10px);
  @include border-right(2px solid #000);
}


Итоговый код CSS может быть следующим.

Файл app-ltr.css:
.elem {
  display: flex;
  margin-left: 10px;
  border-right: 2px solid #000;
}


Файл app-rtl.css:
.elem {
  display: flex;
  margin-right: 10px;
  border-left: 2px solid #000;
}


Заметьте, однако, что последний коммит в репозиторий на GitHub был четыре года назад (Ноябрь 2015).

2. RTLCSS


RTLCSS от Мохамеда Йонеса это фреймворк для конвертирования LTR таблиц стилей в RTL.

Отличие этого инструмента в том, что он применяется к уже собранной версии CSS-файла. Например, если у вас в проекте 50+ Sass-компонентов, RTLCSS пригодится для парсинга скомпилированного CSS-файла и создания из него RTL версии.

Практические примеры


Шапка сайта


Я специально сделал макет, чтобы показать, какой использую подход для реализации RTL-версии разметки.



Давайте начнём с компонентов шапки. Чтобы правильно реализовать это в коде, я схематично изобразил его общую структуру. Обратите внимание, что я разделил шапку на основную секцию и подсекцию. Также, добавил начальный и конечный классы для дочерних элементов.



.header__main,
.header__sub {
    display: flex;
    justify-content: space-between;
}


Поскольку работа CSS Flexbox учитывает направление, заданное на странице, как объяснялось раньше в этом руководстве, он автоматически развернётся при RTL-разметке.



Следующий момент – это разделительная линия между логотипом и навигацией. Сначала я думал использовать border-right. Это работает, но не идеально. Лучше использовать псевдоэлемент, поскольку он развернётся вслед за направлением страницы.



.c-brand:after {
  content: "";
  display: inline-block;
  vertical-align: middle;
  width: 3px;
  height: 38px;
  border-radius: 5px;
  background: #e4e4e4;
  margin-inline-start: 1.25rem;
}


Вот текущий результат:



Далее я буду работать с компонентом разделов (тем, который находится в подсекции шапки и содержит названия и счетчики). Ниже представлен макет того, как этот компонент должен выглядеть в LTR.



На первый взгляд это может показаться простым, но на самом деле требует указания нескольких свойств padding и margin. Вот макет, демонстрирующий это:



.topics-heading {
  margin-inline-end: 1.5rem;
}
 
.topics-list {
  margin-inline-end: 1rem;
}
 
.c-topic {
  padding-inline-start: 0.5rem;
}
 
.c-topic:not(:last-child) {
  margin-inline-end: 10px;
}
 
.c-topic__counter {
  margin-inline-start: 1rem;
}


Как вы можете видеть, я использую логические свойства CSS вместо значений left и right.

Следующим шагом является ссылка «See All». Обратите внимание на стрелку в конце. У неё должно быть следующее поведение:
  • При наведении цвет стрелки должен меняться
  • При наведении, к стрелке должна применяться анимация

Я решил использовать встроенный SVG для этой задачи.Когда задал стрелке смещение translate, задумался об RTL. Не существует логических свойств, подходящих для этой ситуации и мне нужно рассмотреть другое решение. Подошел вариант с изменением margin.

.c-link svg {
  margin-inline-start: 4px;
  transition: 0.15s ease-in;
}
 
.c-link:hover svg {
  margin-inline-start: 8px;
}

Хотя анимирование margins не очень хорошо сказывается на производительности, всё же это работает. Другим решением является определение направления страницы и указания, на основании этого, соответствующего направления для смещения
.c-link:hover svg {
  transform: translateX(6px);
}
 
/* I’m using dir=rtl in the header for the purpose of clarity. It should be added to the root element. */
.c-header[dir="rtl"] .c-link svg {
  transform: scaleX(-1);
}
 
.c-header[dir="rtl"] .c-link:hover svg {
  transform: scaleX(-1) translateX(6px);
}


Обратите внимание, что для RTL направления страницы я добавил scaleX(-1), чтобы развернуть иконку по горизонтали. Также можно использовать rotate(180deg), но вариант с масштабированием мне кажется более простым.



Дальше идёт поле поиска. Требования следующие:
  • Иконка поиска должна появляться в конце поля
  • Расположение иконки поиска должно быть динамическим

.c-input--search {
  background-image: url("data:image/svg+xml...");
  background-position: right 6px center;
}
 
.c-header[dir="rtl"] .c-input--search {
  /* We replace the original icon with a flipped one. */
  background-image: url("data:image/svg+xml...");
  background-position: right 6px center;
}

Также, когда пользователь печатает текст в этом поле, текст не должен накладываться на иконку. Чтобы избежать этого, добавим padding с обеих сторон поля.



В результате получается следующая разметка для LTR и RTL режимов:



Следующим идёт мобильное меню. Для его обозначения я буду использовать гамбургер-иконку. Расположение иконки будет меняться в зависимости от направления (LTR или RTL). То же касается и направления анимации смещения.



Ознакомиться с демо можно на CodePen

Благодарность


Особая благодарность моей жене Kholoud за её постоянную поддержку и множественное вычитывание руководства. Также выражаю благодарность Adebiyi Adedotun Lukman и Sime Vidas за их поразительный отзыв.