Rating: 4.36 / 5 Based on 22 reviews
Software development for customers across the globe is full of challenges. We must take into account different languages and time zones. One of the issues that we might face is adding languages read from right to left, such as Arabic or Hebrew, especially if our frontend application was developed only with Left to Right languages in mind.
In this article, I will guide you on how to handle right to left text and layout in your web app in React - my experience is based on the Welliba app that I’ve helped to develop as a frontend developer. Here, by the way, you can read the Welliba Case Study.
If you’re interested in introducing RTL in your mobile app read the article on Right to Left in Flutter.
Implementing a Right-to-Left version of the website or web app doesn't only refer to translating it into another language. Offering an RTL website version may be important for several reasons:
The browser uses Unicode Bidirectional Algorithm (also known as the bidi algorithm) to determine the visual representation of text. It relies on the fact that every character in Unicode has directionality related to it. For example, letters from the Arabic language are strongly typed as RTL. Therefore, by default, paragraphs containing only Arabic characters will be displayed from right to left.
Translations might also contain mixed content in terms of directionality. Consider the following text:
Source: Unicode Bidirectional Algorithm basics
Bidi algorithm produces output taking into account every part of the text. In this case, every English part will be displayed in an LTR manner, and every Arabic part will be displayed in an RTL manner. The order of parts is determined by base direction, which is LTR by default. It can be changed to RTL by using dir attribute. After changing the base direction to RTL the order of parts is reversed, which is natural if the current language of the page is Arabic.
Source: Unicode Bidirectional Algorithm basics
Enabling proper directionality based on the current language
Frontend apps adjusted for RTL languages are not only about displaying texts from right to left, just like the browser does by default. Because of the fact that the pages will be read in that direction, the whole layout should flow from right to left.
To enable such behavior, the directionality of the application needs to be set dynamically with respect to the current language. This can be done using the dir attribute on the <html> element, which can be set to either "ltr" for left-to-right languages or "rtl" for right-to-left languages. This change results in browsers laying out elements from right to left for RTL languages. This also applies to flexbox and grid layouts, which will have reversed order of elements.
The following HTML code demonstrates how to enable RTL layout when the page is in RTL language.
<!DOCTYPE html>
<html dir="rtl">
<head>
<title>My RTL Page</title>
</head>
<body>
<!-- Page content goes here -->
</body>
</html>
Usually languages are managed by internationalization frameworks. One of the examples is react-18next. Enabling proper directionality based on language direction is performed by setting the dir attribute whenever the current language of the page changes. The following code shows how it can be achieved with simple useEffect.
useEffect(() => {
const dir = i18n.dir(i18n.language);
document.documentElement.dir = dir;
}, [i18n, i18n.language]);
Unfortunately, if the application was built only for LTR languages, setting dir attribute is not enough. Most likely, the page won’t look good at this point, and the reasons behind it are CSS properties that have left and right included in their names. Properties like left, right, margin-left, margin-right, padding-left, padding-right, border-left, and border-right aren’t “reversed” in RTL mode.
Because of that, absolutely or fixed-positioned elements won’t be placed correctly. The same applies to margins, paddings and borders. When the whole layout is reversed, they are applied to the wrong side.
The layout and design of the web app need to be adjusted to ensure that the content is displayed correctly. This may include adjusting the position of menus, images, and other page elements to accommodate RTL languages.
To avoid such problems, the CSS properties mentioned above should be avoided. To space elements inside a flexbox or grid container, it’s a good practice to use gap property. Margins can be defined using margin-block-start, margin-block-end, margin-inline-start, and margin-inline-end. The same applies to paddings. They can be defined with padding-block-start, padding-block-end, padding-inline-start and padding-inline-end.
For left and right properties used for positioning elements, there are no similar alternatives. Styles can be adjusted by using CSS selectors. This approach is based on the fact that <html> tag has a dir attribute set.
[dir='ltr'] & {
right: 1rem;
}
[dir='rtl'] & {
left: 1rem;
}
When we have a large existing application, manually adapting styles would be time-consuming. Fortunately, there are production-ready solutions that can help with that problem almost out of the box. One of them is postcss plugin postcss-rtlcss. It adds RTL versions of styles to our stylesheets whenever it encounters usage of CSS properties that are left or right-oriented.
In the example shown before, such styles would be generated automatically. It’s also capable of preparing styles for shorthand properties like margin or padding, by swapping left and right-related values. At this point, our application should be almost ready. What’s missing to achieve the final result?
Some images or icons might have directionality. One of the examples might be left or right arrow icons. When the page flows in the opposite direction, the arrow should most likely be reversed. We need to identify every case of that type in our application and reverse such images. This can be achieved with transform CSS property and scaleX CSS function. It allows us to define a transformation that resizes elements along the x-axis. When -1 is provided as its parameter, the image would be simply flipped in the opposite direction.
[dir='rtl'] & {
transform: scaleX(-1);
}
Translating elements over the x-axis is another case that won’t be automatically handled by postcss-rtlcss. In RTL mode, elements should probably be translated in the opposite direction. An example of this case can be centering fixed-positioned elements:
position: fixed;
left: 50%;
transform: translateX(-50%);
postcss-rtlcss will add the following styles to our stylesheet
[dir='rtl'] & {
right: 50%;
}
However, transform property will stay the same. This will result in an element that’s shifted to the left and not centered horizontally. To fix that, we need to apply a simple change to our CSS file that will transform the item to the right in RTL mode.
[dir='rtl'] & {
transform: translateX(50%);
}
postcss-rtlcss can be disabled for some pieces of code. This is useful when we don’t want it to generate RTL styles for some part of the application or we want to handle RTL styles ourselves. In this example, the plugin won’t generate RTL-related code into the resulting CSS file, and the items with .item class will always be positioned to the left regardless of directionality. Note that comments are marked as important so that they won’t be removed by the loader, and postcss plugin will be able to access them.
/*! rtl:begin:ignore */
.item {
left: 0;
}
/*! rtl:end:ignore */
Sometimes not all translations for a given language are ready and some parts of the system are displayed in the default language, possibly of different directionality. Usually, the bidi algorithm should handle such situations, but that’s not always the case. Not all Unicode characters are associated with some directionality. Some of them are considered to be weak or neutral characters.
Examples include punctuation, spaces or numbers. The way they are displayed is determined by the surrounding text. If they are surrounded by 2 characters of the same directionality, the inferred directionality is the same. If they are surrounded by 2 characters of different directionality, the inferred directionality is based on base direction. Consider the following example.
The text is in English, but the current language of the application is set to Arabic, so the base direction is set to RTL. Because of that, and the fact that the last character (dot) has weak directionality, the dot is placed just like the whole paragraph was Arabic.
Fortunately, there is a solution to this problem. Whenever the texts are dynamic, and we are not sure about directionality, we can use <bdi> HTML tag. It lets us treat text in isolation from its surroundings. In this case, by using <bdi> instead of <p>, the whole text is treated as LTR and the punctuation is fixed.
To sum up, adding RTL languages to React applications for the first time can be painless, even if an app was written only with LTR languages in mind. Thanks to postcss plugins, most of the tedious work can be omitted, and the app is RTL-ready with minimal effort. All we need to do is make minor CSS changes whenever the postcss plugin wasn’t able to perform appropriate code generation.
The last step would be an app review by someone who natively speaks a given language. After all, people who browse websites displayed only in LTR languages might not notice if something’s wrong. If you need help with React app development you can reach out to the LeanCode team.