How to Create Android Translation App From Scratch with Yandex Translation API

In this episode on how to create android translation app from scratch with Yandex translation API, we are going to explore what it takes to build an android translation app.

There are many android translation apps out there. I know you must have used one.

But imagine if you were tasked to create your own translation app, how would you go about it?

If you are not sure of how to build a translation app or you want to build your own translation app and publish it in Google Play Store following this tutorial will provide a valuable insight and knowledge on how you can achieve it.

We are going to use Yandex Translation API for translating user entered words.

There are many companies that offer translation service and their prices vary. But the three most popular translation services are Google Cloud Vision Translation, Microsoft Cloud Translation and Yandex.

You can read more about different companies that offer translation services with a simple google search.

Why did we choose Yandex Translation API?

All the translation service companies I checked, Yandex has the highest free-quote per day or month. Besides that, their API documentation is simple and easier to use.

In the course of writing this tutorial, I have tested Microsoft and Google translation APIs but we will focus on Yandex. If you need support for any other services you can contact us.

Android Programming topics we will cover here

  1. How to use Retrofit for Android Network calls
  2. How to use Retrofit logger to log your request and response object
  3. How to use android RecyclerView with Pattern pattern
  4. How to delete RecyclerView item
  5. How to use android Room, LiveData and ViewModel
  6. How to use Android text-to-speech
  7. How to use android voice recognition
  8. How to share content in android
  9. How to populate spinner widget with remote data source
  10. Clean UI

Translation App Features

  1. Intro page
  2. List of saved translations
  3. Delete saved translation
  4. About us information
  5. Translate text in up to 90 different languages
  6. Copy text
  7. Delete text
  8. Enter text with voice
  9. Convert text to speech
  10. Paste text
  11. Share translated text
  12. Save Translated text
  13. Clean design
  14. Well commented code
  15. Switch languages

Hand Sketch of Our Android Translation App

Android Translation App Screenshots

Android Translator App Video Demo

1. Create A New Android Project

  • Open Android Studio
  • Go to file menu
  • Select  new
  • Enter project name
  • Enter activity name
  • Keep other default settings
  • Click on finish button to create a new android project

2. Add Library Dependencies in Build.Gradle File

For the dependencies, we will use several android third party libraries to make our work much easier and saves time.

The dependency section of build.gradle is well commented. The comments give us an idea of what each or group of libraries do.

Open build.gradle file and paste the content below.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
    implementation 'com.google.android.material:material:1.1.0-alpha01'

    //Recycler view
    implementation 'androidx.recyclerview:recyclerview:1.0.0'

    //Room persistence data
    implementation "android.arch.persistence.room:rxjava2:1.1.1"
    implementation 'androidx.room:room-common:2.1.0-alpha03'
    implementation 'androidx.room:room-runtime:2.1.0-alpha03'
    annotationProcessor "androidx.room:room-compiler:2.1.0-alpha03"
    implementation 'android.arch.lifecycle:livedata:1.1.1'

    //Live Model
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0"
    implementation "androidx.lifecycle:lifecycle-runtime:2.0.0"
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.0.0"

    //Network call - Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0'
    implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0"

    //logging Networking call
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'

    //fast Adapter
    implementation 'com.mikepenz:fastadapter:3.3.1'

    // dependency injection
    implementation "com.google.dagger:dagger:2.16"
    annotationProcessor "com.google.dagger:dagger-compiler:2.16"
    annotationProcessor "com.google.dagger:dagger-android-processor:2.16"
    implementation "com.google.dagger:dagger-android-support:2.16"

    //Reactive
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.4'

    //debug db
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.3'

    //Runtime permission
    implementation 'com.karumi:dexter:5.0.0'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1-alpha01'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1-alpha01'
}

3. Update Colors.xml

