- PVSM.RU - https://www.pvsm.ru -

Привет. Мы недавно перевели на арабский язык [1] 2ГИС Онлайн, и хотим поделиться своим опытом адаптации интерфейса под RTL (right-to-left). Это будет актуально и для иврита, и для персидского языка.
Я разделю этот опыт на две статьи — теоретическую и практическую. Сегодня — больше про теорию. Я расскажу, зачем нам понадобилось переворачивать весь интерфейс, что для разработчика интерфейсов значит фраза «сделать арабскую версию» и как справиться с арабским языком, смешанным с английским. Особое внимание уделю алгоритму, по которому строится отображение текста смешанной направленности — unicode bidirectional algorithm.
Кажется, ценность в адаптации интерфейса под «справа налево» такая же, как у адаптации на любой другой популярный язык, но это не совсем так.
Различие между английской и русской версией небольшое — чаще всего, это просто переведёный текст. Пользовательский опыт, за исключением редких мелочей, в целом не отличается. Различие же между арабской и английской версией огромное.
Всего 0.6% [2] интернет-ресурсов в мире содержат арабский контент. Однако, на арабском говорит больше 5% пользователей интернета, и эта доля стремительно растёт. Привычное направление чтения для них — справа налево. Какие ощущения у них от современного веба? Точно такие же, как у носителя русского языка при пользовании RTL–интерфейсом. Выбери себе метафору сам — может, это как садиться за руль праворульной машины, когда постоянно водишь леворульную. Или как зайти однажды в 2ГИС и увидеть, что карточки и поиск — справа:

Если мы хотим, чтобы наш сервис был максимально удобным для всех пользователей, адаптировать его под RTL нужно обязательно.
С первого взгляда она мне показалась необъятной — нужно переделать весь интерфейс под требования, которые никто не сможет нормально объяснить.
Посмотрев несколько примеров арабских сайтов, я понимаю, что сделать арабскую версию — это:
С переводами вроде всё понятно. С переворачиванием интерфейса — ничего не понятно. Остановимся на этом подробнее.
Первым делом я добавил тегу html атрибут dir="rtl":
<html dir="rtl">
Всё изменилось, но не совсем так, как я ожидал. Я осознал, что совсем не понимаю, что происходит. По какому принципу выстраиваются элементы друг за другом?
Рассмотрим один и тот же простой кусок вёрстки в LTR и RTL. Он не очень осмысленный, но наглядный:
<table>
<tr>
<td>
Hello world
</td>
<td>
<button>Hello</button> <button>world</button>
</td>
</tr>
</table>

Как видно на скриншоте, атрибут dir (как и css-свойство direction) задаёт:
text-align);inline-block элементы;Элементы поменяли порядок, но символы в словах всё равно расположены как обычно. Потому что порядок символов в строке определяется другим алгоритмом.
Физически в строке символы расположены последовательно, но за итоговое отображение этой последовательности на экране отвечает unicode bidirectional algorithm [3].
Вкратце:
На направленность каждого символа влияет его тип и направленность соседних символов.
1) Сильно направленные (или строго типизированные, strongly typed) — например, буквы. Их направленность заранее определена — для большинства символов это LTR, для арабских и иврита — RTL.
Слова на картинке целиком строго типизированы:

2) Нейтральные — например, знаки пунктуации или пробелы. Их направленность не задана явно, они направлены так же, как соседние сильно направленные символы.
Запятая между направленными слева направо «o» и «w» в строке «Hello, world» принимает их направленность и при базовом LTR, и при RTL:

Но что, если нейтрально направленный символ попадает между двумя сильно направленными символами разной направленности? Такой символ принимает базовую направленность.
Вот тут расположение «++» в одном случае между однонаправленными «C» и «a», а в другом — между разнонаправленными «C» и арабским «و», приводит к разному результату:

То же самое случается с нейтральными символами в конце строки:

