From 5158c6ef02be9e4be168c3b6b42080c6aa072319 Mon Sep 17 00:00:00 2001 From: Next Alone <12210746+NextAlone@users.noreply.github.com> Date: Mon, 4 Nov 2024 01:59:12 +0800 Subject: [PATCH] feat: [WIP] add DeepLX translation provider Signed-off-by: Next Alone <12210746+NextAlone@users.noreply.github.com> --- .../activity/GeneralSettingActivity.java | 47 +++++- .../nnngram/helpers/TranslateHelper.kt | 7 + .../translate/providers/DeepLxranslator.kt | 147 ++++++++++++++++++ .../xyz/nextalone/nnngram/utils/Defines.kt | 2 + .../res/values-zh-rTW/strings_nullgram.xml | 1 + .../main/res/values-zh/strings_nullgram.xml | 1 + .../src/main/res/values/strings_nullgram.xml | 2 + 7 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 TMessagesProj/src/main/java/xyz/nextalone/nnngram/translate/providers/DeepLxranslator.kt diff --git a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/activity/GeneralSettingActivity.java b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/activity/GeneralSettingActivity.java index 973e450b09..1b9bc8c92c 100644 --- a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/activity/GeneralSettingActivity.java +++ b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/activity/GeneralSettingActivity.java @@ -90,6 +90,7 @@ public class GeneralSettingActivity extends BaseActivity { private int translatorRow; private int showOriginalRow; private int deepLFormalityRow; + private int deepLxApiRow; private int translatorTypeRow; private int translationProviderRow; private int translationTargetRow; @@ -306,11 +307,26 @@ else if (position == showBotAPIRow) { listAdapter.notifyItemChanged(translationTargetRow, PARTIAL); } if (!oldProvider.equals(TranslateHelper.getCurrentProviderType())) { - if (oldProvider.equals(ProviderType.DeepLTranslator)) { + boolean wasDeepLTranslator = oldProvider.equals(ProviderType.DeepLTranslator); + boolean isDeepLTranslator = TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLTranslator); + boolean wasDeepLxTranslator = oldProvider.equals(ProviderType.DeepLxTranslator); + boolean isDeepLxTranslator = TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLxTranslator); + + if (wasDeepLTranslator && !isDeepLxTranslator) { listAdapter.notifyItemRemoved(deepLFormalityRow); - updateRows(); - } else if (TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLTranslator)) { - updateRows(); + } else if (wasDeepLxTranslator) { + listAdapter.notifyItemRemoved(deepLxApiRow); + if (!isDeepLxTranslator) { + listAdapter.notifyItemRemoved(deepLFormalityRow); + } + } + + updateRows(); + + if (isDeepLTranslator) { + listAdapter.notifyItemInserted(deepLFormalityRow); + } else if (isDeepLxTranslator) { + listAdapter.notifyItemInserted(deepLxApiRow); listAdapter.notifyItemInserted(deepLFormalityRow); } } @@ -338,6 +354,22 @@ else if (position == showBotAPIRow) { ConfigManager.putInt(Defines.deepLFormality, types.get(i)); listAdapter.notifyItemChanged(deepLFormalityRow, PARTIAL); }); + } else if (position == deepLxApiRow) { + EditTextBoldCursor editText = new EditTextBoldCursor(getParentActivity()); + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + editText.setHint("DeepLx API Url"); // todo: string resource + editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + editText.setText(Config.getDeepLxApi()); + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + frameLayout.addView(editText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP, 16, 0, 16, 0)); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle("DeepLx API Url"); // todo: string resource + builder.setView(frameLayout); + builder.setPositiveButton(LocaleController.getString("Save", R.string.Save), (dialogInterface, i) -> { + Config.setDeepLxApi(editText.getText().toString()); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.show(); } else if (position == translatorTypeRow) { var oldType = TranslateHelper.getCurrentStatus(); TranslateHelper.showTranslatorTypeSelector(getParentActivity(), view, () -> { @@ -441,7 +473,8 @@ protected void updateRows() { if (TranslateHelper.getCurrentStatus() != TranslateHelper.Status.External) { showOriginalRow = TranslateHelper.getCurrentStatus() == TranslateHelper.Status.InMessage ? addRow("showOriginal") : -1; translationProviderRow = addRow("translationProvider"); - deepLFormalityRow = TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLTranslator) ? addRow("deepLFormality") : -1; + deepLxApiRow = TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLxTranslator) ? addRow("deepLxApi") : -1; + deepLFormalityRow = (TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLTranslator) || TranslateHelper.getCurrentProviderType().equals(ProviderType.DeepLxTranslator)) ? addRow("deepLFormality") : -1; translationTargetRow = addRow("translationTarget"); doNotTranslateRow = addRow("doNotTranslate"); autoTranslateRow = addRow("autoTranslate"); @@ -612,6 +645,8 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, boole textCell.setTextAndValue(LocaleController.getString("customTitle", R.string.customTitle), Config.getCustomTitle(), payload, true); } else if (position == drawerListRow) { textCell.setText(LocaleController.getString("drawerList", R.string.drawerList), false); + } else if (position == deepLxApiRow) { + textCell.setTextAndValue(LocaleController.getString(R.string.DeepLxApi), Config.getDeepLxApi().substring(0, 25) + "...", payload, true); } break; } @@ -768,7 +803,7 @@ public int getItemViewType(int position) { return 1; } else if (position == tabsTitleTypeRow || position == translationProviderRow || position == deepLFormalityRow || position == translationTargetRow || position == translatorTypeRow || position == doNotTranslateRow || position == overrideDevicePerformanceRow || position == customTitleRow || - position == drawerListRow) { + position == drawerListRow || position == deepLxApiRow) { return 2; } else if (position == generalRow || position == translatorRow || position == devicesRow || position == storiesRow) { return 4; diff --git a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/helpers/TranslateHelper.kt b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/helpers/TranslateHelper.kt index 4479a55a0d..1a2880fc77 100644 --- a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/helpers/TranslateHelper.kt +++ b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/helpers/TranslateHelper.kt @@ -47,6 +47,7 @@ import xyz.nextalone.nnngram.config.ConfigManager import xyz.nextalone.nnngram.translate.BaseTranslator import xyz.nextalone.nnngram.translate.providers.BaiduTranslator import xyz.nextalone.nnngram.translate.providers.DeepLTranslator +import xyz.nextalone.nnngram.translate.providers.DeepLxTranslator import xyz.nextalone.nnngram.translate.providers.GoogleTranslator import xyz.nextalone.nnngram.translate.providers.LingoTranslator import xyz.nextalone.nnngram.translate.providers.MicrosoftTranslator @@ -101,6 +102,7 @@ object TranslateHelper { YandexTranslator(6), DeepLTranslator(7), TranSmartTranslator(8), + DeepLxTranslator(9), } @JvmStatic @@ -110,6 +112,7 @@ object TranslateHelper { ProviderType.TelegramTranslator.num -> ProviderType.TelegramTranslator ProviderType.MicrosoftTranslator.num -> ProviderType.MicrosoftTranslator ProviderType.DeepLTranslator.num -> ProviderType.DeepLTranslator + ProviderType.DeepLxTranslator.num -> ProviderType.DeepLxTranslator ProviderType.LingoTranslator.num -> ProviderType.LingoTranslator ProviderType.BaiduTranslator.num -> ProviderType.BaiduTranslator ProviderType.TranSmartTranslator.num -> ProviderType.TranSmartTranslator @@ -130,6 +133,7 @@ object TranslateHelper { ProviderType.LingoTranslator -> LingoTranslator ProviderType.BaiduTranslator -> BaiduTranslator ProviderType.TranSmartTranslator -> TranSmartTranslator + ProviderType.DeepLxTranslator -> DeepLxTranslator } @JvmStatic @@ -142,6 +146,7 @@ object TranslateHelper { ProviderType.LingoTranslator -> LingoTranslator ProviderType.BaiduTranslator -> BaiduTranslator ProviderType.TranSmartTranslator -> TranSmartTranslator + ProviderType.DeepLxTranslator -> DeepLxTranslator } @JvmStatic @@ -252,6 +257,8 @@ object TranslateHelper { types.add(ProviderType.BaiduTranslator) names.add(LocaleController.getString("ProviderTranSmartTranslate", R.string.ProviderTranSmartTranslate)) types.add(ProviderType.TranSmartTranslator) + names.add(LocaleController.getString("ProviderDeepLxTranslate", R.string.ProviderDeepLXTranslate)) + types.add(ProviderType.DeepLxTranslator) return Pair(names, types) } diff --git a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/translate/providers/DeepLxranslator.kt b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/translate/providers/DeepLxranslator.kt new file mode 100644 index 0000000000..890b85d19d --- /dev/null +++ b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/translate/providers/DeepLxranslator.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019-2023 qwq233 + * https://github.com/qwq233/Nullgram + * + * 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 2 + * 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 software. + * If not, see + * + */ + +package xyz.nextalone.nnngram.translate.providers + +import android.text.TextUtils +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import org.json.JSONObject +import xyz.nextalone.gen.Config +import xyz.nextalone.nnngram.config.ConfigManager +import xyz.nextalone.nnngram.translate.BaseTranslator +import xyz.nextalone.nnngram.utils.Defines +import xyz.nextalone.nnngram.utils.Log +import java.io.IOException +import java.util.Locale +import java.util.UUID +import java.util.regex.Matcher +import java.util.regex.Pattern + +/** + * @author NextAlone + * @date 2024/11/04 01:14 + * + */ +object DeepLxTranslator : BaseTranslator() { + + private val targetLanguages = listOf( + "bg", "cs", "da", "de", "el", "en-GB", "en-US", "en", "es", "et", + "fi", "fr", "hu", "id", "it", "ja", "lt", "lv", "nl", "pl", "pt-BR", + "pt-PT", "pt", "ro", "ru", "sk", "sl", "sv", "tr", "uk", "zh" + ) + + override fun getTargetLanguages(): List = targetLanguages + + override fun convertLanguageCode(language: String, country: String?): String { + val languageLowerCase: String = language.lowercase(Locale.getDefault()) + val code: String = if (!TextUtils.isEmpty(country)) { + val countryUpperCase: String = country!!.uppercase(Locale.getDefault()) + if (targetLanguages.contains("$languageLowerCase-$countryUpperCase")) { + "$languageLowerCase-$countryUpperCase" + } else { + languageLowerCase + } + } else { + languageLowerCase + } + return code + } + + override suspend fun translateText(text: String, from: String, to: String): RequestResult { + Log.d("text: $text") + Log.d("from: $from") + Log.d("to: $to") + if (from == to) { + return RequestResult(from, text) + } + if (Config.deepLxApi.isEmpty()) { + throw IOException("DeepLx API or token is empty") + } + + client.post(Config.deepLxApi) { + contentType(ContentType.Application.Json) +// header("Referer", "https://www.deepl.com/") +// header("User-Agent", "DeepL/1.8(52) Android 13 (Pixel 5;aarch64)") +// header("Client-Id", uuid) +// header("x-instance", uuid) +// header("x-app-os-name", "Android") +// header("x-app-os-version", "13") +// header("x-app-version", "1.8") +// header("x-app-build", "52") +// header("x-app-device", "Pixel 5") +// header("x-app-instance-id", uuid) + setBody(getRequestBody(text, from, to)) + }.let { + when (it.status) { + HttpStatusCode.OK -> { + val jsonObject = JSONObject(it.bodyAsText()) + if (jsonObject.has("error")) { + throw IOException(jsonObject.getString("message")) + } + return RequestResult( + jsonObject.getString("source_lang"), + jsonObject.getString("data") + ) + } + + else -> { + Log.w(it.bodyAsText()) + return RequestResult(from, null, it.status) + } + } + } + } + + const val FORMALITY_DEFAULT = 0 + const val FORMALITY_MORE = 1 + const val FORMALITY_LESS = 2 + + + private fun getRequestBody(text: String, from: String, to: String): String { + var iCounter = 1 + val iMatcher: Matcher = Pattern.compile("[i]").matcher(text) + while (iMatcher.find()) { + iCounter++ + } + val params = JSONObject().apply { + put("text", text) + put("split_sentences", 1) + put("source_lang", from) + put("target_lang", to) + put("preserve_formatting", true) + put("formality", getFormalityString()) + } + + return params.toString() + } + + private fun getFormalityString(): String? { + return when (ConfigManager.getIntOrDefault(Defines.deepLFormality, -1)) { + FORMALITY_DEFAULT -> "default" + FORMALITY_MORE -> "more" + FORMALITY_LESS -> "less" + else -> "default" + } + } +} diff --git a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/utils/Defines.kt b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/utils/Defines.kt index 72e2352bb1..f9bd30155a 100644 --- a/TMessagesProj/src/main/java/xyz/nextalone/nnngram/utils/Defines.kt +++ b/TMessagesProj/src/main/java/xyz/nextalone/nnngram/utils/Defines.kt @@ -180,6 +180,8 @@ object Defines { @BooleanConfig const val showOriginal = "showOriginal" const val translatorProvider = "translatorProvider" const val deepLFormality = "deepLFormality" + @StringConfig("") const val deepLxApi = "deepLxApi" + const val deepLxPreserveFormatting = "deepLxPreserveFormatting" const val translatorStatus = "translatorStatus" const val targetLanguage = "targetLanguage" const val restrictedLanguages = "restrictedLanguagesFix" diff --git a/TMessagesProj/src/main/res/values-zh-rTW/strings_nullgram.xml b/TMessagesProj/src/main/res/values-zh-rTW/strings_nullgram.xml index ee5a8fb864..9d3ad88ea0 100644 --- a/TMessagesProj/src/main/res/values-zh-rTW/strings_nullgram.xml +++ b/TMessagesProj/src/main/res/values-zh-rTW/strings_nullgram.xml @@ -369,4 +369,5 @@ 隱藏文件夾中的\"全部取消靜音\" 滑動時隱藏鍵盤 在当前位置中开始搜索 + DeepLX 翻譯 diff --git a/TMessagesProj/src/main/res/values-zh/strings_nullgram.xml b/TMessagesProj/src/main/res/values-zh/strings_nullgram.xml index c25bcd0f3b..9ddcd0cfd7 100644 --- a/TMessagesProj/src/main/res/values-zh/strings_nullgram.xml +++ b/TMessagesProj/src/main/res/values-zh/strings_nullgram.xml @@ -379,4 +379,5 @@ 隐藏标题代理入口 加入频道后自动静音 在输入框中显示小米澎湃AI + DeepLX 翻译 diff --git a/TMessagesProj/src/main/res/values/strings_nullgram.xml b/TMessagesProj/src/main/res/values/strings_nullgram.xml index afa45742a2..9634692254 100644 --- a/TMessagesProj/src/main/res/values/strings_nullgram.xml +++ b/TMessagesProj/src/main/res/values/strings_nullgram.xml @@ -392,4 +392,6 @@ Hide proxy entry in title Auto mute after joining channel Enable Xiaomi HyperAI in editor + DeepLX Translate + DeepLX API