Open res folder > strings.xml and add the code below to it.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#4992F9</color>
    <color name="colorPrimaryDark">#1E79F9</color>
    <color name="colorBackground">#6fa7f6</color>
    <color name="colorAccent">#D81B60</color>
    <color name="colorBlack">#000000</color>
    <color name="colorWhite">#ffffff</color>
    <color name="colorDark">#999999</color>
    <color name="colorDarker">#555555</color>
    <color name="colorLine">#f1f1f1</color>
    <color name="colorDarkHighlight">#AC8333</color>
    <color name="colorHighlight">#F9AE1E</color>
</resources>

4. Update Styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />

</resources>

5. Update Strings.xml

Open res folder > strings.xml and add the code below to it.

<resources>
    <string name="app_name">Android Translation</string>
    <string name="title_activity_list_translation">ListTranslationActivity</string>
</resources>

Application Project Structure

Before we go into different files we will create for this project, below is a structure I have created.

The translator app features are separated by folders. We will also include folders/packages for related classes.

Below is how the project is structured. The storetranslation and texttranslation house store translated list page and the main translator page.

  1. di folder is for dependency injection code
  2. base folder will contain BaseViewModels and BaseActivity class
  3. adapter folder will contain Adapter classes
  4. repository folder will contain Data Repository classes
  5. utils folder will contain utility classes

Get Translation API Key from Yandex

Before we go deeper into the code, we need to obtain a translation API key which we will use to send translation request to Yandex server and obtain a response which will contain our targeted language if successful or an error that will indicate that something has gone wrong.

Go to Yandex Translation Page

Click on Get Free API Key. 

You will be prompted to sign up or login if you have not done any of them. When you final login, you will be navigated to a page like below.

You will see your translation API key. Copy it and keep in a safe place. We will use it later in the project.

Please not that when you are on a free quota in Yandex you don’t need to add payment to your account. Once you add payment you will automatically lose your free quota.

What happens from here?

Since this is a long tutorial, we will focus on the core feature of android translation app and leave some other features out. Please note that the full source code is not free. If you want to download the full source code and tutorial, see more details at the end of this page.

Create a new Activity for translation

We need a new activity class and its layout file for our main translation UI.

First, create a new folder and name it texttranslation. Right click on the folder and go to New > Activity > Empty Activity.

Name the activity file TextTranslationActivity.java.

Open the activity_text_translation.xml. In the layout file, we will add all the widgets and ViewGroup our UI requires. Copy and paste the code below inside the layout file.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout_text_translate"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E0E0E0"
    tools:context=".texttranslation.TextTranslationActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.47" />

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="5dp">

            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/original_text_et"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:layout_marginBottom="8dp"
                android:background="@drawable/border"
                android:gravity="top"
                android:hint="Touch and Start Typing..."
                android:padding="8dp"
                android:scrollbars="vertical"
                android:textAppearance="@style/TextAppearance.AppCompat.Body1"
                app:layout_constraintBottom_toTopOf="@+id/paste_btn"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/paste_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline6"
                app:layout_constraintStart_toStartOf="@+id/guideline4"
                app:srcCompat="@drawable/ic_content_paste" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/cancel_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline7"
                app:layout_constraintHorizontal_bias="0.583"
                app:layout_constraintStart_toStartOf="@+id/guideline6"
                app:srcCompat="@drawable/ic_cancel" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/voice_to_text_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline8"
                app:layout_constraintStart_toStartOf="@+id/guideline7"
                app:srcCompat="@drawable/ic_mic_blue_700_24dp" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/read_aloud_original_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline5"
                app:layout_constraintStart_toStartOf="@+id/guideline8"
                app:srcCompat="@drawable/ic_volume_up" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_begin="16dp" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline5"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_end="16dp" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.25" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline7"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.5" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline8"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.75" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="5dp">

            <ScrollView
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:layout_marginBottom="8dp"
                android:fillViewport="true"
                app:layout_constraintBottom_toTopOf="@+id/copy_translation_btn"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent">


                <androidx.appcompat.widget.AppCompatTextView
                    android:id="@+id/translated_text_tv"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@drawable/border"
                    android:textColor="@color/colorBlack" />

            </ScrollView>


            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/copy_translation_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline9"
                app:layout_constraintStart_toStartOf="@+id/guideline11"
                app:srcCompat="@drawable/ic_content_copy_blue" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/share_translation_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline12"
                app:layout_constraintHorizontal_bias="0.583"
                app:layout_constraintStart_toStartOf="@+id/guideline11"
                app:srcCompat="@drawable/ic_share_blue" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/like_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline13"
                app:layout_constraintStart_toStartOf="@+id/guideline12"
                app:srcCompat="@drawable/ic_save_blue" />

            <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/read_aloud_translation_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginEnd="8dp"
                android:layout_marginLeft="8dp"
                android:layout_marginRight="8dp"
                android:layout_marginStart="8dp"
                android:clickable="true"
                app:backgroundTint="@color/colorWhite"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/guideline10"
                app:layout_constraintStart_toStartOf="@+id/guideline13"
                app:srcCompat="@drawable/ic_volume_up" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline9"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_begin="16dp" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_end="16dp" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline11"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.25" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline12"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.5" />

            <androidx.constraintlayout.widget.Guideline
                android:id="@+id/guideline13"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintGuide_percent="0.75" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/translate_text__btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        app:layout_constraintBottom_toTopOf="@+id/cardView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/cardView"
        app:srcCompat="@drawable/ic_send_white" />

