diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt b/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt index d0e8ceabb..7d3209a32 100644 --- a/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt @@ -104,11 +104,16 @@ class DeeplEngine : TranslationEngine( } override suspend fun translate(query: String, source: String, target: String): Translation { + val convertedTarget = when(target){ + "zh-Hant", "zh-Hans" -> "zh" + else -> target + } + if (getApiKey().isNotEmpty()) { val response = api.translate( apiKeyString, sourceOrAuto(source.uppercase()), - target.uppercase(), + convertedTarget.uppercase(), query ) @@ -130,7 +135,7 @@ class DeeplEngine : TranslationEngine( ), splitting = "newlines", lang = DeeplWebTranslationRequestParamsLang( - targetLang = target.uppercase(), + targetLang = convertedTarget.uppercase(), sourceLangUserSelected = sourceOrAuto(source.uppercase()).ifEmpty { "auto" }, preference = DeeplWebTranslationRequestParamsLangPreference( weight = emptyMap() diff --git a/app/src/main/java/com/bnyro/translate/api/gl/GlEngine.kt b/app/src/main/java/com/bnyro/translate/api/gl/GlEngine.kt index 87f7708ae..2cc166a86 100644 --- a/app/src/main/java/com/bnyro/translate/api/gl/GlEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/gl/GlEngine.kt @@ -260,8 +260,15 @@ class GlEngine : TranslationEngine( ).map { Language(it.first.trim(), it.second) } } + protected fun convertLanguageCode(languageCode: String): String { + return when(languageCode){ + "zh-Hant", "zh-Hans" -> "zh" + else -> languageCode + } + } + override suspend fun translate(query: String, source: String, target: String): Translation { - val translation = api.translate(source, target, query) + val translation = api.translate(convertLanguageCode(source), convertLanguageCode(target), query) return Translation(translation.translation) } } diff --git a/app/src/main/java/com/bnyro/translate/api/lv/LVEngine.kt b/app/src/main/java/com/bnyro/translate/api/lv/LVEngine.kt index 9e5a760bf..b97cbb823 100644 --- a/app/src/main/java/com/bnyro/translate/api/lv/LVEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/lv/LVEngine.kt @@ -46,10 +46,17 @@ class LVEngine : TranslationEngine( } } + protected fun convertLanguageCode(languageCode: String): String { + return when(languageCode){ + "zh-Hant", "zh-Hans" -> "zh" + else -> languageCode + } + } + override suspend fun translate(query: String, source: String, target: String): Translation { val response = api.translate( - sourceOrAuto(source), - target, + convertLanguageCode(sourceOrAuto(source)), + convertLanguageCode(target), query.replace("/", "") ) return Translation( diff --git a/app/src/main/java/com/bnyro/translate/api/mh/MhEngine.kt b/app/src/main/java/com/bnyro/translate/api/mh/MhEngine.kt index fa7142077..a6e77fcca 100644 --- a/app/src/main/java/com/bnyro/translate/api/mh/MhEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/mh/MhEngine.kt @@ -58,12 +58,20 @@ class MhEngine : TranslationEngine( .map { Language(it.id, it.name) } } + protected fun convertLanguageCode(languageCode: String): String { + return when(languageCode){ + "zh-Hant" -> "zh-TW" + "zh-Hans" -> "zh-CN" + else -> languageCode + } + } + override suspend fun translate(query: String, source: String, target: String): Translation { val response = api.translate( engine = getSelectedEngine(), - source = sourceOrAuto(source.take(2)), + source = convertLanguageCode(sourceOrAuto(source)), query = query, - target = target.take(2), + target = convertLanguageCode(target), ) return Translation( translatedText = response.translatedText, diff --git a/app/src/main/java/com/bnyro/translate/api/wm/WmEngine.kt b/app/src/main/java/com/bnyro/translate/api/wm/WmEngine.kt index fe22fbf04..820edb2b3 100644 --- a/app/src/main/java/com/bnyro/translate/api/wm/WmEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/wm/WmEngine.kt @@ -50,8 +50,15 @@ class WmEngine : TranslationEngine( } } + protected fun convertLanguageCode(languageCode: String): String { + return when(languageCode){ + "zh-Hant", "zh-Hans" -> "zh" + else -> languageCode + } + } + override suspend fun translate(query: String, source: String, target: String): Translation { - val response = api.translate(source, target, WmTranslationRequest(query)) + val response = api.translate(convertLanguageCode(source), convertLanguageCode(target), WmTranslationRequest(query)) return Translation(response.translation) } } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/translate/api/ya/YandexEngine.kt b/app/src/main/java/com/bnyro/translate/api/ya/YandexEngine.kt index e3f69541d..386135765 100644 --- a/app/src/main/java/com/bnyro/translate/api/ya/YandexEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/ya/YandexEngine.kt @@ -47,8 +47,17 @@ class YandexEngine : TranslationEngine( ).map { (key, value) -> Language(code = key, name = value) } } + protected fun convertLanguageCode(languageCode: String): String { + return when(languageCode){ + "zh-Hant", "zh-Hans" -> "zh" + else -> languageCode + } + } + override suspend fun translate(query: String, source: String, target: String): Translation { - val lang = if (source.isEmpty()) target else "$source-$target" + val convertedSource = convertLanguageCode(source) + val convertedTarget = convertLanguageCode(target) + val lang = if (convertedSource.isEmpty()) convertedTarget else "$convertedSource-$convertedTarget" val uuid = UUID.randomUUID().toString().replace("-", "") + "-0-0" val response = api.translate(lang, query, "android", uuid) diff --git a/app/src/main/java/com/bnyro/translate/ui/components/LanguageSelectionComponent.kt b/app/src/main/java/com/bnyro/translate/ui/components/LanguageSelectionComponent.kt index 97a3f3611..b981803d6 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/LanguageSelectionComponent.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/LanguageSelectionComponent.kt @@ -137,12 +137,9 @@ fun LanguageSelectionComponent(viewModel: TranslationModel) { @Composable fun SwapLanguagesButton(viewModel: TranslationModel) { val context = LocalContext.current - - val switchBtnEnabled = viewModel.sourceLanguage.code.isNotEmpty() - IconButton( onClick = { - if (switchBtnEnabled) viewModel.swapLanguages(context) + viewModel.swapLanguages(context) } ) { Icon( @@ -150,7 +147,7 @@ fun SwapLanguagesButton(viewModel: TranslationModel) { null, modifier = Modifier .size(18.dp), - tint = if (switchBtnEnabled) MaterialTheme.colorScheme.onSurface else Color.Gray + tint = MaterialTheme.colorScheme.onSurface ) } } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/translate/ui/components/TranslationField.kt b/app/src/main/java/com/bnyro/translate/ui/components/TranslationField.kt index 87807bb56..6f5c5207c 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/TranslationField.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/TranslationField.kt @@ -20,18 +20,21 @@ package com.bnyro.translate.ui.components import android.content.Intent import android.os.Handler import android.os.Looper +import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.DoneAll import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.VolumeUp import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -43,11 +46,15 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.bnyro.translate.R import com.bnyro.translate.db.obj.Language +import com.bnyro.translate.obj.Translation import com.bnyro.translate.ui.models.TranslationModel import com.bnyro.translate.util.Preferences import com.bnyro.translate.util.SpeechHelper +import com.bnyro.translate.util.TranslationEngine @Composable fun TranslationField( @@ -57,6 +64,7 @@ fun TranslationField( language: Language, setLanguage: (Language) -> Unit = {}, showLanguageSelector: Boolean = false, + translationEngine: TranslationEngine = translationModel.engine, onTextChange: (String) -> Unit = {} ) { val context = LocalContext.current @@ -70,8 +78,13 @@ fun TranslationField( Preferences.get(Preferences.charCounterLimitKey, "") } + val translationModelTranslation: Translation = translationModel + .translatedTexts[translationEngine.name]?:Translation("") + val inverseTranslationModelTranslation: Translation = translationModel + .inverseTranslatedTexts[translationEngine.name]?:Translation("") + AnimatedVisibility( - visible = text.isNotEmpty(), + visible = text.isNotEmpty() || !isSourceField, // 翻譯欄位總是顯示,但源欄位只在有內容時顯示按鈕 enter = expandVertically(), exit = shrinkVertically(), label = "text actions fade" @@ -84,7 +97,7 @@ fun TranslationField( LanguageSelector( translationModel.availableLanguages, language, - autoLanguageEnabled = translationModel.engine.autoLanguageCode != null && isSourceField, + autoLanguageEnabled = translationEngine.autoLanguageCode != null && isSourceField, viewModel = translationModel, useElevatedButton = false ) { @@ -93,6 +106,19 @@ fun TranslationField( } } + if (!isSourceField){ + Text( + modifier = Modifier.padding( + top = 20.dp, + bottom = 10.dp, + start = 15.dp + ), + text = translationEngine.name, + color = MaterialTheme.colorScheme.primary, + fontSize = 14.sp + ) + } + Spacer(modifier = Modifier.weight(1f)) if (isSourceField && showLanguageSelector) { @@ -128,7 +154,7 @@ fun TranslationField( } ) - if (translationModel.engine.supportsAudio && language.code.isNotEmpty()) { + if (translationEngine.supportsAudio && language.code.isNotEmpty()) { StyledIconButton( imageVector = Icons.Default.VolumeUp ) { @@ -145,11 +171,11 @@ fun TranslationField( } StyledTextField( - text = text + (if (!isSourceField) "\n\n\n" else ""), + text = text + (if (!isSourceField) "" else ""), placeholder = if (isSourceField) stringResource(R.string.enter_text) else null, readOnly = !isSourceField, textColor = if ( - charPref.isNotEmpty() && translationModel.translation.translatedText.length >= charPref.toInt() + charPref.isNotEmpty() && translationModelTranslation.translatedText.length >= charPref.toInt() ) { MaterialTheme.colorScheme.error } else { @@ -158,4 +184,17 @@ fun TranslationField( ) { onTextChange(it) } + + if (!isSourceField){ + Text( + modifier = Modifier.padding( + top = 0.dp, + bottom = 10.dp, + start = 15.dp + ), + text = inverseTranslationModelTranslation.translatedText, + color = MaterialTheme.colorScheme.secondary, + fontSize = 12.sp + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/translate/ui/models/TranslationModel.kt b/app/src/main/java/com/bnyro/translate/ui/models/TranslationModel.kt index abe2e50cb..124faca7c 100644 --- a/app/src/main/java/com/bnyro/translate/ui/models/TranslationModel.kt +++ b/app/src/main/java/com/bnyro/translate/ui/models/TranslationModel.kt @@ -47,6 +47,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.serialization.encodeToString +import androidx.compose.runtime.mutableStateMapOf +import com.bnyro.translate.ext.toastFromMainThread class TranslationModel : ViewModel() { var engine by mutableStateOf(getCurrentEngine()) @@ -70,9 +73,9 @@ class TranslationModel : ViewModel() { var translation by mutableStateOf(Translation("")) - var translatedTexts = TranslationEngines.engines - .associate { it.name to Translation("") } - .toMutableMap() + var translatedTexts = mutableStateMapOf() + + var inverseTranslatedTexts = mutableStateMapOf() var bookmarkedLanguages by mutableStateOf(listOf()) @@ -85,6 +88,15 @@ class TranslationModel : ViewModel() { private var mediaPlayer: MediaPlayer? = null private var audioFile: File? = null + fun detectedLanguageMostly(): String?{ + val detectedLanguageList = translatedTexts.filter { + it.value.detectedLanguage != null + } + return detectedLanguageList.maxByOrNull { + it.value.detectedLanguage?:"" + }?.value?.detectedLanguage + } + private fun getLanguageByPrefKey(key: String): Language? { return runCatching { JsonHelper.json.decodeFromString(Preferences.get(key, "")) @@ -117,10 +129,8 @@ class TranslationModel : ViewModel() { translating = true - translatedTexts = TranslationEngines.engines - .associate { it.name to Translation("") } - .toMutableMap() - + translatedTexts.clear() + TranslationEngines.engines.forEach { translatedTexts[it.name] = Translation("") } viewModelScope.launch(Dispatchers.IO) { val translation = try { engine.translate( @@ -132,7 +142,7 @@ class TranslationModel : ViewModel() { Log.e("error", e.message.toString()) _apiError.emit(e) translating = false - return@launch + Translation("") } translating = false @@ -160,6 +170,19 @@ class TranslationModel : ViewModel() { sourceLanguage.code, targetLanguage.code ) + //if sourceLanguage "auto" use detectedLanguage + val sourceLanguageCode = if(sourceLanguage.code == ""){ + val detectedLanguage = translatedTexts[it.name]?.detectedLanguage + if (detectedLanguage == "" || detectedLanguage == null){ + detectedLanguageMostly() + }else{ detectedLanguage }?:"" + } else { sourceLanguage.code } + + inverseTranslatedTexts[it.name] = it.translate( + translatedTexts[it.name]?.translatedText?:return@runCatching, + targetLanguage.code, + sourceLanguageCode + ) } } } @@ -281,7 +304,10 @@ class TranslationModel : ViewModel() { fun swapLanguages(context: Context) { if (availableLanguages.isEmpty()) return - val temp = sourceLanguage + val temp = if(sourceLanguage.code == ""){ + availableLanguages.find { it.code == detectedLanguageMostly() } + ?:Language("en", "English") + } else { sourceLanguage } sourceLanguage = targetLanguage targetLanguage = temp diff --git a/app/src/main/java/com/bnyro/translate/ui/views/TranslationComponent.kt b/app/src/main/java/com/bnyro/translate/ui/views/TranslationComponent.kt index 3324aadfa..6b62ff19c 100644 --- a/app/src/main/java/com/bnyro/translate/ui/views/TranslationComponent.kt +++ b/app/src/main/java/com/bnyro/translate/ui/views/TranslationComponent.kt @@ -20,16 +20,21 @@ package com.bnyro.translate.ui.views import android.content.ClipData import android.content.ClipboardManager import android.os.Build +import android.util.Log +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowUpward @@ -41,8 +46,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -52,17 +56,18 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import androidx.navigation.NavController import com.bnyro.translate.R +import com.bnyro.translate.obj.Translation import com.bnyro.translate.ui.components.ButtonWithIcon import com.bnyro.translate.ui.components.TranslationField import com.bnyro.translate.ui.models.TranslationModel -import com.bnyro.translate.ui.nav.Destination import com.bnyro.translate.util.Preferences import kotlinx.coroutines.launch @@ -184,19 +189,24 @@ fun TranslationComponent( } } - TranslationField( - translationModel = viewModel, - isSourceField = false, - text = viewModel.translation.translatedText, - language = viewModel.targetLanguage, - showLanguageSelector = showLanguageSelector, - setLanguage = { - if (it == viewModel.sourceLanguage) { - viewModel.sourceLanguage = viewModel.targetLanguage + val enabledEngines = viewModel.enabledSimEngines + for (enabledEngine in enabledEngines){ + val translatedText = viewModel.translatedTexts[enabledEngine.name]?.translatedText?:"" + TranslationField( + translationModel = viewModel, + isSourceField = false, + text = translatedText, + language = viewModel.targetLanguage, + showLanguageSelector = showLanguageSelector, + translationEngine = enabledEngine, + setLanguage = { + if (it == viewModel.sourceLanguage) { + viewModel.sourceLanguage = viewModel.targetLanguage + } + viewModel.targetLanguage = it } - viewModel.targetLanguage = it - } - ) + ) + } } if (scrollState.value > 100) {