Недавно мне довелось работать над сложным веб-приложением, клиент которого нужно было интернационализировать (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, показанное выше, или его вариант.