</androidx.constraintlayout.widget.ConstraintLayout>

The drawable icons used in some of the widgets can be obtain from the drawable folder or you can create your own icons. If everything works well with no error, you will get a UI similar to the image below.

Open the TextTranslationActivity class, we will get instances of the widgets we added in the layout file. In other to avoid repetition and boilerplate code, we will use ButterKnife library for View binding and event listeners.

Copy and paste the code below to your android app. As you can see, the code is well commented and very explanatory.

package com.inducesmile.androidtranslation.texttranslation;

import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.inducesmile.androidtranslation.R;
import com.inducesmile.androidtranslation.repository.api.model.YandexResponse;
import com.inducesmile.androidtranslation.repository.database.model.TranslationObject;
import com.inducesmile.androidtranslation.texttranslation.dialog.SaveDialog;
import com.inducesmile.androidtranslation.utils.CommonUtils;
import com.inducesmile.androidtranslation.utils.Constants;
import com.inducesmile.androidtranslation.utils.CopyPasteText;
import com.inducesmile.androidtranslation.utils.ShareUtils;
import com.inducesmile.androidtranslation.utils.TextToVoice;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import javax.inject.Inject;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.Observer;
import dagger.android.AndroidInjection;

public class TextTranslationActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = TextTranslationActivity.class.getSimpleName();


    private AppCompatEditText mOriginalText;
    private AppCompatTextView mTranslatedText;

    private ConstraintLayout mLayout;

    private String originalText = "";
    private String translatedText = "";
    private String transAbbr = "en";
    private String origAbbr = "en";

    private FloatingActionButton pasteText, deleteText, speakText, readText, copyText, shareText,
                                 favoriteText, translateReadText, convertText;

    private TextToSpeech tts;

    private ProgressDialog mProgressDialog;

    private CopyPasteText copyPasteText;

    private Spinner spinner, translationSpinner;

    @Inject
    TextTranslationViewModel textTranslationViewModel;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_translation);

        setTitle("");
        AndroidInjection.inject(this);

        copyPasteText = new CopyPasteText(this);

        mOriginalText = (AppCompatEditText)findViewById(R.id.original_text_et);
        mTranslatedText = (AppCompatTextView)findViewById(R.id.translated_text_tv);

        pasteText = (FloatingActionButton)findViewById(R.id.paste_btn);
        deleteText = (FloatingActionButton)findViewById(R.id.cancel_btn);
        speakText = (FloatingActionButton)findViewById(R.id.voice_to_text_btn);
        readText = (FloatingActionButton)findViewById(R.id.read_aloud_original_btn);

        copyText = (FloatingActionButton)findViewById(R.id.copy_translation_btn);
        shareText = (FloatingActionButton)findViewById(R.id.share_translation_btn);
        favoriteText = (FloatingActionButton)findViewById(R.id.like_btn);
        translateReadText = (FloatingActionButton)findViewById(R.id.read_aloud_translation_btn);

        convertText = (FloatingActionButton)findViewById(R.id.translate_text__btn);

        pasteText.setOnClickListener(this);
        deleteText.setOnClickListener(this);
        speakText.setOnClickListener(this);
        readText.setOnClickListener(this);

        copyText.setOnClickListener(this);
        shareText.setOnClickListener(this);
        favoriteText.setOnClickListener(this);
        translateReadText.setOnClickListener(this);

        convertText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userTextInput = getOriginalText();
                List<String> list = Arrays.asList(Constants.YANDEX_TRANSLATE_ABBR);
                if(TextUtils.isEmpty(userTextInput)){
                    Toast.makeText(TextTranslationActivity.this, "Content to translate cannot be empty ...", Toast.LENGTH_SHORT).show();
                }else if(TextUtils.isEmpty(transAbbr)){
                    Toast.makeText(TextTranslationActivity.this, "Target Language is empty", Toast.LENGTH_SHORT).show();
                }
                else if(!list.contains(transAbbr)) {
                    Toast.makeText(TextTranslationActivity.this, "Target Language was not field", Toast.LENGTH_SHORT).show();
                }else {
                    textTranslationViewModel.makeApiTranslationCall(userTextInput, transAbbr);
                }
            }
        });

        watchTranslatedText();
    }


    //Watch translated text for change
    private void watchTranslatedText(){
        textTranslationViewModel.getYandexResponse().observe(this, new Observer<YandexResponse>() {
            @Override
            public void onChanged(YandexResponse yandexResponse) {
                if (yandexResponse != null) {
                    translatedText = String.valueOf(yandexResponse.getText());
                    translatedText = translatedText.replaceAll("\\p{P}", "");
                    mTranslatedText.setText(translatedText);
                }
            }
        });
    }

    private boolean isOriginalTextExist(){
        String textContent = Objects.requireNonNull(mOriginalText.getText()).toString();
        return TextUtils.isEmpty(textContent);
    }

    private boolean isTranslatedTextExist(){
        String textContent = Objects.requireNonNull(mTranslatedText.getText()).toString();
        return TextUtils.isEmpty(textContent);
    }


    private String getOriginalText(){
        return Objects.requireNonNull(mOriginalText.getText()).toString();
    }


    private String getTranslatedText(){
        return Objects.requireNonNull(mTranslatedText.getText()).toString();
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.text_translate_menu, menu);

        MenuItem item = menu.findItem(R.id.original_lang_spinner);
        populateFirstSpinner(item);

        MenuItem item2 = menu.findItem(R.id.translate_lang_spinner);
        populateSecondSpinner(item2);

        return super.onCreateOptionsMenu(menu);
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int clickItem = item.getItemId();
        if(clickItem == R.id.switch_item){

            //Switch selected language
            String firstText = (String)spinner.getSelectedItem();
            String secondText = (String)translationSpinner.getSelectedItem();
            int firstIndex = CommonUtils.getLanguageIndexPosition(firstText);
            int secondIndex = CommonUtils.getLanguageIndexPosition(secondText);

            spinner.setSelection(secondIndex);
            translationSpinner.setSelection(firstIndex);

            // switch language abbreviation
            String tempAbbreviation = origAbbr;
            transAbbr = origAbbr;
            origAbbr = tempAbbreviation;

            //switching text content
            mOriginalText.setText(getTranslatedText());
            mTranslatedText.setText(getOriginalText());


        }
        return super.onOptionsItemSelected(item);
    }



    //Language option for original text
    private void populateFirstSpinner(MenuItem item){
        spinner = (Spinner) item.getActionView();
        ArrayAdapter<String> languageAdapter = new ArrayAdapter<String>(this, R.layout.custom_spinner_item, CommonUtils.sortedListString());

        languageAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(languageAdapter);
        spinner.setSelection(CommonUtils.getEnglishLanguagePosition());

        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
                String item = parent.getItemAtPosition(position).toString();

                int index = Arrays.asList(Constants.YANDEX_TRANSLATE_SUPPORTED_LANGUAGE).indexOf(item);
                origAbbr = Constants.YANDEX_TRANSLATE_ABBR[index];
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }


    //Language option for translation
    private void populateSecondSpinner(MenuItem item){
        translationSpinner = (Spinner) item.getActionView();
        ArrayAdapter<String> translationAdapter = new ArrayAdapter<String>(this, R.layout.custom_spinner_item, CommonUtils.sortedListString());

        translationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        translationSpinner.setAdapter(translationAdapter);
        translationSpinner.setSelection(CommonUtils.getEnglishLanguagePosition());

        translationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
                String item = parent.getItemAtPosition(position).toString();

                int index = Arrays.asList(Constants.YANDEX_TRANSLATE_SUPPORTED_LANGUAGE).indexOf(item);
                transAbbr = Constants.YANDEX_TRANSLATE_ABBR[index];
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }

    
    //All button options used in the translation app
    @Override
    public void onClick(View v) {
        FloatingActionButton floatingActionButton = (FloatingActionButton)v;
        int buttonId = floatingActionButton.getId();

        switch (buttonId){
            //paste translation text
            case R.id.paste_btn:
                String copiedContext = copyPasteText.pasteTextContent();
                if(TextUtils.isEmpty(copiedContext)){
                    Toast.makeText(TextTranslationActivity.this, "Paste content is empty ", Toast.LENGTH_SHORT).show();
                }else{
                    mOriginalText.setText(copiedContext);
                }
                break;
                //Delete translation text
            case R.id.cancel_btn:
                mOriginalText.setText("");
                break;
                //context translation text to voice
            case R.id.voice_to_text_btn:
                askSpeechInput();
                break;
                //text to sppech
            case R.id.read_aloud_original_btn:
                if(isOriginalTextExist()){
                    Toast.makeText(TextTranslationActivity.this, "Text is empty ", Toast.LENGTH_SHORT).show();
                }else{
                    TextToVoice textToVoice = new TextToVoice(TextTranslationActivity.this);
                    textToVoice.turnTextToSpeech(getOriginalText());
                }
                break;
                //copy translation text
            case R.id.copy_translation_btn:
                if(isTranslatedTextExist()){
                    Toast.makeText(TextTranslationActivity.this, "Translation text is empty ", Toast.LENGTH_SHORT).show();
                }else{
                    copyPasteText.copyTextContent(getTranslatedText());
                }
                break;
                //share translation text
            case R.id.share_translation_btn:
                ShareUtils.shareTranslatedText(TextTranslationActivity.this, getOriginalText(), getTranslatedText());
                break;
            case R.id.like_btn:
                //The like id was changed to save.
                if(TextUtils.isEmpty(getOriginalText()) || TextUtils.isEmpty(getTranslatedText())){
                    Toast.makeText(TextTranslationActivity.this, "Translation text is empty ", Toast.LENGTH_SHORT).show();
                }else{
                    SaveDialog deleteDialog = new SaveDialog(TextTranslationActivity.this);
                    TranslationObject saveObject = new TranslationObject(getOriginalText(), getTranslatedText());
                    deleteDialog.addNewMessage(textTranslationViewModel, saveObject);
                }

                break;
            case R.id.read_aloud_translation_btn:
                if (isTranslatedTextExist()) {
                    Toast.makeText(TextTranslationActivity.this, "Text is empty ", Toast.LENGTH_SHORT).show();
                } else {
                    TextToVoice textToVoice = new TextToVoice(TextTranslationActivity.this);
                    textToVoice.turnTextToSpeech(getTranslatedText());
                }
                break;
                default:
                    break;
        }
    }


    //Android speech input
    private void askSpeechInput() {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Hi speak something");
        try {
            startActivityForResult(intent, Constants.REQ_CODE_SPEECH_INPUT);
        } catch (ActivityNotFoundException a) {
            Log.d(TAG, "Error " + a.getMessage());
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
            case Constants.REQ_CODE_SPEECH_INPUT: {
                if (resultCode == RESULT_OK && null != data) {
                    ArrayList<String> result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                    mOriginalText.setText(result.get(0));
                }
                break;
            }
        }
    }

}

