From 0505a26934ddae020e2085f4b1674c6d19270a89 Mon Sep 17 00:00:00 2001 From: Chopper <156493704+choppeh@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:12:57 -0300 Subject: [PATCH] MachineTranslations & Snowmtl: Fix the regex in the translator engine and add font size settings (#7465) * Fix the regex in the translator engine and add font size settings * Remove extra lines * Remove rateLimit * Remove init pref * Use lazy statement in the snowmtl client * Relax the exception and show pages without dialog * Fix the translator's bad formatted response for some cases * Change listener return to false Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> --- .../assets/i18n/messages_en.properties | 4 + .../assets/i18n/messages_es.properties | 1 + .../assets/i18n/messages_fr.properties | 1 + .../assets/i18n/messages_id.properties | 1 + .../assets/i18n/messages_it.properties | 1 + .../assets/i18n/messages_pt_br.properties | 4 + .../machinetranslations/build.gradle.kts | 6 +- .../MachineTranslations.kt | 75 ++++++++++++++++++- .../interceptors/ComposedImageInterceptor.kt | 6 +- .../extension/all/snowmtl/Snowmtl.kt | 15 ++-- .../interceptors/TranslationInterceptor.kt | 21 +++++- .../all/snowmtl/translator/BingTranslator.kt | 4 +- .../extension/all/solarmtl/Solarmtl.kt | 2 + 13 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 lib-multisrc/machinetranslations/assets/i18n/messages_en.properties create mode 100644 lib-multisrc/machinetranslations/assets/i18n/messages_es.properties create mode 100644 lib-multisrc/machinetranslations/assets/i18n/messages_fr.properties create mode 100644 lib-multisrc/machinetranslations/assets/i18n/messages_id.properties create mode 100644 lib-multisrc/machinetranslations/assets/i18n/messages_it.properties create mode 100644 lib-multisrc/machinetranslations/assets/i18n/messages_pt_br.properties diff --git a/lib-multisrc/machinetranslations/assets/i18n/messages_en.properties b/lib-multisrc/machinetranslations/assets/i18n/messages_en.properties new file mode 100644 index 000000000..135a66d80 --- /dev/null +++ b/lib-multisrc/machinetranslations/assets/i18n/messages_en.properties @@ -0,0 +1,4 @@ +font_size_title=Font size +font_size_summary=Font changes will not be applied to downloaded or cached chapters. The font size will be adjusted according to the size of the dialog box. +font_size_message=Font size changed to %s +default_font_size=Default diff --git a/lib-multisrc/machinetranslations/assets/i18n/messages_es.properties b/lib-multisrc/machinetranslations/assets/i18n/messages_es.properties new file mode 100644 index 000000000..48bc523f9 --- /dev/null +++ b/lib-multisrc/machinetranslations/assets/i18n/messages_es.properties @@ -0,0 +1 @@ +font_size_title=Tamaño de letra diff --git a/lib-multisrc/machinetranslations/assets/i18n/messages_fr.properties b/lib-multisrc/machinetranslations/assets/i18n/messages_fr.properties new file mode 100644 index 000000000..1beb3574f --- /dev/null +++ b/lib-multisrc/machinetranslations/assets/i18n/messages_fr.properties @@ -0,0 +1 @@ +font_size_title=Taille de la police diff --git a/lib-multisrc/machinetranslations/assets/i18n/messages_id.properties b/lib-multisrc/machinetranslations/assets/i18n/messages_id.properties new file mode 100644 index 000000000..b129b31a4 --- /dev/null +++ b/lib-multisrc/machinetranslations/assets/i18n/messages_id.properties @@ -0,0 +1 @@ +font_size_title=Ukuran font diff --git a/lib-multisrc/machinetranslations/assets/i18n/messages_it.properties b/lib-multisrc/machinetranslations/assets/i18n/messages_it.properties new file mode 100644 index 000000000..917acd0f9 --- /dev/null +++ b/lib-multisrc/machinetranslations/assets/i18n/messages_it.properties @@ -0,0 +1 @@ +font_size_title=Dimensione del carattere diff --git a/lib-multisrc/machinetranslations/assets/i18n/messages_pt_br.properties b/lib-multisrc/machinetranslations/assets/i18n/messages_pt_br.properties new file mode 100644 index 000000000..8ab7a029d --- /dev/null +++ b/lib-multisrc/machinetranslations/assets/i18n/messages_pt_br.properties @@ -0,0 +1,4 @@ +font_size_title=Tamanho da fonte +font_size_summary=As alterações de fonte não serão aplicadas aos capítulos baixados ou armazenados em cache. O tamanho da fonte será ajustado de acordo com o tamanho da caixa de diálogo. +font_size_message=Tamanho da fonte foi alterada para %s +default_font_size=Padrão diff --git a/lib-multisrc/machinetranslations/build.gradle.kts b/lib-multisrc/machinetranslations/build.gradle.kts index 9dce2478c..a2e964c4a 100644 --- a/lib-multisrc/machinetranslations/build.gradle.kts +++ b/lib-multisrc/machinetranslations/build.gradle.kts @@ -2,4 +2,8 @@ plugins { id("lib-multisrc") } -baseVersionCode = 2 +baseVersionCode = 3 + +dependencies { + api(project(":lib:i18n")) +} diff --git a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslations.kt b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslations.kt index d74aec66f..eef9e39bd 100644 --- a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslations.kt +++ b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslations.kt @@ -1,9 +1,16 @@ package eu.kanade.tachiyomi.multisrc.machinetranslations +import android.app.Application +import android.content.SharedPreferences import android.os.Build +import android.widget.Toast import androidx.annotation.RequiresApi +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -15,10 +22,13 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient import okhttp3.Request import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Calendar @@ -29,7 +39,7 @@ abstract class MachineTranslations( override val name: String, override val baseUrl: String, val language: Language, -) : ParsedHttpSource() { +) : ParsedHttpSource(), ConfigurableSource { override val supportsLatest = true @@ -37,9 +47,26 @@ abstract class MachineTranslations( override val lang = language.lang - override val client = network.cloudflareClient.newBuilder() - .addInterceptor(ComposedImageInterceptor(baseUrl, language)) - .build() + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + protected var fontSize: Int + get() = preferences.getString(FONT_SIZE_PREF, DEFAULT_FONT_SIZE)!!.toInt() + set(value) = preferences.edit().putString(FONT_SIZE_PREF, value.toString()).apply() + + private val intl = Intl( + language = language.lang, + baseLanguage = "en", + availableLanguages = setOf("en", "es", "fr", "id", "it", "pt-BR"), + classLoader = this::class.java.classLoader!!, + ) + + override val client: OkHttpClient by lazy { + network.cloudflareClient.newBuilder() + .addInterceptor(ComposedImageInterceptor(baseUrl, language, fontSize)) + .build() + } // ============================== Popular =============================== @@ -203,9 +230,49 @@ abstract class MachineTranslations( return FilterList(filters) } + override fun setupPreferenceScreen(screen: PreferenceScreen) { + // Some libreoffice font sizes + val sizes = arrayOf( + "24", "26", "28", + "32", "36", "40", + "42", "44", "48", + "54", "60", "72", + "80", "88", "96", + ) + + ListPreference(screen.context).apply { + key = FONT_SIZE_PREF + title = intl["font_size_title"] + entries = sizes.map { + "${it}pt" + if (it == DEFAULT_FONT_SIZE) " - ${intl["default_font_size"]}" else "" + }.toTypedArray() + entryValues = sizes + summary = intl["font_size_summary"] + + setOnPreferenceChangeListener { _, newValue -> + val selected = newValue as String + val index = this.findIndexOfValue(selected) + val entry = entries[index] as String + + fontSize = selected.toInt() + + Toast.makeText( + screen.context, + intl["font_size_message"].format(entry), + Toast.LENGTH_LONG, + ).show() + + false + } + }.also(screen::addPreference) + } + companion object { val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE) const val PREFIX_SEARCH = "id:" + const val FONT_SIZE_PREF = "fontSizePref" + const val DEFAULT_FONT_SIZE = "24" + private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US) } } diff --git a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/interceptors/ComposedImageInterceptor.kt b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/interceptors/ComposedImageInterceptor.kt index 6278f6037..677416fed 100644 --- a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/interceptors/ComposedImageInterceptor.kt +++ b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/interceptors/ComposedImageInterceptor.kt @@ -26,7 +26,6 @@ import uy.kohesive.injekt.injectLazy import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream -import java.io.IOException import java.io.InputStream import kotlin.math.pow import kotlin.math.sqrt @@ -36,6 +35,7 @@ import kotlin.math.sqrt class ComposedImageInterceptor( baseUrl: String, val language: Language, + val fontSize: Int = 24, ) : Interceptor { private val json: Json by injectLazy() @@ -55,7 +55,7 @@ class ComposedImageInterceptor( } val dialogues = request.url.fragment?.parseAs>() - ?: throw IOException("Dialogues not found") + ?: emptyList() val imageRequest = request.newBuilder() .url(url) @@ -104,7 +104,7 @@ class ComposedImageInterceptor( } private fun createTextPaint(font: Typeface?): TextPaint { - val defaultTextSize = 24.pt // arbitrary + val defaultTextSize = fontSize.pt return TextPaint().apply { color = Color.BLACK textSize = defaultTextSize diff --git a/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/Snowmtl.kt b/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/Snowmtl.kt index ab026c08d..228e24ccc 100644 --- a/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/Snowmtl.kt +++ b/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/Snowmtl.kt @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.multisrc.machinetranslations.Language import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor import eu.kanade.tachiyomi.network.interceptor.rateLimit +import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit @RequiresApi(Build.VERSION_CODES.O) @@ -27,10 +28,12 @@ class Snowmtl( private val translator: TranslatorEngine = BingTranslator(clientUtils, headers) - override val client = network.cloudflareClient.newBuilder() - .rateLimit(2) - .readTimeout(2, TimeUnit.MINUTES) - .addInterceptor(TranslationInterceptor(language, translator)) - .addInterceptor(ComposedImageInterceptor(baseUrl, language)) - .build() + override val client: OkHttpClient by lazy { + network.cloudflareClient.newBuilder() + .connectTimeout(1, TimeUnit.MINUTES) + .readTimeout(2, TimeUnit.MINUTES) + .addInterceptor(TranslationInterceptor(language, translator)) + .addInterceptor(ComposedImageInterceptor(baseUrl, language, fontSize)) + .build() + } } diff --git a/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/interceptors/TranslationInterceptor.kt b/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/interceptors/TranslationInterceptor.kt index 656ff99d8..dac36a647 100644 --- a/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/interceptors/TranslationInterceptor.kt +++ b/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/interceptors/TranslationInterceptor.kt @@ -62,7 +62,20 @@ class TranslationInterceptor( tokens: List, mapping: Map>, ) = tokens.mapNotNull { token -> - val list = token.decode().parseAs>() + val list = try { + token.decode().parseAs>() + } catch (_: Exception) { + // The translator may return an invalid JSON, but it keeps the pattern sent. + TRANSLATOR_EXTRACT_REGEX.findAll(token).map { + listOf( + it.groups[1]?.value!!.encode(), + it.groups[3]?.value!!.let { dialog -> + dialog.takeIf { it.startsWith("\"") } ?: dialog.encode() + }, + ) + }.toList().flatten() + } + val key = list.first() val text = list.last() @@ -102,7 +115,7 @@ class TranslationInterceptor( private fun String.encode() = "\"${this}\"" private fun String.decode() = this.substringAfter("\"").substringBeforeLast("\"") - private val delimiter: String = "|" + private val delimiter: String = "¦" /** * Tokenizes a list of texts based on the translator's character capacity per request @@ -137,6 +150,10 @@ class TranslationInterceptor( private inline fun String.parseAs(): T { return json.decodeFromString(this) } + + companion object { + val TRANSLATOR_EXTRACT_REGEX = """"?(-?\d+)(\\?")?,((\\?")?([^(\])]+))""".toRegex() + } } private class AssociatedDialog( diff --git a/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/translator/BingTranslator.kt b/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/translator/BingTranslator.kt index 4f63158d9..0653e2b3b 100644 --- a/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/translator/BingTranslator.kt +++ b/src/all/snowmtl/src/eu/kanade/tachiyomi/extension/all/snowmtl/translator/BingTranslator.kt @@ -97,8 +97,8 @@ class BingTranslator(private val client: OkHttpClient, private val headers: Head val matchTwo = IG_PARAM_REGEX.find(scriptTwo)?.groups return TokenGroup( - token = matchOne?.get(2)?.value ?: "", - key = matchOne?.get(1)?.value ?: "", + token = matchOne?.get(4)?.value ?: "", + key = matchOne?.get(3)?.value ?: "", ig = matchTwo?.get(1)?.value ?: "", iid = document.selectFirst("div[data-iid]:not([class])")?.attr("data-iid") ?: "", ) diff --git a/src/all/solarmtl/src/eu/kanade/tachiyomi/extension/all/solarmtl/Solarmtl.kt b/src/all/solarmtl/src/eu/kanade/tachiyomi/extension/all/solarmtl/Solarmtl.kt index f4a264431..beef89ff1 100644 --- a/src/all/solarmtl/src/eu/kanade/tachiyomi/extension/all/solarmtl/Solarmtl.kt +++ b/src/all/solarmtl/src/eu/kanade/tachiyomi/extension/all/solarmtl/Solarmtl.kt @@ -5,6 +5,7 @@ import androidx.annotation.RequiresApi import eu.kanade.tachiyomi.multisrc.machinetranslations.Language import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations import eu.kanade.tachiyomi.network.interceptor.rateLimit +import java.util.concurrent.TimeUnit @RequiresApi(Build.VERSION_CODES.O) class Solarmtl( @@ -15,6 +16,7 @@ class Solarmtl( language, ) { override val client = super.client.newBuilder() + .connectTimeout(1, TimeUnit.MINUTES) .rateLimit(2) .build() }