Google MLKIT Natural Language Translation App | Android Development
With ML Kit’s on-device translation API, you can dynamically translate text between more than 50 languages.
- Broad language support Translate between more than 50 different languages. See the complete list.
- Proven translation models Powered by the same models used by the Google Translate app’s offline mode.
- Dynamic model management Keep on-device storage requirements low by dynamically downloading and managing language packs.
- Runs on the device Translations are performed quickly, and don’t require you to send users’ text to a remote server.
🟢 ANDROID WITH MACHINE LEARNING! (COURSE)
🟢 KOTLIN INTERVIEW BOOTCAMP! (COURSE)
Adapter
Started implementing a TranslationLanguageAdapter
class for a RecyclerView in Android using Kotlin. The code you've provided defines the adapter and a nested LanguageViewHolder
. The LanguageViewHolder
is responsible for binding the data to the corresponding views using data binding.
class TranslationLanguageAdapter(
private val languages: List<SupportedLanguages>,
private val onItemClick: (SupportedLanguages) -> Unit
) : RecyclerView.Adapter<TranslationLanguageAdapter.LanguageViewHolder>() {
private var selectedItemPosition: Int = RecyclerView.NO_POSITION
class LanguageViewHolder(
private val binding: ItemTranslationLanguageBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(supportedLanguages: SupportedLanguages) {
binding.language = supportedLanguages
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LanguageViewHolder {
val binding = ItemTranslationLanguageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return LanguageViewHolder(binding)
}
override fun onBindViewHolder(holder: LanguageViewHolder, position: Int) {
val language = languages[position]
holder.bind(language)
// Set the background based on the selected item
if (position == selectedItemPosition) {
holder.itemView.setBackgroundResource(R.drawable.background_clicked_button)
} else {
holder.itemView.setBackgroundResource(R.drawable.background_unclicked_button)
}
holder.itemView.setOnClickListener {
// Update the selected item position
val previousSelectedItemPosition = selectedItemPosition
selectedItemPosition = holder.adapterPosition
// Notify the adapter about the item change to update backgrounds
notifyItemChanged(previousSelectedItemPosition)
notifyItemChanged(selectedItemPosition)
onItemClick.invoke(language)
}
}
override fun getItemCount(): Int {
return languages.size
}
Enum Class
The provided Kotlin code defines an enum class named SupportedLanguages
that encapsulates information about various languages. Each language is represented by an enum entry, and each entry has three properties:
langEnglish
: Represents the English name of the language.langNative
: Represents the native name of the language.lanCode
: Represents the language code associated with the language.
enum class SupportedLanguages(val langEnglish: String, val langNative: String, val lanCode: String) {
English("English", "English", "en"),
German("German", "Deutsch", "de"),
Turkish("Turkish", "Türkçe", "tr"),
Spanish("Spanish", "español", "es"),
French("French", "français", "fr"),
Italian("Italian", "italiano", "it"),
Irish("Irish", "Gaeilge", "ga"),
Japanese("Japanese", "日本語", "ja"),
Korean("Korean", "한국어", "ko"),
Dutch("Dutch", "Nederlands", "nl"),
Chinese("Chinese", "中文", "zh"),
Norwegian("Norwegian", "norsk", "no"),
Polish("Polish", "polski", "pl"),
Afrikaans("Afrikaans", "Afrikaans", "af"),
Arabic("Arabic", "العربية", "ar"),
Belarusian("Belarusian", "беларуская", "be"),
Bulgarian("Bulgarian", "български", "bg"),
Bengali("Bengali", "বাংলা", "bn"),
Catalan("Catalan", "català", "ca"),
Czech("Czech", "čeština", "cs"),
Welsh("Welsh", "Cymraeg", "cy"),
Danish("Danish", "dansk", "da"),
Greek("Greek", "ελληνικά", "el"),
Estonian("Estonian", "eesti", "et"),
Persian("Persian", "فارسى", "fa"),
Finnish("Finnish", "suomi", "fi"),
Galician("Galician", "galego", "gl"),
Gujarati("Gujarati", "ગુજરાતી", "gu"),
Hebrew("Hebrew", "עברית", "he"),
Hindi("Hindi", "हिंदी", "hi"),
Croatian("Croatian", "hrvatski", "hr"),
Hungarian("Hungarian", "magyar", "hu"),
Indonesian("Indonesian", "Bahasa Indonesia", "id"),
Icelandic("Icelandic", "íslenska", "is"),
Georgian("Georgian", "ქართული", "ka"),
Kannada("Kannada", "ಕನ್ನಡ", "kn"),
Lithuanian("Lithuanian", "lietuvių", "lt"),
Latvian("Latvian", "latviešu", "lv"),
Macedonian("Macedonian", "македонски јазик", "mk"),
Marathi("Marathi", "मराठी", "mr"),
Malay("Malay", "Bahasa Malaysia", "ms"),
Maltese("Maltese", "Malti", "mt"),
Portuguese("Portuguese", "Português", "pt"),
Romanian("Romanian", "română", "ro"),
Russian("Russian", "русский", "ru"),
Slovak("Slovak", "slovenčina", "sk"),
Slovenian("Slovenian", "slovenski", "sl"),
Albanian("Albanian", "shqipe", "sq"),
Swedish("Swedish", "svenska", "sv"),
Swahili("Kiswahili", "Kiswahili", "sw"),
Tamil("Tamil", "தமிழ்", "ta"),
Telugu("Telugu", "తెలుగు", "te"),
Thai("Thai", "ไทย", "th"),
Ukrainian("Ukrainian", "українська", "uk"),
Urdu("Urdu", "اُردو", "ur"),
Vietnamese("Vietnamese", "Tiếng Việt", "vi"),
}
Translation Resource
The provided Kotlin code defines a sealed class named TranslationResource<T>
. This class is commonly used for representing the different states that can occur during the process of fetching or processing data, such as success, loading, or encountering an error.
sealed class TranslationResource<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T) : TranslationResource<T>(data)
class Loading<T>(data: T? = null) : TranslationResource<T>(data)
class Error<T>(message: String, data: T? = null) : TranslationResource<T>(data, message)
}
ViewModel
The TranslationViewModel
class you provided is a part of the Android Architecture Components and is designed to handle translation-related logic in an Android application. It uses the ViewModel
class to manage and persist UI-related data across configuration changes.
This ViewModel is designed to be used with an associated UI component (like an Activity or Fragment) that observes the translateData
LiveData to update the user interface based on the translation state. The use of the ViewModel
class ensures that the translation-related data is retained during configuration changes, providing a robust and lifecycle-aware solution.
class TranslationViewModel : ViewModel() {
private val translate = MutableLiveData<TranslationResource<String?>>()
val translateData: LiveData<TranslationResource<String?>>
get() = translate
fun processTranslation(translateText: String, languageSource: String, languageTarget: String) {
translate.postValue(TranslationResource.Loading())
val options = TranslatorOptions.Builder()
.setSourceLanguage(languageSource)
.setTargetLanguage(languageTarget)
.build()
val translator = Translation.getClient(options)
val conditions = DownloadConditions.Builder()
.requireWifi()
.build()
translator.downloadModelIfNeeded(conditions)
.addOnSuccessListener {
translator.translate(translateText)
.addOnSuccessListener { translatedText ->
translate.postValue(TranslationResource.Success(translatedText))
}
.addOnFailureListener { exception ->
translate.postValue(TranslationResource.Error("Cannot be translate!"))
}
}
.addOnFailureListener { exception ->
translate.postValue(TranslationResource.Error("Model couldn’t be downloaded! Internet connection required."))
}
}
}
Fragment
The TranslationFragment
class represents a fragment in an Android application responsible for displaying a user interface related to translation. It uses the Android Architecture Components, including ViewModel, LiveData, and data binding, to handle translation-related logic and UI updates. Here's a breakdown of the code:
class TranslationFragment : Fragment() {
private var _binding: FragmentTranslationBinding? = null
private val binding get() = _binding!!
private val viewModel: TranslationViewModel by viewModels()
private var languageCodeSource: String? = null
private var languageCodeTarget: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentTranslationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeViewModel()
handleUIElements()
}
private fun handleUIElements() {
with(binding) {
// RecyclerView
recyclerViewSource.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
val sourceAdapter = TranslationLanguageAdapter(SupportedLanguages.entries) { selectedLanguage ->
languageCodeSource = selectedLanguage.lanCode
Toast.makeText(requireContext(), "Selected: ${selectedLanguage.langNative}", Toast.LENGTH_SHORT).show()
}
recyclerViewSource.adapter = sourceAdapter
recyclerViewTarget.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
val targetAdapter = TranslationLanguageAdapter(SupportedLanguages.entries) { selectedLanguage ->
languageCodeTarget = selectedLanguage.lanCode
Toast.makeText(requireContext(), "Selected: ${selectedLanguage.langNative}", Toast.LENGTH_SHORT).show()
}
recyclerViewTarget.adapter = targetAdapter
// Scroll Result Text
textViewTranslation.movementMethod = ScrollingMovementMethod()
buttonCopyTranslate.setOnClickListener { copyTextToClipboard() }
val textChangeListener = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
if (languageCodeSource != null && languageCodeTarget != null) {
Handler(Looper.getMainLooper()).postDelayed({
viewModel.processTranslation(s.toString(), languageCodeSource!!, languageCodeTarget!!)
buttonCopyTranslate.visibility = if (s.isNullOrEmpty()) View.INVISIBLE else View.VISIBLE
}, 500)
} else {
Toast.makeText(requireContext(), "Select Source & Target Language!", Toast.LENGTH_SHORT).show()
}
}
}
textInputTranslation.addTextChangedListener(textChangeListener)
}
}
private fun observeViewModel() {
viewModel.translateData.observe(viewLifecycleOwner, ::setTranslatedText)
}
private fun setTranslatedText(translationResource: TranslationResource<String?>?) {
when(translationResource) {
is TranslationResource.Success -> {
binding.circularProgress.visibility = View.INVISIBLE
translationResource.data?.let {
binding.textViewTranslation.text = it
}
}
is TranslationResource.Error -> {
binding.circularProgress.visibility = View.INVISIBLE
translationResource.data?.let {
binding.textViewTranslation.text = it
}
}
is TranslationResource.Loading -> {
binding.circularProgress.visibility = View.VISIBLE
}
else -> {
binding.circularProgress.visibility = View.INVISIBLE
binding.textViewTranslation.text = "null"
}
}
}
private fun copyTextToClipboard() {
val clipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Copied Text", binding.textViewTranslation.text.toString())
clipboardManager.setPrimaryClip(clip)
Toast.makeText(requireContext(), "Translation copied to clipboard!", Toast.LENGTH_SHORT).show()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Design
The XML layout file you provided represents the UI layout for the TranslationFragment
. It uses a LinearLayout
as the root layout, and here's a breakdown of its structure:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:layout_marginBottom="?android:actionBarSize"
android:orientation="vertical"
tools:context=".identification.LanguageIdentificationFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewSource"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:listitem="@layout/item_translation_language" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textLayoutTranslation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:hint="Enter Text Translate!"
android:padding="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/textInputTranslation"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/poppins_regular"
android:maxLength="2000"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewTarget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:listitem="@layout/item_translation_language" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="10dp"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textViewTranslation"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_border_textview"
android:fontFamily="@font/poppins_regular"
android:hint="The text to be translated will appear here!"
android:padding="15dp"
android:scrollbars="vertical"
android:textSize="14sp" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circularProgress"
style="?attr/circularProgressIndicatorStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="invisible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/buttonCopyTranslate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@android:color/transparent"
android:padding="10dp"
android:src="@drawable/icon_copy"
android:tint="@color/material_dynamic_tertiary60"
android:visibility="invisible" />
</FrameLayout>
</LinearLayout>