As can be seen on the code, we have injected a View Model class

@Inject
TextTranslationViewModel textTranslationViewModel;

We will need to create a this in the same folder. It will inherit from android ViewModel class

The ViewModel class is responsible for analyzing and preparing our remote data from the currency API.

Create a ViewModel Class

Create a new ViewModel class and name it TextTranslationViewModel. Open the class and add the code below.

public class TextTranslationViewModel extends BaseViewModel<ITextInterface> {

    private static final String TAG = TextTranslationViewModel.class.getSimpleName();

    private final MutableLiveData<YandexResponse> mYandexResponse = new MutableLiveData<>();

    private final MutableLiveData<Boolean> hasSavedText = new MutableLiveData<>();


    public TextTranslationViewModel(DataManager dataManager) {
        super(dataManager);
    }


    public MutableLiveData<YandexResponse> getYandexResponse(){
        return mYandexResponse;
    }


    public void makeApiTranslationCall(String original, String target){
        setIsLoading(true);
        Observable<YandexResponse> returnObject = getDataManager().getTranslatedTextUsingYandexApi(ApiEndPoint.YANDEX_API_KEY, original, target);
        getCompositeDisposable().add(returnObject.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<YandexResponse>() {
                    @Override
                    public void accept(YandexResponse yandexResponse) throws Exception {
                        setIsLoading(false);
                        Log.d(TAG, "The value of the return type is " + yandexResponse.getText());
                        mYandexResponse.setValue(yandexResponse);
                    }
                }, throwable -> {
                    setIsLoading(false);
                    Log.d(TAG, "Error has occurred ");
                })
        );
    }


    public void saveTranslatedText(TranslationObject translationObject){
        Observable<Boolean> isTextSaved = getDataManager().addTranslationObject(translationObject);
        getCompositeDisposable().add(isTextSaved.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean aBoolean) throws Exception {
                        Log.d(TAG, "New data inserted ");
                    }
                }, throwable -> {
                    Log.d(TAG, "Error has occurred ");
                })
        );
    }
}

WHAT WE HAVE NOW

Like I said from the beginner, we will only cover the core feature of the translator app and the code above has achieved that. If you want to download the complete source code see the section below.

 

 HOW TO DOWNLOAD THE COMPLETE SOURCE CODE

You can follow this tutorial and complete the tutorial with the code snippets provided in the tutorial.

The source code is not free to download because we spent a lot of time to create these apps from scratch and your little support will help us to even create more apps from scratch.

Buy

Source Code Customization

If you like the source code and will also like to customize its look and feel or add and remove some features, kindly contact us using the comment section below or through our contact page.

Please note that we charge $20 per hour for customization.

Summary

We have walk through the core feature of android translator app and I guess you have a better insight on how to continue to improve your source code.

I will suggest you buy the source code to learn how other features were implemented.

If you have questions and suggestions, kindly use the comment box below to get in touch

I look forward in seeing you in our next build from scratch episode.

Add a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.