From 7de77ca073607982b94a4f9cc915d47300e77038 Mon Sep 17 00:00:00 2001 From: Julius Linus Date: Wed, 4 Oct 2023 08:35:55 -0500 Subject: [PATCH] Adjusting Translation Provider - Added 4 new model data classes - Added the new API function to NcApi - Implemented the changes in the Repository + ViewModel - Implemented the changes in the Activity - Added some helper functions to support impl Signed-off-by: Julius Linus --- .../java/com/nextcloud/talk/api/NcApi.java | 5 ++ .../repositories/TranslateRepository.kt | 6 ++ .../repositories/TranslateRepositoryImpl.kt | 5 ++ .../translate/repositories/model/Language.kt | 41 ++++++++++ .../repositories/model/LanguagesData.kt | 37 +++++++++ .../repositories/model/LanguagesOCS.kt | 38 ++++++++++ .../repositories/model/LanguagesOverall.kt | 35 +++++++++ .../repositories/model/TranslateOCS.kt | 2 +- .../talk/translate/ui/TranslateActivity.kt | 75 +++++++++++-------- .../viewmodels/TranslateViewModel.kt | 41 +++++++++- .../talk/ui/dialog/MessageActionsDialog.kt | 4 +- .../com/nextcloud/talk/utils/ApiUtils.java | 4 + .../database/user/CapabilitiesUtilNew.kt | 17 +---- app/src/main/res/values/strings.xml | 2 + 14 files changed, 259 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/translate/repositories/model/Language.kt create mode 100644 app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesData.kt create mode 100644 app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOCS.kt create mode 100644 app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOverall.kt diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 7e8f3451a3..cacc4c0b4d 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -49,6 +49,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileOverall; import com.nextcloud.talk.polls.repositories.model.PollOverall; +import com.nextcloud.talk.translate.repositories.model.LanguagesOverall; import com.nextcloud.talk.translate.repositories.model.TranslationsOverall; import java.util.List; @@ -675,6 +676,10 @@ Observable translateMessage(@Header("Authorization") String @Query("toLanguage") String toLanguage, @Nullable @Query("fromLanguage") String fromLanguage); + @GET + Observable getLanguages(@Header("Authorization") String authorization, + @Url String url); + @GET Observable getReminder(@Header("Authorization") String authorization, @Url String url); diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepository.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepository.kt index 0e925aee83..7692cae58a 100644 --- a/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepository.kt @@ -1,5 +1,6 @@ package com.nextcloud.talk.translate.repositories +import com.nextcloud.talk.translate.repositories.model.Language import io.reactivex.Observable interface TranslateRepository { @@ -11,4 +12,9 @@ interface TranslateRepository { toLanguage: String, fromLanguage: String? ): Observable + + fun getLanguages( + authorization: String, + url: String + ): Observable> } diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepositoryImpl.kt index 9a81d9248f..8828f33aba 100644 --- a/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/TranslateRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.nextcloud.talk.translate.repositories import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.translate.repositories.model.Language import io.reactivex.Observable import javax.inject.Inject @@ -15,4 +16,8 @@ class TranslateRepositoryImpl @Inject constructor(private val ncApi: NcApi) : Tr ): Observable { return ncApi.translateMessage(authorization, url, text, toLanguage, fromLanguage).map { it.ocs?.data!!.text } } + + override fun getLanguages(authorization: String, url: String): Observable> { + return ncApi.getLanguages(authorization, url).map { it.ocs?.data?.languages } + } } diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/model/Language.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/Language.kt new file mode 100644 index 0000000000..15287dab27 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/Language.kt @@ -0,0 +1,41 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.translate.repositories.model + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class Language( + @JsonField(name = ["from"]) + var from: String?, + @JsonField(name = ["fromLabel"]) + var fromLabel: String?, + @JsonField(name = ["to"]) + var to: String?, + @JsonField(name = ["toLabel"]) + var toLabel: String? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesData.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesData.kt new file mode 100644 index 0000000000..3cd2f22066 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesData.kt @@ -0,0 +1,37 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.translate.repositories.model + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class LanguagesData( + @JsonField(name = ["languageDetection"]) + var languageDetection: Boolean?, + @JsonField(name = ["languages"]) + var languages: List? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOCS.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOCS.kt new file mode 100644 index 0000000000..f8c88edd83 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOCS.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.translate.repositories.model + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.models.json.generic.GenericMeta +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class LanguagesOCS( + @JsonField(name = ["meta"]) + var meta: GenericMeta?, + @JsonField(name = ["data"]) + var data: LanguagesData? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOverall.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOverall.kt new file mode 100644 index 0000000000..1e8a35b900 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/LanguagesOverall.kt @@ -0,0 +1,35 @@ +/* + * Nextcloud Talk application + * + * @author Julius Linus + * Copyright (C) 2023 Julius Linus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.translate.repositories.model + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +class LanguagesOverall( + @JsonField(name = ["ocs"]) + var ocs: LanguagesOCS? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/translate/repositories/model/TranslateOCS.kt b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/TranslateOCS.kt index 66cde16d3b..617eb20a18 100644 --- a/app/src/main/java/com/nextcloud/talk/translate/repositories/model/TranslateOCS.kt +++ b/app/src/main/java/com/nextcloud/talk/translate/repositories/model/TranslateOCS.kt @@ -27,7 +27,7 @@ import kotlinx.parcelize.Parcelize @Parcelize @JsonObject -data class TranslateOCS( // TODO finish this model +data class TranslateOCS( @JsonField(name = ["meta"]) var meta: GenericMeta?, @JsonField(name = ["data"]) diff --git a/app/src/main/java/com/nextcloud/talk/translate/ui/TranslateActivity.kt b/app/src/main/java/com/nextcloud/talk/translate/ui/TranslateActivity.kt index a65dd45db7..9ba98ee99b 100644 --- a/app/src/main/java/com/nextcloud/talk/translate/ui/TranslateActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/translate/ui/TranslateActivity.kt @@ -28,6 +28,7 @@ import android.content.Context import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.text.method.ScrollingMovementMethod +import android.util.Log import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter @@ -38,14 +39,14 @@ import com.nextcloud.talk.R import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.databinding.ActivityTranslateBinding +import com.nextcloud.talk.translate.repositories.model.Language import com.nextcloud.talk.translate.viewmodels.TranslateViewModel import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.bundle.BundleKeys -import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew -import org.json.JSONArray import java.util.Locale import javax.inject.Inject +@Suppress("TooManyFunctions") @AutoInjector(NextcloudTalkApplication::class) class TranslateActivity : BaseActivity() { @@ -60,6 +61,7 @@ class TranslateActivity : BaseActivity() { private var toLanguages: Array? = null private var fromLanguages: Array? = null + private var languages: List? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -79,8 +81,20 @@ class TranslateActivity : BaseActivity() { onTranslatedState(state.msg) } - is TranslateViewModel.ErrorState -> { - onErrorState() + is TranslateViewModel.TranslationErrorState -> { + onTranslationErrorState() + } + + is TranslateViewModel.LanguagesErrorState -> { + onLanguagesErrorState() + } + + is TranslateViewModel.LanguagesRetrievedState -> { + Log.d(TAG, "Languages are: ${state.list}") + languages = state.list + getLanguageOptions() + setupSpinners() + setItems() } } } @@ -88,8 +102,7 @@ class TranslateActivity : BaseActivity() { setContentView(binding.root) setupSystemColors() setupTextViews() - getLanguageOptions() - setupSpinners() + viewModel.getLanguages() setupCopyButton() if (savedInstanceState == null) { @@ -102,7 +115,7 @@ class TranslateActivity : BaseActivity() { override fun onResume() { super.onResume() - setItems() + languages?.let { setItems() } } override fun onSaveInstanceState(outState: Bundle) { outState.run { @@ -155,19 +168,16 @@ class TranslateActivity : BaseActivity() { } private fun getLanguageOptions() { - val currentUser = userManager.currentUser.blockingGet() - val json = JSONArray((CapabilitiesUtilNew.getLanguages(currentUser) as ArrayList<*>).toArray()) - val fromLanguagesSet = mutableSetOf(resources.getString(R.string.translation_detect_language)) val toLanguagesSet = mutableSetOf(resources.getString(R.string.translation_device_settings)) - for (i in 0 until json.length()) { - val current = json.getJSONObject(i) - if (current.getString(FROM_ID) != Locale.getDefault().language) { - toLanguagesSet.add(current.getString(FROM_LABEL)) + for (language in languages!!) { + val locale = Locale.getDefault().language + if (language.from != locale) { + toLanguagesSet.add(language.fromLabel!!) } - fromLanguagesSet.add(current.getString(TO_LABEL)) + fromLanguagesSet.add(language.toLabel!!) } toLanguages = toLanguagesSet.toTypedArray() @@ -179,7 +189,7 @@ class TranslateActivity : BaseActivity() { binding.toLanguageInputLayout.isEnabled = value } - private fun showDialog() { + private fun showDialog(titleInt: Int, messageInt: Int) { val dialogBuilder = MaterialAlertDialogBuilder(this@TranslateActivity) .setIcon( viewThemeUtils.dialog.colorMaterialAlertDialogIcon( @@ -187,8 +197,8 @@ class TranslateActivity : BaseActivity() { R.drawable.ic_warning_white ) ) - .setTitle(R.string.translation_error_title) - .setMessage(R.string.translation_error_message) + .setTitle(titleInt) + .setMessage(messageInt) .setPositiveButton(R.string.nc_ok) { dialog, _ -> dialog.dismiss() } @@ -210,18 +220,15 @@ class TranslateActivity : BaseActivity() { return getISOFromServer(language) } - private fun getISOFromServer(language: String): String { - val currentUser = userManager.currentUser.blockingGet() - val json = JSONArray((CapabilitiesUtilNew.getLanguages(currentUser) as ArrayList<*>).toArray()) - - for (i in 0 until json.length()) { - val current = json.getJSONObject(i) - if (current.getString(FROM_LABEL) == language) { - return current.getString(FROM_ID) + private fun getISOFromServer(label: String): String { + var result = "" + for (language in languages!!) { + if (language.fromLabel == label) { + result = language.from!! } } - return "" + return result } private fun setupSpinners() { @@ -279,15 +286,19 @@ class TranslateActivity : BaseActivity() { enableSpinners(true) } - private fun onErrorState() { + private fun onTranslationErrorState() { + binding.progressBar.visibility = View.GONE + enableSpinners(true) + showDialog(R.string.translation_error_title, R.string.translation_error_message) + } + + private fun onLanguagesErrorState() { binding.progressBar.visibility = View.GONE enableSpinners(true) - showDialog() + showDialog(R.string.languages_error_title, R.string.languages_error_message) } companion object { - private const val FROM_ID = "from" - private const val FROM_LABEL = "fromLabel" - private const val TO_LABEL = "toLabel" + val TAG = TranslateActivity::class.simpleName } } diff --git a/app/src/main/java/com/nextcloud/talk/translate/viewmodels/TranslateViewModel.kt b/app/src/main/java/com/nextcloud/talk/translate/viewmodels/TranslateViewModel.kt index a8952fde91..dc5a439fb8 100644 --- a/app/src/main/java/com/nextcloud/talk/translate/viewmodels/TranslateViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/translate/viewmodels/TranslateViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.translate.repositories.TranslateRepository +import com.nextcloud.talk.translate.repositories.model.Language import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils import io.reactivex.Observer @@ -21,9 +22,14 @@ class TranslateViewModel @Inject constructor( sealed interface ViewState - object StartState : ViewState + data object StartState : ViewState class TranslatedState(val msg: String) : ViewState - object ErrorState : ViewState + + class LanguagesRetrievedState(val list: List) : ViewState + + data object LanguagesErrorState : ViewState + + data object TranslationErrorState : ViewState private val _viewState: MutableLiveData = MutableLiveData(StartState) val viewState: LiveData @@ -47,6 +53,35 @@ class TranslateViewModel @Inject constructor( ?.subscribe(TranslateObserver()) } + fun getLanguages() { + val currentUser: User = userManager.currentUser.blockingGet() + val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token) + val url: String = ApiUtils.getUrlForLanguages(currentUser.baseUrl) + Log.d(TAG, "URL is: $url") + repository.getLanguages(authorization, url) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer> { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onError(e: Throwable) { + _viewState.value = LanguagesErrorState + Log.e(TAG, "Error while retrieving languages: $e") + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(list: List) { + _viewState.value = LanguagesRetrievedState(list) + Log.d(TAG, "Languages retrieved: $list") + } + }) + } + inner class TranslateObserver : Observer { override fun onSubscribe(d: Disposable) { _viewState.value = StartState @@ -57,7 +92,7 @@ class TranslateViewModel @Inject constructor( } override fun onError(e: Throwable) { - _viewState.value = ErrorState + _viewState.value = TranslationErrorState Log.e(TAG, "Error while translating message", e) } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 1cd258656f..d487aff281 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -57,7 +57,6 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers -import org.json.JSONArray import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) @@ -94,8 +93,7 @@ class MessageActionsDialog( initMenuItemTranslate( !message.isDeleted && ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && - CapabilitiesUtilNew.isTranslationsSupported(user) && - JSONArray((CapabilitiesUtilNew.getLanguages(user) as ArrayList<*>).toArray()).length() > 0 + CapabilitiesUtilNew.isTranslationsSupported(user) ) initMenuReplyToMessage(message.replyable && hasChatPermission) initMenuReplyPrivately( diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java index 9cac574364..75f520046f 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java @@ -532,6 +532,10 @@ public static String getUrlForTranslation(String baseUrl) { return baseUrl + ocsApiVersion + "/translation/translate"; } + public static String getUrlForLanguages(String baseUrl) { + return baseUrl + ocsApiVersion + "/translation/languages"; + } + public static String getUrlForReminder(User user, String roomToken, String messageId, int version) { String url = ApiUtils.getUrlForChatMessage(version, user.getBaseUrl(), roomToken, messageId); return url + "/reminder"; diff --git a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt index babf6bed6f..1969c69125 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/database/user/CapabilitiesUtilNew.kt @@ -214,12 +214,8 @@ object CapabilitiesUtilNew { @JvmStatic fun isLinkPreviewAvailable(user: User): Boolean { - if (user.capabilities?.coreCapability?.referenceApi != null && + return user.capabilities?.coreCapability?.referenceApi != null && user.capabilities?.coreCapability?.referenceApi == "true" - ) { - return true - } - return false } fun isTranslationsSupported(user: User?): Boolean { @@ -227,20 +223,13 @@ object CapabilitiesUtilNew { val capabilities = user.capabilities return capabilities?.spreedCapability?.config?.containsKey("chat") == true && capabilities.spreedCapability!!.config!!["chat"] != null && - capabilities.spreedCapability!!.config!!["chat"]!!.containsKey("translations") + capabilities.spreedCapability!!.config!!["chat"]!!.containsKey("has-translation-providers") && + capabilities.spreedCapability!!.config!!["chat"]!!["has-translation-providers"] == true } return false } - fun getLanguages(user: User?): Any? { - return if (isTranslationsSupported(user)) { - user!!.capabilities!!.spreedCapability!!.config!!["chat"]!!["translations"] - } else { - null - } - } - fun isRemindSupported(user: User?): Boolean { if (user?.capabilities != null) { val capabilities = user.capabilities diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f80f15f292..8913b73573 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -722,4 +722,6 @@ How to translate with transifex: started a call Error 429 Too Many Requests Caption + Retrieval failed + Languages could not be retrieved