React Native

i18n issues on RTL languages: don’t be LEFT out in the dark, be RIGHT

Today, I’d like to talk to you about internationalisation in React Native apps and especially about ambushes that we found on an app we are working on (and shipping it in 20 languages). On one particular occasion, I had to implement Arabic and Hebrew languages and everything was going smoothly. We had that really nice dynamic questionnaire in an horizontal FlatList that was building itself gradually with the user’s answers, adding the needed questions to a list after each answer. And on Android, the app was adding the questions to the right side of the list instead of the left side where they were supposed to be… but why!? I’ll tell you what happened further in this article.

Internationalisation? RTL? What are we talking about?

  • What does internationalisation stand for?

Internationalisation, or more often seen as i18n for short, is everything that lets a user experience and understand the apps we’re making, regarding their “usual usage”. It concerns obviously translations in another language, but also timezones or UX.

  • What is LTR/RTL?

This stands for Left-To-Right and Right-To-Left. Majority of languages is LTR but there’s also some RTL languages and they should not be neglected, especially if we want our app to be worldwide. There are around 600 millions people speaking a RTL language in the world, that’s a lot! Some examples are Arabic, Hebrew, or Persian.

  • And what about React Native for all of this?

Depending on the OS, handling internationalisation is already automatic and provides relatively good results (languages are already tagged as LTR or RTL in the OS for example). There are a lot of differences on how it is handled between iOS and Android though as we’ll see a glimpse of this a bit later.

There’s also several libraries for React Native that do the job like Luxon for everything date and timezone related for example, or i18n-js, react-intl and i18n-next for translations.

The main internationalisation points, linked to languages, that we can think of from the beginning are often these ones:

👉 View components’ designs to handle RTL and LTR

  • Text automatically inverts itself
  • But be careful of pictures! → For example, an arrow to the left to act as a “back button” needs to be an arrow to the right in RTL!

👉 UX (Horizontal scrolling direction, etc) in case of RTL languages → It has to be reversed

And seeing all the troubles I encountered on this topic, I was thinking that issues that can impact up to 600 million app users in the world surely would have been searched and solved by other developers. Right…?

How it all started : Horizontal FlatList

Now we know all what we have to know, back to my dynamic questionnaire in an horizontal FlatList in Arabic and Hebrew and the Android version adding questions to the wrong side of the list. You can see the different behaviours below:

On Android, after selecting an answer, we go back to the beginning - to the right

On iOS, we have a normal behavior: after selecting an answer, we have a newly added question and we go on it - to the left

While I was struggling with my left and my right, I inspected everything but the conclusion was still the same: why on Android the list behavior was that different?Well, even the Internet didn’t know about this. I had to roll my sleeves up and ended up in the source code of RN’s FlatList to finally understand why…

  • The problem is located in the "computeViewableItems" function in "ViewabilityHelper.js", which calculates which items to display and is executed when we push cells in the FlatList: the function actually returns the “first” element of the list (the one that is “the most to the right” but doesn’t consider the horizontal FlatList’s reading direction)
  • The solution to this issue: do not use the getItemLayout prop (but that’s not a real solution unfortunately…)
  • FlatList components have an issue with the getItemLayout prop on Android when using a RTL language: it’s impossible to scroll towards an element dynamically added to the list. After inspecting the FlatList component’s source code for a bit, we can see it’s linked to badly calculated metrics: not using the prop solves the issue as it doesn’t calculate the metrics again, but comes with the downside to not be optimized as the prop was calculating how many elements needed to be rendered. We’ll have to use the windowSize prop to specify how many elements our list can have and, in any case, if our list is too long, navigation and scrolling will be hampered.
  • There’s weirdly no issues if the FlatList is vertical though.
  • You may think we could have been using some other libraries like react-native-snap-carousel but it doesn’t work: the list’s data correctly inverts on iOS but not on Android when we’re using RTL languages! And even other libraries like FlashList don’t seem to support RTL languages properly at the moment. RecyclerListView might be the answer but I haven’t tried it yet!

Another issue! (and how to overcome it)

I noticed that there’s a lot of small troubles on internationalisation that can be unknown unless you’ve faced them, because there’s not a lot of documented answers, including libraries issues and even React Native ones! Here’s an example that I have encountered and how I avoided it with my small experience on the subject:

Text component with LTR and RTL languages mixed

👉 Solution: Indicate a special character at the beginning of the string when it’s needed!

👉 i18n only checks the first character of a string to decide if it’s a LTR or RTL string. This can lead to misunderstandings in reading directions, for example if a RTL string has a LTR name at the beginning (like your app name for example!)

👉 There’s 2 ways to specify reading direction:

  • Through the Text component prop "writingDirection"
  • And through a special unicode character at the beginning of your string. This is especially useful if you can’t entirely control the text you need to display or if you intend to reuse your component in more languages and don’t want to bother managing the prop. The special characters are [U+200E] to specify an LTR string, and [U+200F] for an RTL one. Because every time I needed to specify a direction was RTL, I mainly use this page to easily grab the character in my clipboard!

So what have we learnt on i18n and RTL languages?

Phew! I hope you learnt how to dodge some bullets here! The key point to handle a perfect internationalisation is: be careful of how Android handles it! This OS supports less features provided from tools and libraries that React Native can use, whereas on iOS everything seems to be natively supported. We also learnt that, despite having several alternatives to the “same thing”, libraries don’t always offer RTL support for what they bring. If you’re planning to make an app that will handle these languages, keep in mind that not all your libraries do! Finally, we learnt that, even if there’s a lot of users who speak a RTL language, there’s sadly not much information on RTL issues on the Internet. So I hoped this small article helped you a bit or made you curious on the subject!

And you? Did you encounter other issues linked to internationalisation in your apps? Feel free to share your problems (and your solutions too!) through my contact info below!

Développeur mobile ?

Rejoins nos équipes