feat: Start using moko for i18n

This commit is contained in:
Ahmad Ansori Palembani 2024-06-16 07:18:12 +07:00
parent 872cc92c93
commit 6d531fbb5c
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
19 changed files with 171 additions and 44 deletions

View file

@ -155,6 +155,7 @@ android {
dependencies {
implementation(projects.core)
implementation(projects.i18n)
implementation(projects.sourceApi)
// Compose

View file

@ -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

View file

@ -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>

View file

@ -19,6 +19,7 @@ buildscript {
classpath(kotlinx.serialization.gradle)
classpath(libs.firebase.crashlytics.gradle)
classpath(libs.sqldelight.gradle)
classpath(libs.moko.generator)
}
repositories {
gradlePluginPortal()

View 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)
}
}
}

View file

@ -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)

View file

@ -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()

View file

@ -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
}

View file

@ -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)

View file

@ -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.
*

View file

@ -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)

View file

@ -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)
}

View file

@ -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
View file

@ -0,0 +1,2 @@
# Generated
locales_config.xml

49
i18n/build.gradle.kts Normal file
View 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",
)
}
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View 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>

View file

@ -33,6 +33,7 @@ rootProject.name = "Yokai"
include(":app")
include(":core")
include(":domain")
include(":i18n")
include(":presentation-core")
include(":presentation-widget")
include(":source-api")