Handling LTR text in RTL environments
Recently we finally managed to solve the layouting problems in our Android app caused by RTL user names in LTR language environments. Here's how.
But first things first: what is RTL/LTR? Most languages including English are written from left to right (LTR). However there are some languages - Arabic and Hebrew being the most important of them - which are written from right to left (RTL). This of course has implications on layouting especially in mixed environments.
Consider an Android device whose language is set to English. And now there's an app displaying some user names in a simple ListView. One of those user names is written in Hebrew. This is what happens:
Arthur, 42 y/o
Trillian, 39 y/o
Somewhat ugly, huh? It doesn't fit into the overall layout and the complete line is LTR instead of just the name. Why is that?
Trying to display RTL strings in LTR environments isn't as simple as one would think. Especially on Android. The reason for this is the automatic detections algorithm Android OS uses to render TextViews with RTL text. During the layouting phase of a TextView Android checks the first character of the TextView's String content. Depending on the position of this character in the unicode table Android decides whether to lay out the TextView according to RTL or LTR. This mainly has implications on the gravity which is applied to the TextViews content and how some layout_* parameters (like align_parentLeft/Right) are interpreted leading to the phenomenon depicted above. What we actually want to achieve is that the LTR text is still rendered from left-to-right (as this is how the name is written in it's original form), but with left gravity. To make things a little more interesting we're also appending an LTR string containing the user's age.
Okay, that's enough theory for now - how did we actually solve the problem? Simply by taking a close look at Android's LTR detection algorithm and using some text layout direction control characters. Take a look at this code snippet:
/**
* Reformat the String as LTR to ensure that it is laid out from
* left to right, but it doesn't affect overall layouting of
* TextViews etc..
*/
public static String makeLtr ( String string ) {
if (checkRtl(string)) {
/* prepend the string with an LTR control sign (so
that Android's RTL check returns false) and an RTL
control sign (so that the string itself is printed in
RTL) and append an LTR control sign (so that if we
append another String it is laid out LTR). */
return "\u200E" + "\u200F" + string + "\u200E";
} else {
return string;
}
}
/**
* Check if the given String is probably written in RTL by
* checking if the very first character is within the range of
* RTL unicode characters.
*/
public static boolean checkRtl ( String string ) {
if (TextUtils.isEmpty(string)) {
return false;
}
char c = string.charAt(0);
return c >= 0x590 && c <= 0x6ff;
}
By applying makeLtr() to every user name String we can be sure, that the TextView is laid out correctly, that is like so:
Arthur, 42 y/o
Trillian, 39 y/o
عليّ(RTL!), 56 y/o (yeah, LTR and gravity left!)
Ford Prefect, 111 y/o
The TextView itself doesn't have to be modified in any way. Of course this little trick can also be applied to other programming languages and frameworks or LTR Strings in RTL environments just by encapsulating the String in the corresponding unicode control characters.
But first things first: what is RTL/LTR? Most languages including English are written from left to right (LTR). However there are some languages - Arabic and Hebrew being the most important of them - which are written from right to left (RTL). This of course has implications on layouting especially in mixed environments.
Consider an Android device whose language is set to English. And now there's an app displaying some user names in a simple ListView. One of those user names is written in Hebrew. This is what happens:
Arthur, 42 y/o
Trillian, 39 y/o
عليّ, y/o 56
Ford Prefect, 111 y/o
Somewhat ugly, huh? It doesn't fit into the overall layout and the complete line is LTR instead of just the name. Why is that?
Trying to display RTL strings in LTR environments isn't as simple as one would think. Especially on Android. The reason for this is the automatic detections algorithm Android OS uses to render TextViews with RTL text. During the layouting phase of a TextView Android checks the first character of the TextView's String content. Depending on the position of this character in the unicode table Android decides whether to lay out the TextView according to RTL or LTR. This mainly has implications on the gravity which is applied to the TextViews content and how some layout_* parameters (like align_parentLeft/Right) are interpreted leading to the phenomenon depicted above. What we actually want to achieve is that the LTR text is still rendered from left-to-right (as this is how the name is written in it's original form), but with left gravity. To make things a little more interesting we're also appending an LTR string containing the user's age.
Okay, that's enough theory for now - how did we actually solve the problem? Simply by taking a close look at Android's LTR detection algorithm and using some text layout direction control characters. Take a look at this code snippet:
/**
* Reformat the String as LTR to ensure that it is laid out from
* left to right, but it doesn't affect overall layouting of
* TextViews etc..
*/
public static String makeLtr ( String string ) {
if (checkRtl(string)) {
/* prepend the string with an LTR control sign (so
that Android's RTL check returns false) and an RTL
control sign (so that the string itself is printed in
RTL) and append an LTR control sign (so that if we
append another String it is laid out LTR). */
return "\u200E" + "\u200F" + string + "\u200E";
} else {
return string;
}
}
/**
* Check if the given String is probably written in RTL by
* checking if the very first character is within the range of
* RTL unicode characters.
*/
public static boolean checkRtl ( String string ) {
if (TextUtils.isEmpty(string)) {
return false;
}
char c = string.charAt(0);
return c >= 0x590 && c <= 0x6ff;
}
By applying makeLtr() to every user name String we can be sure, that the TextView is laid out correctly, that is like so:
Arthur, 42 y/o
Trillian, 39 y/o
عليّ(RTL!), 56 y/o (yeah, LTR and gravity left!)
Ford Prefect, 111 y/o
The TextView itself doesn't have to be modified in any way. Of course this little trick can also be applied to other programming languages and frameworks or LTR Strings in RTL environments just by encapsulating the String in the corresponding unicode control characters.