Недавно мне довелось работать над сложным веб-приложением, клиент которого нужно было интернационализировать (i18n
), а локализации OOTB (l10n
) включали языки обоих направлений — LTR и RTL. Кроме того, было решено, что локализация будет происходить на месте, в любом состоянии приложение может быть в любой момент, без перезагрузки всего сайта для каждого языка.
В целом все работало довольно хорошо, пока я не столкнулся с конкретной проблемой, которую я коснусь позже в этой статье. Просматривая Интернет в поисках элегантного решения проблемы, я был удивлен, увидев все еще много неуверенности в правильности такого многоязычного пользовательского интерфейса. В какой-то момент я наткнулся на специальную библиотеку (позвольте мне не упоминать ее здесь), которая выполняет некоторую постобработку CSS
на этапе сборки. Мне это показалось настолько неправильным, что я решил написать об этом несколько слов.
Сформулируем наиболее очевидную и действительно единственно правильную отправную точку: язык и направленность должны быть определены через атрибуты lang
и dir
в элементе html (ссылки на документацию MDN приведены ).
<html lang="he" dir="rtl"> ... </html>
Направленность — главная тема здесь, я буду придерживаться ее. Эффект dir
очевиден — он определяет первичный документооборот. Изменение значения этого атрибута (rtl
или ltr
, значение auto
вне этого диапазона) в живом документе вызывает немедленную перерисовку сайта.
Существует еще одно средство для обработки направления содержимого, свойство direction
элемента CSS
. Несмотря на то, что это может привести к, казалось бы, такому же эффекту, абсолютно рекомендуется выражать направление сайта через атрибут разметки dir
, который имеет гораздо более глубокое влияние на интерпретацию содержимого сайта UA (см. здесь очень сильную формулировку об этом от W3C). рекомендация).
Не
Как правило, следует избегать использования «жестко закодированных» выравниваний. Я практически НЕ использую свойства CSS
, где появляются слова left
/right
.
Все приведенные ниже CSS
примеры не i18n
подходят:
left: 15px; ... text-align: right; ... float: left;
Применение атрибута dir
немедленно влияет на поведение выравнивания текста всех элементов блока «под» ним. Это должен быть редкий, исключительный случай, когда нужно было бы применять left
/right
штучек напрямую.
Do
В первую очередь всегда следует использовать средства позиционирования, не зависящие от направления (также известные как логические), и, следовательно, i18n
дружественные:
align-self: flex-start; ... justify-content: flex-end; ... text-align: start;
Один из известных случаев, когда используются эти частицы left
/right
, — это корректировка интервала/плотности. Например, вы хотели бы сделать отступ текста на 24 пикселя от края. Обычно для этого используют margin
, а здесь в наше CSS
вступает «зло» margin-left
. Не !!!
Все соответствующие браузеры поддерживают определенные интервалы block
/ inline
.
margin-inline-start
— установит отступ слева в потоке LTR и отступ справа в потоке RTL, легко!
Очень хорошее введение, справочник по спецификациям и источник примеров можно найти в этой статье MDN: Логические свойства CSS — основные понятия.
Простой учебный пример
Давайте возьмем этот пример использования, чтобы подчеркнуть суть: модальное всплывающее окно должно иметь эту причудливую кнопку закрытия в верхнем углу. Обычно он находится в дальнем углу, верно? Бьюсь об заклад, что на подавляющем большинстве сайтов мы увидим стилизацию, подобную этой:
position: absolute; top: -1.2em; right: -1.2em;
Когда придет время локализовать сайт на rtl
, что будет? Другой стиль, основанный на некотором совпадении направления текущего документа, будет применен, заменив свойство right
на left
. Не делайте этого. Лучшим способом было бы иметь контейнер кнопок полной ширины со следующим стилем:
display: flex; justify-content: flex-end;
Помимо готовности ltr
/rtl
с самого начала, этот дизайн разметки готов к добавлению следующей кнопки, теперь мы можем масштабировать изменения.
В какой-то момент должна быть жестко задана направленность, что делать?
Признаюсь, бывают редкие случаи (опять же редкие :)), когда разные направления приходится обрабатывать вручную и простой ltr
/rtl
поток не поможет. Хорошим примером могут быть изображения с семантикой направления/указания, которые должны быть перевернуты, например, когда rtl
не повреждено.
Было бы абсолютно лучшим способом справиться с этим следующим образом:
.point-image:dir(rtl) { transform: rotateY(180deg); }
:dir()
матчер - гениальная вещь! Он работает на фактической рассчитанной направленности. Его можно применить к элементу self, избегая необходимости карабкаться по дереву предков, и поэтому он работает как шарм в ShadowDOM
!
Жаль, что :dir()
сопоставитель пока работает только в FireFox (2020/02). Вы можете отслеживать эту ошибку для разрешения Chromium и эту для разрешения WebKit.
Обновление от 05.03.2021: ошибка Chromium помечена как ИСПРАВЛЕНАЯ!
Между тем, неизбежно добавить (да, я предпочитаю уже добавить их оба) еще одно правило сопоставления, что-то вроде:
html[dir=rtl] .point-image { transform: rotateY(180deg); }
На самом деле не такая уж большая проблема, я бы сказал, иметь несколько правил CSS
для наших особых редких случаев… если бы мы не использовали shadowDOM
, но ИМХО мы должны используйте его столько раз, сколько необходимо для достижения действительно компонентного дизайна со всеми его достоинствами. На самом деле то сложное веб-приложение, о котором я упоминал в начале, полностью построено из веб-компонентов, пользовательских элементов, использующих shadowRoot
. Как эта проблема направленности сыграла там роль?
Направленность в ShadowDOM
Эта часть самая темная 😏 точка этой истории. Решение проблемы основано на этом SO-вопросе. Но давайте сначала сформулируем проблему: нам нужно оформить этот .point-image
в соответствии с потоком документов ltr
/rtl
, это нужно сделать в shadowRoot
.
Первоначальный, еще достаточно чистый и чисто CSS-подход был таким:
:host-context([dir=rtl]) .point-image { transform: rotateY(180deg); }
Верно, :host-context
не работает в FireFox, но эй, отличные новости, FireFox и Chromium поддерживают :dir()
, который, как мы уже говорили, прекрасно работает в тени. Плохая новость заключалась в том, что ни один из них не работает для WebKit, который даже активно возражает против реализации :host-context
.
Короче говоря, единственным широким и в то же время единообразным решением этой проблемы было использование JS
, который я считаю здесь анти-шаблоном, и компромисс в отношении следующего:
- любой компонент, чувствительный к направленности, добавляет к себе некоторый класс, чтобы сигнализировать о направленности в соответствии с текущим значением атрибута
dir
HTML при инициации (connectedCallback
— самая ранняя возможная точка для этого) - устанавливает
MutationObserver
прослушивание изменений атрибутаdir
для обновления этого класса в течение всего срока службы - стилизует любые соответствующие дочерние элементы в его
shadowDOM
следующим образом (давайте предположим, что мы используем класс ‘rtl’, чтобы сигнализировать о направленностиrtl
):
:host(.rtl) .point-image { transform: rotateY(180deg); }
Чтобы обернуть это в код, ниже приведен несколько упрощенный пример того, как может выглядеть компонент, чувствительный к направленности:
Сводка
- основным и, вероятно, единственным средством управления направленностью веб-сайта должен быть атрибут
dir
(предположительно, элементhtml
) - если/когда возникает какая-то редкая необходимость стилизовать что-то в зависимости от направленности страницы,
html[dir=rtl]
-подобный сопоставитель обычно покрывает это; как только псевдокласс:dir(rtl)
станет широко поддерживаться, мы перейдем на него, так как он имеет преимущества работы над расчетной направленностью, самоприменимостью элемента и «проникновением» в возможностиshadowDOM
- до тех пор, пока
:dir()
не будет полностью доступен и по-прежнему будет твердо отстаивать важность теневых веб-компонентов, от которых мы не собираемся отказываться, любой чувствительный к направленности компонент должен будет использовать решение на основеJS
, показанное выше, или его вариант.