3) Слабо направленные (или слабо типизированные, weakly typed) — например, числа. Они имеют свою направленность, но никак не влияют на окружающие символы.
Непрерывные слова из цифр выстраиваются слева направо, но два числа подряд, разделённые нейтральным символом, будут идти друг за другом справа налево, если задана базовая RTL–направленность:

Ещё более наглядный случай — число, в котором разряды разделены пробелом:

При этом допускается разделять числа точкой, запятой, двоеточием — эти разделители тоже слабо направлены (подробнее можно посмотреть в спецификации [4]):

Последовательные символы одинаковой направленности объединяются в блоки (directional run). Эти блоки выстраиваются друг за другом в порядке, определённым базовым направлением:

Слабо направленные числа, несмотря на то, что имеют свою направленность, не влияют на формирование блоков, что может приводить к такому результату — они продолжают предыдущий направленный блок:

Некоторые символы в разных контекстах имеют разную форму — например, открывающая скобка в RTL будет выглядеть как закрывающая в LTR (что логично, ведь контент в скобках будет идти после — то есть, слева от неё).
В большинстве случаев это не создаёт проблем, но если скобки случайно окажутся разной направленности, визуально они будут смотреть в одну сторону. Например, если скобка висит в конце строки:

Как мы увидели выше, часто текст по этим правилам форматируется не так, как нам хотелось бы.
В этом случае нам пригодятся инструменты для встраивания желаемого направления в существующий контекст или переопределения направлений конкретных символов.
С заданием базового направления мы уже познакомились выше: это делает атрибут dir. Это глобальный атрибут [5], он применим к любому элементу.
dir создаёт новый уровень встраивания (embedding level) и изолирует содержимое от внешнего контекста. Контент внутри направлен согласно значению атрибута, а внешняя направленность самого контейнера становится нейтральной.
Явная установка атрибута dir позволяет избежать почти всех проблем форматирования смешанного текста:
أنا أحب <span dir="ltr">C++</span> و Java

Если направленность контента неизвестна заранее, можно указать auto в качестве значения атрибута dir. Тогда направление содержимого определится с помощью «некоторой эвристики» — оно просто возьмётся у первого попавшегося строго типизированного символа.
<p dir="auto">{comment}</p>
Аналогично работает тег <bdi> и css-правило unicode-bidi: isolate:
<span>Landmark: <bdi>{name}</bdi> — {distance}</span>

Можно открыть новый уровень встраивания без изоляции — правило unicode-bidi: embed в комбинации с нужным значением правила direction определяют и направление внутри элемента, и его направленность снаружи. Но это на практике не нужно почти никогда.
<bdo dir="rtl"> или unicode-bidi: bidi-override; direction: rtl. Переопределяет направление каждого символа внутри элемента. Нужно использовать крайне редко (например, если нужно поменять местами два конкретных символа) и не забывать изолировать дочерние элементы.
<bdo dir="rtl">Hello, world!</bdo>

