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 {
|
||||
implementation(projects.core)
|
||||
implementation(projects.i18n)
|
||||
implementation(projects.sourceApi)
|
||||
|
||||
// Compose
|
||||
|
|
|
@ -17,7 +17,6 @@ import android.net.wifi.WifiManager
|
|||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
|
@ -103,30 +102,6 @@ fun Context.contextCompatDrawable(@DrawableRes resource: Int): Drawable? {
|
|||
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 */
|
||||
fun Float.dpToPxEnd(resources: Resources): Float {
|
||||
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_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 -->
|
||||
<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>
|
||||
|
|
|
@ -19,6 +19,7 @@ buildscript {
|
|||
classpath(kotlinx.serialization.gradle)
|
||||
classpath(libs.firebase.crashlytics.gradle)
|
||||
classpath(libs.sqldelight.gradle)
|
||||
classpath(libs.moko.generator)
|
||||
}
|
||||
repositories {
|
||||
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 {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(libs.okhttp)
|
||||
api(libs.okhttp.logging.interceptor)
|
||||
api(libs.okhttp.dnsoverhttps)
|
||||
api(libs.okhttp.brotli)
|
||||
api(libs.okio)
|
||||
implementation(projects.i18n)
|
||||
api(libs.bundles.logging)
|
||||
}
|
||||
}
|
||||
val androidMain by getting {
|
||||
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.annotation)
|
||||
api(libs.rxjava)
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
|||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.network.AndroidCookieJar
|
||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||
|
@ -15,6 +14,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
|||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getMString
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.*
|
||||
|
||||
|
@ -48,7 +49,7 @@ class CloudflareInterceptor(
|
|||
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
||||
// we don't crash the entire app
|
||||
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) {
|
||||
throw IOException(e)
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ class CloudflareInterceptor(
|
|||
if (!cloudflareBypassed) {
|
||||
// Prompt user to update WebView if it seems too outdated
|
||||
if (isWebViewOutdated) {
|
||||
context.toast(R.string.please_update_webview, Toast.LENGTH_LONG)
|
||||
context.toast(MR.strings.please_update_webview, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
throw CloudflareBypassException()
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.os.Build
|
|||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
|
@ -15,6 +14,7 @@ import okhttp3.Headers
|
|||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import yokai.i18n.MR
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
|
||||
|
@ -56,7 +56,7 @@ abstract class WebViewInterceptor(
|
|||
|
||||
if (!WebViewUtil.supportsWebView(context)) {
|
||||
launchUI {
|
||||
context.toast(R.string.webview_is_required, Toast.LENGTH_LONG)
|
||||
context.toast(MR.strings.webview_is_required, Toast.LENGTH_LONG)
|
||||
}
|
||||
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.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import yokai.util.lang.getMString
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||
|
||||
class NetworkPreferences(private val preferenceStore: PreferenceStore) {
|
||||
|
||||
fun dohProvider() = preferenceStore.getInt("doh_provider", -1)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
||||
fun defaultUserAgent() = preferenceStore.getString("default_user_agent", NetworkHelper.DEFAULT_USER_AGENT)
|
||||
}
|
|
@ -3,6 +3,7 @@ chucker = "3.5.2"
|
|||
coil3 = "3.0.0-alpha06"
|
||||
flexible-adapter = "c8013533"
|
||||
fast_adapter = "5.6.0"
|
||||
moko = "0.24.0"
|
||||
nucleus = "3.0.0"
|
||||
okhttp = "5.0.0-alpha.14"
|
||||
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" }
|
||||
loading-button = { module = "br.com.simplepass:loading-button-android", version = "2.2.0" }
|
||||
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" }
|
||||
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", 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(":core")
|
||||
include(":domain")
|
||||
include(":i18n")
|
||||
include(":presentation-core")
|
||||
include(":presentation-widget")
|
||||
include(":source-api")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue