mirror of
https://github.com/null2264/yokai.git
synced 2025-07-17 14:26:54 +00:00
feat: Start using moko for i18n
This commit is contained in:
parent
872cc92c93
commit
6d531fbb5c
19 changed files with 171 additions and 44 deletions
|
@ -155,6 +155,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.core)
|
implementation(projects.core)
|
||||||
|
implementation(projects.i18n)
|
||||||
implementation(projects.sourceApi)
|
implementation(projects.sourceApi)
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
|
|
|
@ -17,7 +17,6 @@ import android.net.wifi.WifiManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
|
@ -103,30 +102,6 @@ fun Context.contextCompatDrawable(@DrawableRes resource: Int): Drawable? {
|
||||||
return ContextCompat.getDrawable(this, resource)
|
return ContextCompat.getDrawable(this, resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts to dp.
|
|
||||||
*/
|
|
||||||
val Int.pxToDp: Int
|
|
||||||
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
|
||||||
|
|
||||||
val Float.pxToDp: Float
|
|
||||||
get() = (this / Resources.getSystem().displayMetrics.density)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts to px.
|
|
||||||
*/
|
|
||||||
val Int.dpToPx: Int
|
|
||||||
get() = this.toFloat().dpToPx.toInt()
|
|
||||||
|
|
||||||
val Int.spToPx: Int
|
|
||||||
get() = this.toFloat().spToPx.toInt()
|
|
||||||
|
|
||||||
val Float.spToPx: Float
|
|
||||||
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics)
|
|
||||||
|
|
||||||
val Float.dpToPx: Float
|
|
||||||
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)
|
|
||||||
|
|
||||||
/** Converts to px and takes into account LTR/RTL layout */
|
/** Converts to px and takes into account LTR/RTL layout */
|
||||||
fun Float.dpToPxEnd(resources: Resources): Float {
|
fun Float.dpToPxEnd(resources: Resources): Float {
|
||||||
return this * resources.displayMetrics.density * if (resources.isLTR) 1 else -1
|
return this * resources.displayMetrics.density * if (resources.isLTR) 1 else -1
|
||||||
|
|
|
@ -1116,12 +1116,6 @@
|
||||||
<string name="file_picker_error">No file picker app found</string>
|
<string name="file_picker_error">No file picker app found</string>
|
||||||
<string name="file_picker_uri_permission_unsupported">Failed to acquire persistent folder access. The app may behave unexpectedly.</string>
|
<string name="file_picker_uri_permission_unsupported">Failed to acquire persistent folder access. The app may behave unexpectedly.</string>
|
||||||
|
|
||||||
<!-- Webview -->
|
|
||||||
<string name="failed_to_bypass_cloudflare">Failed to bypass Cloudflare</string>
|
|
||||||
<string name="please_update_webview">Please update the WebView app for better compatibility</string>
|
|
||||||
<!-- Do not translate "WebView" -->
|
|
||||||
<string name="webview_is_required">WebView is required for Tachiyomi</string>
|
|
||||||
|
|
||||||
<!-- App widget -->
|
<!-- App widget -->
|
||||||
<string name="appwidget_updates_description">See your recently updated library entries</string>
|
<string name="appwidget_updates_description">See your recently updated library entries</string>
|
||||||
<string name="appwidget_unavailable_locked">Widget not available when app lock is enabled</string>
|
<string name="appwidget_unavailable_locked">Widget not available when app lock is enabled</string>
|
||||||
|
|
|
@ -19,6 +19,7 @@ buildscript {
|
||||||
classpath(kotlinx.serialization.gradle)
|
classpath(kotlinx.serialization.gradle)
|
||||||
classpath(libs.firebase.crashlytics.gradle)
|
classpath(libs.firebase.crashlytics.gradle)
|
||||||
classpath(libs.sqldelight.gradle)
|
classpath(libs.sqldelight.gradle)
|
||||||
|
classpath(libs.moko.generator)
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
|
41
buildSrc/src/main/kotlin/LocalesConfigPlugin.kt
Normal file
41
buildSrc/src/main/kotlin/LocalesConfigPlugin.kt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.tasks.TaskProvider
|
||||||
|
import org.gradle.kotlin.dsl.TaskContainerScope
|
||||||
|
|
||||||
|
fun TaskContainerScope.registerLocalesConfigTask(project: Project): TaskProvider<Task> {
|
||||||
|
return with(project) {
|
||||||
|
register("generateLocalesConfig") {
|
||||||
|
val emptyResourcesElement = "<resources>\\s*</resources>|<resources/>".toRegex()
|
||||||
|
val valuesPrefix = "values-?".toRegex()
|
||||||
|
|
||||||
|
val languages = fileTree("$projectDir/src/main/res/")
|
||||||
|
.matching {
|
||||||
|
include("**/strings.xml")
|
||||||
|
}
|
||||||
|
.filterNot {
|
||||||
|
it.readText().contains(emptyResourcesElement)
|
||||||
|
}
|
||||||
|
.map { it.parentFile.name }
|
||||||
|
.sorted()
|
||||||
|
.joinToString(separator = "\n") {
|
||||||
|
val language = it
|
||||||
|
.replace(valuesPrefix, "")
|
||||||
|
.replace("-r", "-")
|
||||||
|
.takeIf(String::isNotBlank) ?: "en"
|
||||||
|
" <locale android:name=\"$language\"/>"
|
||||||
|
}
|
||||||
|
|
||||||
|
val content = """
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
$languages
|
||||||
|
</locale-config>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val localeFile = file("$projectDir/src/main/res/xml/locales_config.xml")
|
||||||
|
localeFile.parentFile.mkdirs()
|
||||||
|
localeFile.writeText(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,16 +9,18 @@ kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(libs.okhttp)
|
implementation(projects.i18n)
|
||||||
api(libs.okhttp.logging.interceptor)
|
|
||||||
api(libs.okhttp.dnsoverhttps)
|
|
||||||
api(libs.okhttp.brotli)
|
|
||||||
api(libs.okio)
|
|
||||||
api(libs.bundles.logging)
|
api(libs.bundles.logging)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val androidMain by getting {
|
val androidMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api(libs.okhttp)
|
||||||
|
api(libs.okhttp.logging.interceptor)
|
||||||
|
api(libs.okhttp.dnsoverhttps)
|
||||||
|
api(libs.okhttp.brotli)
|
||||||
|
api(libs.okio)
|
||||||
|
|
||||||
api(androidx.core)
|
api(androidx.core)
|
||||||
api(androidx.annotation)
|
api(androidx.annotation)
|
||||||
api(libs.rxjava)
|
api(libs.rxjava)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.network.AndroidCookieJar
|
import eu.kanade.tachiyomi.network.AndroidCookieJar
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||||
|
@ -15,6 +14,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import yokai.i18n.MR
|
||||||
|
import yokai.util.lang.getMString
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class CloudflareInterceptor(
|
||||||
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
||||||
// we don't crash the entire app
|
// we don't crash the entire app
|
||||||
catch (e: CloudflareBypassException) {
|
catch (e: CloudflareBypassException) {
|
||||||
throw IOException(context.getString(R.string.failed_to_bypass_cloudflare))
|
throw IOException(context.getMString(MR.strings.failed_to_bypass_cloudflare))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw IOException(e)
|
throw IOException(e)
|
||||||
}
|
}
|
||||||
|
@ -130,7 +131,7 @@ class CloudflareInterceptor(
|
||||||
if (!cloudflareBypassed) {
|
if (!cloudflareBypassed) {
|
||||||
// Prompt user to update WebView if it seems too outdated
|
// Prompt user to update WebView if it seems too outdated
|
||||||
if (isWebViewOutdated) {
|
if (isWebViewOutdated) {
|
||||||
context.toast(R.string.please_update_webview, Toast.LENGTH_LONG)
|
context.toast(MR.strings.please_update_webview, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw CloudflareBypassException()
|
throw CloudflareBypassException()
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.os.Build
|
||||||
import android.webkit.WebSettings
|
import android.webkit.WebSettings
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
|
@ -15,6 +14,7 @@ import okhttp3.Headers
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import yokai.i18n.MR
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ abstract class WebViewInterceptor(
|
||||||
|
|
||||||
if (!WebViewUtil.supportsWebView(context)) {
|
if (!WebViewUtil.supportsWebView(context)) {
|
||||||
launchUI {
|
launchUI {
|
||||||
context.toast(R.string.webview_is_required, Toast.LENGTH_LONG)
|
context.toast(MR.strings.webview_is_required, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.util.TypedValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts to dp.
|
||||||
|
*/
|
||||||
|
val Int.pxToDp: Int
|
||||||
|
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
||||||
|
|
||||||
|
val Float.pxToDp: Float
|
||||||
|
get() = (this / Resources.getSystem().displayMetrics.density)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts to px.
|
||||||
|
*/
|
||||||
|
val Int.dpToPx: Int
|
||||||
|
get() = this.toFloat().dpToPx.toInt()
|
||||||
|
|
||||||
|
val Int.spToPx: Int
|
||||||
|
get() = this.toFloat().spToPx.toInt()
|
||||||
|
|
||||||
|
val Float.spToPx: Float
|
||||||
|
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics)
|
||||||
|
|
||||||
|
val Float.dpToPx: Float
|
||||||
|
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)
|
|
@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.util.system
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import yokai.util.lang.getMString
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a toast in this context.
|
* Display a toast in this context.
|
||||||
|
@ -14,6 +16,16 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT)
|
||||||
Toast.makeText(this, resource, duration).show()
|
Toast.makeText(this, resource, duration).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a toast in this context.
|
||||||
|
*
|
||||||
|
* @param resource the text resource.
|
||||||
|
* @param duration the duration of the toast. Defaults to short.
|
||||||
|
*/
|
||||||
|
fun Context.toast(resource: StringResource, duration: Int = Toast.LENGTH_SHORT) {
|
||||||
|
toast(getMString(resource), duration)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a toast in this context.
|
* Display a toast in this context.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package yokai.util.lang
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import dev.icerock.moko.resources.desc.Resource
|
||||||
|
import dev.icerock.moko.resources.desc.StringDesc
|
||||||
|
|
||||||
|
fun Context.getMString(stringRes: StringResource): String = StringDesc.Resource(stringRes).toString(this)
|
|
@ -1,13 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class NetworkPreferences(private val preferenceStore: PreferenceStore) {
|
class NetworkPreferences(private val preferenceStore: PreferenceStore) {
|
||||||
|
|
||||||
fun dohProvider() = preferenceStore.getInt("doh_provider", -1)
|
fun dohProvider() = preferenceStore.getInt("doh_provider", -1)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
|
||||||
fun defaultUserAgent() = preferenceStore.getString("default_user_agent", NetworkHelper.DEFAULT_USER_AGENT)
|
fun defaultUserAgent() = preferenceStore.getString("default_user_agent", NetworkHelper.DEFAULT_USER_AGENT)
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ chucker = "3.5.2"
|
||||||
coil3 = "3.0.0-alpha06"
|
coil3 = "3.0.0-alpha06"
|
||||||
flexible-adapter = "c8013533"
|
flexible-adapter = "c8013533"
|
||||||
fast_adapter = "5.6.0"
|
fast_adapter = "5.6.0"
|
||||||
|
moko = "0.24.0"
|
||||||
nucleus = "3.0.0"
|
nucleus = "3.0.0"
|
||||||
okhttp = "5.0.0-alpha.14"
|
okhttp = "5.0.0-alpha.14"
|
||||||
shizuku = "12.1.0"
|
shizuku = "12.1.0"
|
||||||
|
@ -59,6 +60,10 @@ junit-android = { module = "androidx.test.ext:junit", version = "1.1.5" }
|
||||||
junrar = { module = "com.github.junrar:junrar", version = "7.5.5" }
|
junrar = { module = "com.github.junrar:junrar", version = "7.5.5" }
|
||||||
loading-button = { module = "br.com.simplepass:loading-button-android", version = "2.2.0" }
|
loading-button = { module = "br.com.simplepass:loading-button-android", version = "2.2.0" }
|
||||||
mockk = { module = "io.mockk:mockk", version = "1.13.11" }
|
mockk = { module = "io.mockk:mockk", version = "1.13.11" }
|
||||||
|
|
||||||
|
moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko" }
|
||||||
|
moko-generator = { module = "dev.icerock.moko:resources-generator", version.ref = "moko" }
|
||||||
|
|
||||||
okio = { module = "com.squareup.okio:okio", version = "3.9.0" }
|
okio = { module = "com.squareup.okio:okio", version = "3.9.0" }
|
||||||
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
||||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||||
|
|
2
i18n/.gitignore
vendored
Normal file
2
i18n/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Generated
|
||||||
|
locales_config.xml
|
49
i18n/build.gradle.kts
Normal file
49
i18n/build.gradle.kts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
id("com.android.library")
|
||||||
|
id("dev.icerock.mobile.multiplatform-resources")
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
androidTarget()
|
||||||
|
|
||||||
|
applyDefaultHierarchyTemplate()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
api(libs.moko.resources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val androidMain by getting {
|
||||||
|
dependsOn(commonMain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "yokai.i18n"
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
named("main") {
|
||||||
|
res.srcDir("src/commonMain/moko-resources")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplatformResources {
|
||||||
|
resourcesPackage.set("yokai.i18n")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
val localesConfigTask = registerLocalesConfigTask(project)
|
||||||
|
preBuild {
|
||||||
|
dependsOn(localesConfigTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-Xexpect-actual-classes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
2
i18n/src/androidMain/AndroidManifest.xml
Normal file
2
i18n/src/androidMain/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest />
|
8
i18n/src/commonMain/moko-resources/base/strings.xml
Normal file
8
i18n/src/commonMain/moko-resources/base/strings.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- WebView -->
|
||||||
|
<string name="failed_to_bypass_cloudflare">Failed to bypass Cloudflare</string>
|
||||||
|
<string name="please_update_webview">Please update the WebView app for better compatibility</string>
|
||||||
|
<!-- Do not translate "WebView" -->
|
||||||
|
<string name="webview_is_required">WebView is required for Tachiyomi</string>
|
||||||
|
</resources>
|
|
@ -33,6 +33,7 @@ rootProject.name = "Yokai"
|
||||||
include(":app")
|
include(":app")
|
||||||
include(":core")
|
include(":core")
|
||||||
include(":domain")
|
include(":domain")
|
||||||
|
include(":i18n")
|
||||||
include(":presentation-core")
|
include(":presentation-core")
|
||||||
include(":presentation-widget")
|
include(":presentation-widget")
|
||||||
include(":source-api")
|
include(":source-api")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue