A German, an Arab and a Korean post on your app. Now what?

Husayn Hakeem
ProAndroidDev
Published in
5 min readMay 28, 2018

--

Suppose you have a social media kind of app where users can share content (containing text). When a German, an Arab and a Korean respectively share content in German, Arabic and Korean, how will they be able to understand each other’s posts? (assuming they don’t understand the other languages, and the content is all public). Well instagram has a seemingly simple answer to this issue, it allows users to translate content on the app to their language. Being a frequent user of Instagram myself and an Android engineer, I thought it’d be fun to build this brilliant feature into a library. This is my attempt at doing so, the said library -which I named Tradur- can be found below, contributions are more than welcome.

The output

Before getting into any technicalities, here’s a sample app I built using Tradur. It’s basically the same quote translated into different languages: French, Chinese, Arabic, German, Korean and Spanish. Tradur translted it into my language, English.

ps: The quote is by steve jobs “My favorite things in life don’t cost any money. It’s really clear that the most precious resource we all have is time.

A sample app using Tradur

100 languages

Google offers a powerful translation API that’s fairly straightforward in terms of usage, and to top it off supports translation from and to more than 100 languages! So at the core of Tradur, Google translate API is used.

The scenario

The goal of Tradur is to allow a user X to translate text content on an app to their -device’s- language. This is directly inspired by Instagram which sets a “See translation” label beneath each post that’s is in a different language than the user’s. And this is exactly what Tradur does.

The structure

For each text content the user would be able to translate, there should be an associated translation element. This gives us 2 components already, the translatable text content and the Tradur translation widget. The translation logic should be separated from the translation widget, and finally there should be an element to store any information related to what is rendered on the screen by the translation and translatable widgets, this is the view state.

Concretely

It’s pretty obvious that the translatable text content will be represented by a TextView. Similarly, the Tradur translation widget is also a TextView which, once clicked, either translated the translatable content or sets it back to its original value. How can these 2 elements be linked? By adding an attribute in TradurTextView referencing by id the translatable textView.

This would allow us to write something like the following.

With this, the text value of textview can be retrieved inside TradurTextView and translated when needed.

The ViewState

The view state will determine what Tradur TextView and the translatable TextView display.

  • The translatable TextView can either display its original text or the translated text.
  • The Tradur TextView can display a message inciting the user to translate the text, and once the text is translated inciting the user to revert back to the original text. While the text is being translated (which can take some time), it should display a loading message. These 3 states can be represented using an enum class.

The above scenarios describe the possible states of our text views, which gives us the following view state class.

As stated before, what’s displayed on the screen depends solely on the view state, this ensures that the view rendering is controlled by a unique source and makes debugging much easier when rendering issues arise. With the view state built, the following method is added to TradurTextView which renders the UI depending on attributes of the view state. This method must be called any time the view state changes in order to render the latest UI updates.

The translation

How the translation is done isn’t as important as to how it’s used by TradurTextView, for purposes of brievety, I will omit getting into it, but essentialy it it based on Google Translate API and performs the translation on a worker thread using coroutines. You can check its implementation here.

A call to the translator is made each time the user chooses to translate some text content (a caching optimization can be done here). An onClick listener is implemented in TradurTextView which deals with 2 scenarios:

  • The text content has not been translated, a call to TextTranslator is made here. The call is represented in the following method.

For each case (loading the translation, when the translation succeeds and when it fails), a new view state is created from the previous state, and a call to refresh the UI is made.

  • The text content has already been translated, in this case a revert to the original -untranslated- text is done which is then displayed.

The translatable TextView

When the translatable TextView is not in a list, its id is unique and can be retrieved using a call to findViewbyId. But when it is part of a layout in a list (a recyclerView for example), having only the context of the activity/fragment it’s embedded in is not sufficient to get it (a call to findViewbyId will return the first view with the given id, whereas the user may have clicked on the second list item).

When the user clicks on the Tradur TextView on item 2, a findViewById will return the Translatable TextView of item 1 instead of that of item 2. So how can the correct view be found given we only have its id and its associated Tradur TextView?

A solution I came up to this issue is a sort of Breadth first search on the views near the associated Tradur TextView. The search is done on its view siblings, and then on its parent and its view siblings, and so on. You can find its implementation here.

With Tradur’s first version, more work is to be done, there is room for optimizations and probably even some bug fixes. Contributions are welcome!

For more on Java, Kotlin and Android, follow me to get notified when I write new posts, or let’s connect on Github and Twitter!

--

--