Solarmtl: Add support to Indonesian and Arabic (#10213)

* Add support to Indonesian and Arabic

* Add settings to disable translator
This commit is contained in:
Chopper 2025-08-21 16:18:48 -03:00 committed by Draff
parent 6011d20b86
commit 89beec2f78
Signed by: Draff
GPG Key ID: E8A89F3211677653
15 changed files with 172 additions and 154 deletions

View File

@ -4,3 +4,10 @@ font_size_message=Font size changed to %s
default_font_size=Default default_font_size=Default
disable_website_setting_title=Disable source settings disable_website_setting_title=Disable source settings
disable_website_setting_summary=Site fonts will be disabled and your device's fonts will be applied. This does not apply to downloaded or cached chapters. disable_website_setting_summary=Site fonts will be disabled and your device's fonts will be applied. This does not apply to downloaded or cached chapters.
disable_word_break_title=Disable word break
disable_word_break_summary=This feature prevents words from being automatically broken in the middle of a line.
disable_translator_title=Disable translator
disable_translator_summary=Disable auto translation and enable source translation
translate_dialog_box_title=Translator
translate_dialog_box_summary=Engine used to translate dialog boxes
translate_dialog_box_toast=The translator has been changed to

View File

@ -4,3 +4,10 @@ font_size_message=Tamanho da fonte foi alterada para %s
default_font_size=Padrão default_font_size=Padrão
disable_website_setting_title=Desativar configurações do site disable_website_setting_title=Desativar configurações do site
disable_website_setting_summary=As fontes do site serão desativadas e as fontes de seu dispositivo serão aplicadas. Isso não se aplica a capítulos baixados ou armazenados em cache. disable_website_setting_summary=As fontes do site serão desativadas e as fontes de seu dispositivo serão aplicadas. Isso não se aplica a capítulos baixados ou armazenados em cache.
disable_word_break_title=Desativar quebra de palavras
disable_word_break_summary=Esse recurso impede que palavras sejam quebradas automaticamente no meio da linha.
disable_translator_title=Desativar tradutor
disable_translator_summary=Desativar tradução automática e ativar tradução da fonte
translate_dialog_box_title=Tradutor
translate_dialog_box_summary=Motor utilizado para traduzir caixas de diálogo
translate_dialog_box_toast=O tradutor foi alterado para

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc") id("lib-multisrc")
} }
baseVersionCode = 5 baseVersionCode = 6
dependencies { dependencies {
api(project(":lib:i18n")) api(project(":lib:i18n"))

View File

@ -10,7 +10,12 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.i18n.Intl import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.TranslationInterceptor
import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.TranslatorEngine
import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.bing.BingTranslator
import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.google.GoogleTranslator
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
@ -68,6 +73,14 @@ abstract class MachineTranslations(
get() = preferences.getString(FONT_SIZE_PREF, DEFAULT_FONT_SIZE)!!.toInt() get() = preferences.getString(FONT_SIZE_PREF, DEFAULT_FONT_SIZE)!!.toInt()
set(value) = preferences.edit().putString(FONT_SIZE_PREF, value.toString()).apply() set(value) = preferences.edit().putString(FONT_SIZE_PREF, value.toString()).apply()
protected var disableWordBreak: Boolean
get() = preferences.getBoolean(DISABLE_WORD_BREAK_PREF, language.disableWordBreak)
set(value) = preferences.edit().putBoolean(DISABLE_WORD_BREAK_PREF, value).apply()
protected var disableTranslator: Boolean
get() = preferences.getBoolean(DISABLE_TRANSLATOR_PREF, language.disableTranslator)
set(value) = preferences.edit().putBoolean(DISABLE_TRANSLATOR_PREF, value).apply()
protected var disableSourceSettings: Boolean protected var disableSourceSettings: Boolean
get() = preferences.getBoolean(DISABLE_SOURCE_SETTINGS_PREF, language.disableSourceSettings) get() = preferences.getBoolean(DISABLE_SOURCE_SETTINGS_PREF, language.disableSourceSettings)
set(value) = preferences.edit().putBoolean(DISABLE_SOURCE_SETTINGS_PREF, value).apply() set(value) = preferences.edit().putBoolean(DISABLE_SOURCE_SETTINGS_PREF, value).apply()
@ -81,12 +94,19 @@ abstract class MachineTranslations(
private val settings get() = language.apply { private val settings get() = language.apply {
fontSize = this@MachineTranslations.fontSize fontSize = this@MachineTranslations.fontSize
disableWordBreak = this@MachineTranslations.disableWordBreak
} }
open val useDefaultComposedImageInterceptor: Boolean = true
override val client: OkHttpClient get() = clientInstance!! override val client: OkHttpClient get() = clientInstance!!
private val translators = arrayOf(
"Bing",
"Google",
)
private val provider: String get() =
preferences.getString(TRANSLATOR_PROVIDER_PREF, translators.first())!!
/** /**
* This ensures that the `OkHttpClient` instance is only created when required, and it is rebuilt * This ensures that the `OkHttpClient` instance is only created when required, and it is rebuilt
* when there are configuration changes to ensure that the client uses the most up-to-date settings. * when there are configuration changes to ensure that the client uses the most up-to-date settings.
@ -99,10 +119,23 @@ abstract class MachineTranslations(
return field return field
} }
protected open fun clientBuilder() = network.cloudflareClient.newBuilder() private val clientUtils = network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES) .rateLimit(3, 2, TimeUnit.SECONDS)
.readTimeout(2, TimeUnit.MINUTES) .build()
.addInterceptorIf(useDefaultComposedImageInterceptor, ComposedImageInterceptor(baseUrl, settings))
protected fun clientBuilder(): OkHttpClient.Builder {
val translator: TranslatorEngine = when (provider) {
"Google" -> GoogleTranslator(clientUtils, headers)
else -> BingTranslator(clientUtils, headers)
}
return network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(2, TimeUnit.MINUTES)
.rateLimit(3)
.addInterceptorIf(!disableTranslator && language.lang != language.origin, TranslationInterceptor(settings, translator))
.addInterceptor(ComposedImageInterceptor(baseUrl, settings))
}
private fun OkHttpClient.Builder.addInterceptorIf(condition: Boolean, interceptor: Interceptor): OkHttpClient.Builder { private fun OkHttpClient.Builder.addInterceptorIf(condition: Boolean, interceptor: Interceptor): OkHttpClient.Builder {
return this.takeIf { condition.not() } ?: this.addInterceptor(interceptor) return this.takeIf { condition.not() } ?: this.addInterceptor(interceptor)
@ -311,13 +344,70 @@ abstract class MachineTranslations(
key = DISABLE_SOURCE_SETTINGS_PREF key = DISABLE_SOURCE_SETTINGS_PREF
title = "${intl["disable_website_setting_title"]}" title = "${intl["disable_website_setting_title"]}"
summary = intl["disable_website_setting_summary"] summary = intl["disable_website_setting_summary"]
setDefaultValue(false) setDefaultValue(language.disableSourceSettings)
setOnPreferenceChange { _, newValue -> setOnPreferenceChange { _, newValue ->
disableSourceSettings = newValue as Boolean disableSourceSettings = newValue as Boolean
true true
} }
}.also(screen::addPreference) }.also(screen::addPreference)
} }
SwitchPreferenceCompat(screen.context).apply {
key = DISABLE_WORD_BREAK_PREF
title = "${intl["disable_word_break_title"]}"
summary = intl["disable_word_break_summary"]
setDefaultValue(language.disableWordBreak)
setOnPreferenceChange { _, newValue ->
disableWordBreak = newValue as Boolean
true
}
}.also(screen::addPreference)
if (language.target == language.origin) {
return
}
if (language.supportNativeTranslation) {
SwitchPreferenceCompat(screen.context).apply {
key = DISABLE_TRANSLATOR_PREF
title = "${intl["disable_translator_title"]}"
summary = intl["disable_translator_summary"]
setDefaultValue(language.disableTranslator)
setOnPreferenceChange { _, newValue ->
disableTranslator = newValue as Boolean
true
}
}.also(screen::addPreference)
}
if (!disableTranslator && language.supportNativeTranslation) {
ListPreference(screen.context).apply {
key = TRANSLATOR_PROVIDER_PREF
title = intl["translate_dialog_box_title"]
entries = translators
entryValues = translators
summary = buildString {
appendLine(intl["translate_dialog_box_summary"])
append("\t* %s")
}
setDefaultValue(translators.first())
setOnPreferenceChange { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entries[index] as String
Toast.makeText(
screen.context,
"${intl["translate_dialog_box_toast"]} '$entry'",
Toast.LENGTH_LONG,
).show()
true
}
}.also(screen::addPreference)
}
} }
/** /**
@ -338,6 +428,9 @@ abstract class MachineTranslations(
const val PREFIX_SEARCH = "id:" const val PREFIX_SEARCH = "id:"
private const val FONT_SIZE_PREF = "fontSizePref" private const val FONT_SIZE_PREF = "fontSizePref"
private const val DISABLE_SOURCE_SETTINGS_PREF = "disableSourceSettingsPref" private const val DISABLE_SOURCE_SETTINGS_PREF = "disableSourceSettingsPref"
private const val DISABLE_WORD_BREAK_PREF = "disableWordBreakPref"
private const val DISABLE_TRANSLATOR_PREF = "disableWordBreakPref"
private const val TRANSLATOR_PROVIDER_PREF = "translatorProviderPref"
private const val DEFAULT_FONT_SIZE = "24" private const val DEFAULT_FONT_SIZE = "24"
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US) private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US)

View File

@ -2,18 +2,13 @@ package eu.kanade.tachiyomi.multisrc.machinetranslations
class MachineTranslationsFactoryUtils class MachineTranslationsFactoryUtils
interface Language { class Language(
val lang: String val lang: String,
val target: String val target: String = lang,
val origin: String val origin: String = "en",
var fontSize: Int var fontSize: Int = 24,
var disableSourceSettings: Boolean var disableSourceSettings: Boolean = false,
} var disableWordBreak: Boolean = false,
var disableTranslator: Boolean = false,
data class LanguageImpl( var supportNativeTranslation: Boolean = false,
override val lang: String, )
override val target: String = lang,
override val origin: String = "en",
override var fontSize: Int = 24,
override var disableSourceSettings: Boolean = false,
) : Language

View File

@ -32,7 +32,7 @@ import java.io.InputStream
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
class ComposedImageInterceptor( class ComposedImageInterceptor(
baseUrl: String, baseUrl: String,
var language: Language, val language: Language,
) : Interceptor { ) : Interceptor {
private val json: Json by injectLazy() private val json: Json by injectLazy()
@ -231,8 +231,13 @@ class ComposedImageInterceptor(
return StaticLayout.Builder.obtain(text, 0, text.length, textPaint, dialog.width.toInt()).apply { return StaticLayout.Builder.obtain(text, 0, text.length, textPaint, dialog.width.toInt()).apply {
setAlignment(Layout.Alignment.ALIGN_CENTER) setAlignment(Layout.Alignment.ALIGN_CENTER)
setIncludePad(false) setIncludePad(language.disableSourceSettings)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (language.disableWordBreak) {
setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE)
setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE)
return@apply
}
setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED) setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED)
setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL) setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
} }

View File

@ -1,11 +1,11 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.interceptors package eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.extension.all.snowmtl.LanguageSetting
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.TranslatorEngine
import eu.kanade.tachiyomi.multisrc.machinetranslations.Dialog import eu.kanade.tachiyomi.multisrc.machinetranslations.Dialog
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations.Companion.PAGE_REGEX import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations.Companion.PAGE_REGEX
import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.TranslatorEngine
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -19,7 +19,7 @@ import uy.kohesive.injekt.injectLazy
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
class TranslationInterceptor( class TranslationInterceptor(
var language: LanguageSetting, val language: Language,
private val translator: TranslatorEngine, private val translator: TranslatorEngine,
) : Interceptor { ) : Interceptor {

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.translator package eu.kanade.tachiyomi.multisrc.machinetranslations.translator
interface TranslatorEngine { interface TranslatorEngine {
val capacity: Int val capacity: Int

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.translator.bing package eu.kanade.tachiyomi.multisrc.machinetranslations.translator.bing
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.TranslatorEngine import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.TranslatorEngine
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
@ -15,8 +15,7 @@ import okhttp3.Response
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class BingTranslator(private val client: OkHttpClient, private val headers: Headers) : class BingTranslator(private val client: OkHttpClient, private val headers: Headers) : TranslatorEngine {
TranslatorEngine {
private val baseUrl = "https://www.bing.com" private val baseUrl = "https://www.bing.com"

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.translator.bing package eu.kanade.tachiyomi.multisrc.machinetranslations.translator.bing
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.translator.google package eu.kanade.tachiyomi.multisrc.machinetranslations.translator.google
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.TranslatorEngine import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.TranslatorEngine
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.translator.google package eu.kanade.tachiyomi.multisrc.machinetranslations.translator.google
import okhttp3.Response import okhttp3.Response

View File

@ -1,97 +1,15 @@
package eu.kanade.tachiyomi.extension.all.snowmtl package eu.kanade.tachiyomi.extension.all.snowmtl
import android.os.Build import android.os.Build
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.preference.ListPreference import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.all.snowmtl.interceptors.TranslationInterceptor
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.TranslatorEngine
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.bing.BingTranslator
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.google.GoogleTranslator
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations 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) @RequiresApi(Build.VERSION_CODES.O)
class Snowmtl( class Snowmtl(
private val language: LanguageSetting, language: Language,
) : MachineTranslations( ) : MachineTranslations(
name = "Snow Machine Translations", name = "Snow Machine Translations",
baseUrl = "https://snowmtl.ru", baseUrl = "https://snowmtl.ru",
language, language,
) { )
override val lang = language.lang
private val translators = arrayOf(
"Bing",
"Google",
)
private val settings: LanguageSetting get() = language.copy(
fontSize = this@Snowmtl.fontSize,
disableSourceSettings = this@Snowmtl.disableSourceSettings,
)
private val provider: String get() =
preferences.getString(TRANSLATOR_PROVIDER_PREF, translators.first())!!
private val clientUtils = network.cloudflareClient.newBuilder()
.rateLimit(3, 2, TimeUnit.SECONDS)
.build()
override val useDefaultComposedImageInterceptor = false
override fun clientBuilder(): OkHttpClient.Builder {
val translator: TranslatorEngine = when (provider) {
"Google" -> GoogleTranslator(clientUtils, headers)
else -> BingTranslator(clientUtils, headers)
}
return super.clientBuilder()
.rateLimit(3)
.addInterceptor(TranslationInterceptor(settings, translator))
.addInterceptor(ComposedImageInterceptor(baseUrl, settings))
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
super.setupPreferenceScreen(screen)
if (language.target == language.origin) {
return
}
ListPreference(screen.context).apply {
key = TRANSLATOR_PROVIDER_PREF
title = "Translator"
entries = translators
entryValues = translators
summary = buildString {
appendLine("Engine used to translate dialog boxes")
append("\t* %s")
}
setDefaultValue(translators.first())
setOnPreferenceChange { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entries[index] as String
Toast.makeText(
screen.context,
"The translator has been changed to '$entry'",
Toast.LENGTH_LONG,
).show()
true
}
}.also(screen::addPreference)
}
companion object {
private const val TRANSLATOR_PROVIDER_PREF = "translatorProviderPref"
}
}

View File

@ -11,19 +11,11 @@ class SnowmtlFactory : SourceFactory {
} }
private val languageList = listOf( private val languageList = listOf(
LanguageSetting("ar", disableSourceSettings = true), Language("ar", disableSourceSettings = true),
LanguageSetting("en"), Language("en"),
LanguageSetting("es"), Language("es"),
LanguageSetting("fr"), Language("fr"),
LanguageSetting("id"), Language("id"),
LanguageSetting("it"), Language("it"),
LanguageSetting("pt-BR", "pt"), Language("pt-BR", "pt"),
) )
data class LanguageSetting(
override val lang: String,
override val target: String = lang,
override val origin: String = "en",
override var fontSize: Int = 24,
override var disableSourceSettings: Boolean = false,
) : Language

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.all.solarmtl
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.multisrc.machinetranslations.LanguageImpl import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -11,7 +11,9 @@ class SolarmtlFactory : SourceFactory {
} }
private val languageList = listOf( private val languageList = listOf(
LanguageImpl("en"), Language("ar", disableSourceSettings = true),
LanguageImpl("fr"), Language("en"),
LanguageImpl("pt-BR", "pt"), Language("fr", disableTranslator = true, supportNativeTranslation = true),
Language("id"),
Language("pt-BR", "pt", disableTranslator = true, supportNativeTranslation = true),
) )