Some languages of the world (Arabic, Hebrew etc.) are RTL, meaning they are read right-to-left, instead of left-to-right. Typically in web applications supporting one of these languages, everything is reversed, meaning scroll bars, progress indicators, buttons etc.
I recently took part in a discussion with the jQuery UI team about how they would start to implement RTL support and I thought many of the issues were generic across any web application or library, so this is a blog post about my thoughts on the discussion.
How RTL support is done
Lets start with an example. Here is a little html fragment and how it looks in LTR.
and the code is
Now, if it were in a RTL language, it would look like this…
Which has the following code..
Lets go through the differences. Firstly the element has dir="rtl"
on it. This tells the browser that all text should be laid out from right to left, but it doesn’t just effect text, it effects all inline and inline-block code. This means that because the button
elements default to inline-block, they are reversed, so Cancel is on the left and Ok on the right (without any css changes). Note that this dir attribute is inherited and is not a CSS style.
However the rest of the differences have to be done with CSS and involve basically reversing all spacial attributes, so float: right
becomes float: left
and margin-left
becomes margin-right
.
Further Changes
In an ideal world, all the work would be done in the CSS - which would mean no JavaScript changes and it would keep everything simple, however that isn’t always possible. An example from jQuery UI was the progress bar which would have an absolutely positioned div for the progress and which is increasing the size. You either need to change the CSS so that the element is not absolutely positioned or you need to change the JavaScript to know whether the layout is RTL and if it is ,also change the left position (remember that the RTL attribute only changes inline, inline-block directions - not positions, floats, margins, borders, padding etc.)
There are further complications and browser differences which mean that a complex web application can’t refactor everything on to the CSS - but it is good to try, because changes in the JavaScript can be pervasive, with an isRTL boolean starting to appear everywhere.
LTR inside RTL
The initial approach that the jQuery UI team had thought to go with was that each widget determines its own direction (traversing the DOM backwards to see if an ancestor has the attribute), stores it somewhere at start up and then uses the boolean wherever it is needed. This approach works fine if we just consider the JavaScript, however with the CSS it becomes tricky.
At first you might think you could write selectors like so.
However, this approach (having the RTL and LTR CSS co-existing on the same page) is so that different widgets can have different directions, but some widgets contain other widgets, like a tab control, so what do you do if you have RTL tab which has LTR content? A first approach might be CSS like so…
But you are only really supporting one redirection level (e.g. not RTL in LTR in RTL) and you are trebling the size of your CSS (OK so you could increase the specificity of the last selector so you can put it with the first, but things are starting to look awful).
Another approach would be to change the names of the css classes - it looks nicer at first..
But consider that if you go for this approach, you cannot use the CSS ancestor selector anywhere (or if you do, the ancestor CSS class must be marked rtl_
) and furthermore, every class that is used across more than 1 widget, must be marked rtl_
and therefore have it’s css class changed.
You could concede that only css classes used in widgets which have ancestors need to be treated like this, but in my opinion, things get very messy, very fast and you see the size of the CSS and the JavaScript jump in size.
Solutions
In my opinion, the use-cases across the web for web applications which mix both LTR and RTL are limited. The only viable one I heard during the meeting was a translation or language learning application. I also think that applications that can change their language without a refresh are limited (and not only that a change from a LTR language to a RTL one) so, the simplest thing is to have one LTR stylesheet and one RTL stylesheet and therefore not impact the performance and size of either language set for most users and keep the required changes to the JavaScript to an absolute minimum. You could tell users that they should use iframes for mixing script or else support it only in the JavaScript and let people deal with the CSS themselves if they need to.
One bonus you get from this approach (although not completely excluded from a different approach - just harder) is that you can look at automating the changes to the CSS. It would be easy to create a Less plugin which automatically reversed all the CSS properties (I’m sure you could do this in postcss too, used for autoprefixer). I’d do this with the use of a plugin and a variable, so that you had ultimate control in a single set of CSS.
That way the input would be..
Generating
or
Depending on the direction.
Further Problems
Scrollbars cross browser
When using scrollbars in overflow divs, like above, all browsers will show the scrollbar on the left, rather than the right. However, when it comes to setting the direction on the body, only IE will move the main page scroll bar over to the left - Chrome and Firefox will keep it on the right.
LTR characters mixed with RTL characters
When you combine characters that can be used in both RTL and LTR languages (punctuation for instance) then where the punctuation is displayed, depends on the direction. So, below I’ve used Google Translate to convert the same text to Arabic and English, and displayed them both in RTL.
The full-stop ‘.’ is the last character in both strings, after “Know” in English, but because we have told the browser the direction is RTL and it is the last character in the string and is punctuation so of indeterminate direction, the browser puts it on the far left hand side. Here is the same text but with the direction RTL.
Essentially the data format is start-to-beginning, but the browser is still rendering the RTL script RTL and the punctuation in whatever direction you have defined - so the full stop is always on the end of the string, which is on the right hand side. The reason that only punctuation at the end of the sentence is effected is because indeterminate direction characters that are surrounded on both sides by the same direction will inherit that direction, rather than the over-ridden direction of the HTML node. The easiest way to see what is happening is to do a substring in JavaScript, because even your LTR editor will be displaying RTL script, RTL.
This means - do not use RTL with non RTL script or vice-versa or you can end up with something you do not expect.