При этом снаружи элемент трактуется как сильно направленный. Чтобы он вёл себя как isolate снаружи, но как bidi-override внутри, нужно использовать unicode-bidi: isolate-override.
Вставка управляющих символов — неприятный способ, но он полезен, когда у нас нет доступа к разметке, но есть доступ к контенту. Например, это могут быть просто невидимые сильно направленные символы, ‎ и ‏ (/ или u200e/u200f). Они помогают задать нужное направление нейтральному символу.
Например, в этом случае, чтобы восклицательный знак в конце строки принял направление LTR, нужно, чтобы он находился между двумя LTR символами:
<span dir="rtl">Hello, world!‎</span>
Также любая описанная выше логика реализуется через управляющие символы. Для изоляции — LRI/RLI, для переопределения — LRO/RLO, и т.д. — смотри подробное руководство по управляющим символам.
К сожалению, в IE тег <bdi>, dir="auto" и соответствующие им правила CSS не поддерживаются. Кроме того, спецификация [6] этих правил всё ещё на стадии Editor's Draft.
Если нужен аналог dir="auto", работающий в любом браузере, можно парсить контент регуляркой [7] и выставлять атрибут dir самостоятельно. Но лучше, конечно, так не делать.
Однозначно, управлять направлением текста по возможности нужно через HTML–атрибут dir и тег <bdi>, а не через правила CSS. Направление текста — это не стилизация, это часть контента. Страница может быть вставлена через какой-нибудь instant view или быть прочитана через RSS–reader.
Мы познакомились с теорией. Но знание теории не освобождает от необходимости страдать.
Главная проблема, с которой я столкнулся на первых же минутах разработки под RTL–язык, это его чужеродность. Мы пишем код слева направо. Моя система, браузер и редактор работают слева направо, все наши внутренние продукты — слева направо. Поэтому, как только в это пространство попадает арабский язык, всё плохо и больно:
Если символы на экране расположены не в том порядке, в каком они на самом деле располагаются в строке, что будет, если попытаться редактировать двунаправленный текст? Или хотя бы выделить и скопировать его часть?
Ничего хорошего. Попробуйте сами:
Landmarks: دبي مارينا مول — 600 m, داماك العقارية — 1.2 km
azbycxdwevfugthsirjqkplomn

И то же самое при правке кода в редакторе и код-ревью — боль.
Даже в порядке элементов в массиве нельзя быть уверенным:

Или того хуже, код вообще не выглядит валидным:

Можно довести до абсурда [8]:

В любой ситуации есть только один источник правды — логическое расположение символов в строке. Символы могут быть расположены визуально правильно в редакторе или в почтовом клиенте, но на итоговой странице правильно их расположить не получится, потому что логический порядок нарушен.
Посмотреть, как изначально расположены символы в строке и почему они визуально расположились именно так, позволяет инструмент на сайте Юникода: http://unicode.org/cldr/utility/bidi.jsp [9]

Мы познакомились с правилами, по которым определяется порядок элементов и порядок символов в строке, и узнали, как можно на него влиять.
Что нужно обязательно помнить:
dir="rtl" задаёт направление потока, выравнивает текст как text-align: right, меняет порядок ячеек таблицы, флексов и гридов. За последовательность символов в строке отвечает unicode bidirectional algorithm;На практике вся эта теория выливается в одно простое правило:
При смешанной направленности нужно явно изолировать уровни встраивания с помощью атрибута
dir, а если контент неопределённой направленности — использовать<bdi>иdir="auto".
Также нужно готовиться к тому, что в редакторе текст может выглядеть не совсем так, как будет выглядеть в браузере. И это обязательно причинит тебе боль.
В следующей статье я расскажу о нашем практическом опыте. Как быстро сделать прототип RTL-версии и какие выбрать решения для продакшна. Как быть заранее готовым, но какие моменты невозможно предусмотреть.
Автор: Vladislav Sapozhnikov
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/279829
Ссылки в тексте:
[1] перевели на арабский язык: https://2gis.ae/dubai/lang/ar
[2] Всего 0.6%: https://en.wikipedia.org/wiki/Languages_used_on_the_Internet
[3] unicode bidirectional algorithm: https://unicode.org/reports/tr9/
[4] спецификации: http://unicode.org/reports/tr9/#W4
[5] глобальный атрибут: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
[6] спецификация: https://drafts.csswg.org/css-writing-modes-3/#unicode-bidi
[7] парсить контент регуляркой: https://stackoverflow.com/questions/12006095/javascript-how-to-check-if-character-is-rtl
[8] абсурда: https://twitter.com/aemkei/status/984703097908981760
[9] http://unicode.org/cldr/utility/bidi.jsp: http://unicode.org/cldr/utility/bidi.jsp
[10] Источник: https://habr.com/post/358148/?utm_source=habrahabr&utm_medium=rss&utm_campaign=358148
Нажмите здесь для печати.