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

BIDI (unicode bidirectional algorithm)

imageМультиязычные сайты — это хорошо, но довольно муторно. И если для самых популярных [1] языков достаточно иметь несколько вариантов текста, то с добавлением RTL (right-to-left) всё становится гораздо хуже. Приходится заводить новый набор стилей с заменой всего правого на левое и наоборот (касается свойств типа float, padding, margin etc), но и это ещё не все. Могут возникнуть ситуации, когда в одном документе соседствуют фразы на языках с разным направлением, здесь и начинает работать bidi. Если это кому-нибудь интересно…

Bidi

Когда на странице присутствуют языки с разным (RTL и LTR) написанием, символы отображаются, как правило, не в том порядке, в каком они хранятся в памяти браузера. Bidi занимается тем, что расставляет их по местам. Порядок символов зависит от так называемого базового направления текста. Оно задаётся атрибутом dir [2], который можно указать для большинства тэгов. Самый простой способ установить базовое направление всей страницы в RTL — добавить атрибут dir на самый верх: <html dir="rtl">. Кроме того большинство символов в юникоде сами имеют определённое направление. По этому признаку они делятся на строго типизированные, слабо типизированные и не типизированные (нейтральные).

Строго типизированные символы

Это большинство букв. Последовательность LTR символов выводится на экран один за другим слева направо, RTL — справа налево:

image


Фраза из нескольких разнонаправленных кусков выводится в несколько заходов. Их направление не зависит от базового, а только от направления символов. По-этому, например, слова на арабском языке будут читаться справа налево как в RTL, так и в LTR версиях сайта. А вот порядок этих «заходов» будет меняться. В следующем примере вы прочитаете сначала bahrain, затем مصر, затем kuwait:

image


Если же мы добавим в какой-нибудь родительский элемент dir=”rtl”, то базовое направление, а значит и порядок слов, изменится, но сами слова будут читаться так же как раньше:

image


Обратите внимание, что единственное различие в коде этих двух страниц (картинки кликабельны) — наличие атрибута dir, но не порядок слов.

Не типизированные символы

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

image


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

The title is <?php echo $title ?>! in <?php echo $lang ?>.

Для названия на арабском правильным было бы перенести восклицательный знак:

image


Но bidi этого не сделает. Восклицательный знак (как и пробел рядом с ним) стоит между символами с разным направлением, а значит принимает базовое направление и будет выведен во время следующего — LTR — захода. То есть в первый заход выведется “The title is “, во второй — “مفتاح معايير الويب” и затем “! in Arabic”:

image


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

Зеркальные символы

Это всякие скобки и заслуживают отдельного упоминания, потому что bidi их не только переносит, но и разворачивает. Открывающая(ся?) скобка всегда «открывается» в базовом направлении, закрывающая — в обратном. И вот как это, бывает, выглядит:

image

Правила те же: в верхней строке закрывающая скобка стоит на границе -> принимает базовое направление -> переносится и переворачивается. Не приятно.

Слабо типизированные символы

Цифры считаются LTR символами, но при этом не влияют на нейтральные:

image


Так как цифры стоят между арабскими символами, выводится они будут во время RTL захода, но при этом слева-направо и не влияя на пробелы. Ещё есть тонкости вроде той, что знак доллара, например, будет считаться частью числа, а не нейтральным символом, но об этом я знаю мало.

Что делать?

Проблемы, как видно, возникают на границе текстов с разным направлением и связаны с нейтральными символами. Bidi не может самостоятельно определить направление такого символа и рассматривает его как базовое. Самое простое решение — обернуть проблемный участок в <span> с соответствующим dir. Таким образом мы устраним неоднозначность, но для этого надо заранее знать направление текста, который будет расположен в данном месте.В HTML5 добавляется ещё одно возможное значение у атрибута dir — "auto" [3] и даже целый новый тег <bdi> [4]. При использовании любого из них bidi найдёт внутри элемента первый строго типизированный символ и примет соответствующее ему базовое направление, что решит большинство проблем.
Помимо тегов можно использовать специальные маркеры [5] (directional mark). Это непечатные символы, которые по своему действию заменяют строго типизированный LTR или RTL символ. В html можно использовать мнемоники &lrm; и &rlm;

Ну и такой вот тест [6]:

Номер строки IE 10 Opera 12.15 FF 21 Chrome 27.0.1453.94 m
Title is مفتاح معايير الويب! in Arabic
- - - -
Title is مفتاح معايير الويب!&rlm; in Arabic
+ + + +
Title is <span dir="rtl">مفتاح معايير الويب!</span> in Arabic
+ + + +
Title is <span dir="auto">مفتاح معايير الويب!</span> in Arabic
- - + +
Title is <bdi>مفتاح معايير الويب!</bdi> in Arabic
- - + +

Плюс означает, что восклицательный знак переносится вправо, то есть выводится как часть арабского текста.

Больше дёгтя!

А ещё есть поля ввода, в которых тоже может оказаться мешанина из разнонаправленных символов.
Имеем мы, допустим, RTL сайт и поле для ввода телефонного номера в свободном формате. Если вводить цифры подряд, или через дефис, то всё нормально, но вот реакция на пробелы довольно забавная. Вот пример [7]. Попробуйте так же выделить пару цифр скобками. Почему так? Да всё по тому же: цифры — LTR символы, но из-за своей слабой типизации они не влияют на нейтральные, а значит пробелы принимают базовое направление (RTL) и вместо одной LTR строки (которая бы вывелась за один заход) получается несколько, разделённых пробелами и всё это выводится в соответствии с bidi: «слова» — справа налево, символы в них — слева направо. Если добавить в начало строго типизированный LTR символ, то он установит направление нейтральных так же в LTR и проблема исчезнет. Единственное решение, которое я вижу — это как-то обрабатывать пользовательский ввод с помощью js. Например добавлять маркер &lrm; перед каждым нейтральным символом. Если кто-то расскажет более красивое решение, буду признателен.

Вместо заключения

Алгоритм хороший, но не идеальный и это надо учитывать. По поводу полей ввода я задавал вопрос [8] на хабре, но получил только один ответ (хотя и очень подробный, спасибо, yogev_ezra [9]). Текст написан на основе статьи [10] с w3, которая для меня всё разъяснила. Для перевода у меня сил не хватило, решил попробовать пересказать. Картинки оттуда же. Указания на любые ошибки приветствуются.

P.S.
Да, и спасибо gribozavr [11] за подсказку [12].

Автор: 0lorin

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/algoritmy/35840

Ссылки в тексте:

[1] самых популярных: http://ru.wikipedia.org/wiki/Языки_в_Интернете

[2] dir: http://htmlbook.ru/html/attr/dir

[3] "auto": http://www.w3.org/International/tests/html-css/bidi-html5/results-bidi-html5#dirauto

[4] <bdi>: http://www.w3.org/International/tests/html-css/bidi-html5/results-bidi-html5#bdi

[5] маркеры: http://www.w3.org/International/articles/inline-bidi-markup/#lrmrlm

[6] тест: http://jsfiddle.net/Olorin/W9qDS

[7] пример: http://jsfiddle.net/Olorin/ePkmU/

[8] вопрос: http://habrahabr.ru/qa/40169/

[9] yogev_ezra: http://habrahabr.ru/users/yogev_ezra/

[10] статьи: http://www.w3.org/International/articles/inline-bidi-markup/

[11] gribozavr: http://habrahabr.ru/users/gribozavr/

[12] подсказку: http://habrahabr.ru/post/104493/#comment_3265295

[13] Источник: http://habrahabr.ru/post/181123/