refactor: language model

This commit is contained in:
Acclorite 2025-08-04 13:16:29 +03:00
parent 96c3917169
commit 3f0cd812a5
No known key found for this signature in database
GPG key ID: 6E54C611F6EE8593
12 changed files with 112 additions and 89 deletions

View file

@ -7,28 +7,28 @@
package ua.acclorite.book_story.core.data
import kotlinx.collections.immutable.toPersistentList
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.core.language.Language
object CoreData {
val defaultLanguage = Language.fromCode(code = "en")
val defaultLanguage = Language.fromLanguageTag(languageTag = "en") // English
val languages = listOf(
Language.fromCode(code = "en"),
Language.fromCode(code = "uk"),
Language.fromCode(code = "de"),
Language.fromCode(code = "ar"),
Language.fromCode(code = "es"),
Language.fromCode(code = "tr"),
Language.fromCode(code = "fr"),
Language.fromCode(code = "pl"),
Language.fromCode(code = "it"),
Language.fromCode(code = "zh"),
Language.fromCode(code = "hi"),
Language.fromCode(code = "pt", displayName = "pt-BR"),
Language.fromCode(code = "ta"),
Language.fromCode(code = "ml"),
Language.fromCode(code = "cs"),
Language.fromCode(code = "ro"),
Language.fromCode(code = "be"),
Language.fromCode(code = "ja"),
Language.fromLanguageTag(languageTag = "en"), // English
Language.fromLanguageTag(languageTag = "uk"), // Ukrainian
Language.fromLanguageTag(languageTag = "de"), // German
Language.fromLanguageTag(languageTag = "ar"), // Arabic
Language.fromLanguageTag(languageTag = "es"), // Spanish
Language.fromLanguageTag(languageTag = "tr"), // Turkish
Language.fromLanguageTag(languageTag = "fr"), // French
Language.fromLanguageTag(languageTag = "pl"), // Polish
Language.fromLanguageTag(languageTag = "it"), // Italian
Language.fromLanguageTag(languageTag = "zh-CN"), // Chinese (China)
Language.fromLanguageTag(languageTag = "hi"), // Hindi
Language.fromLanguageTag(languageTag = "pt-BR"), // Portuguese (Brazil)
Language.fromLanguageTag(languageTag = "ta"), // Tamil
Language.fromLanguageTag(languageTag = "ml"), // Malayalam
Language.fromLanguageTag(languageTag = "cs"), // Czech
Language.fromLanguageTag(languageTag = "ro"), // Romanian
Language.fromLanguageTag(languageTag = "be"), // Belarusian
Language.fromLanguageTag(languageTag = "ja"), // Japanese
).sortedBy { it.displayName }.toPersistentList()
}

View file

@ -0,0 +1,32 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
package ua.acclorite.book_story.core.language
import androidx.compose.runtime.Immutable
import java.util.Locale
@Immutable
data class Language(
val locale: Locale,
val displayName: String
) {
companion object {
fun getDisplayName(locale: Locale): String {
return locale
.getDisplayName(locale)
.replaceFirstChar { it.uppercaseChar() }
}
fun fromLanguageTag(languageTag: String): Language {
val locale = Locale.forLanguageTag(languageTag)
return Language(
locale = locale,
displayName = getDisplayName(locale)
)
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
package ua.acclorite.book_story.core.language
import java.util.Locale
object LanguageUtils {
fun findLanguage(
languages: List<Language>,
locale: Locale,
defaultLanguage: Language
): Language {
// Searching for exact locale match
languages.find { language ->
language.locale == locale
}?.let { return it }
// Searching for localLe language match
languages.find { language ->
language.locale.language == locale.language
}?.let { return it }
return defaultLanguage
}
}

View file

@ -1,24 +0,0 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
package ua.acclorite.book_story.core.model
import androidx.compose.runtime.Immutable
@Immutable
data class Language(
val languageCode: LanguageCode,
val displayName: String
) {
companion object {
fun fromCode(code: String, displayName: String = code): Language {
return Language(
languageCode = LanguageCode(code = code),
displayName = LanguageCode.getDisplayName(code = displayName)
)
}
}
}

View file

@ -1,22 +0,0 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
package ua.acclorite.book_story.core.model
import androidx.compose.runtime.Immutable
import java.util.Locale
@Immutable
@JvmInline
value class LanguageCode(val code: String) {
companion object {
fun getDisplayName(code: String): String {
val target = Locale.forLanguageTag(code)
val displayName = target.getDisplayName(target)
return displayName.replaceFirstChar { it.uppercaseChar() }
}
}
}

View file

@ -20,8 +20,9 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ua.acclorite.book_story.core.data.CoreData
import ua.acclorite.book_story.core.language.Language
import ua.acclorite.book_story.core.language.LanguageUtils
import ua.acclorite.book_story.core.log.logI
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.data.local.data_store.DataStore
import ua.acclorite.book_story.data.settings.model.Setting
import ua.acclorite.book_story.presentation.browse.model.BrowseLayout
@ -63,19 +64,23 @@ class SettingsManager @Inject constructor(
/* ------ Settings --------------------------- */
/* ------ General ---------------------------- */
val language =
setting<Language, String>(
key = stringPreferencesKey("language"),
default = Locale.getDefault().language.take(2).let { defaultLocale ->
CoreData.languages.find { locale -> locale.languageCode.code == defaultLocale }
?: CoreData.defaultLanguage
},
serialize = { it.languageCode.code },
deserialize = { code ->
CoreData.languages.find { locale -> locale.languageCode.code == code }
?: CoreData.defaultLanguage
}
)
val language = setting<Language, String>(
key = stringPreferencesKey("language"),
default = LanguageUtils.findLanguage(
languages = CoreData.languages,
locale = Locale.getDefault(),
defaultLanguage = CoreData.defaultLanguage
),
serialize = { it.locale.toLanguageTag() },
deserialize = { languageTag ->
val locale = Locale.forLanguageTag(languageTag)
LanguageUtils.findLanguage(
languages = CoreData.languages,
locale = locale,
defaultLanguage = CoreData.defaultLanguage
)
}
)
val theme = setting<Theme, String>(
key = stringPreferencesKey("theme"), default = Theme.Companion.entries().first(),
serialize = { it.name }, deserialize = { Theme.valueOf(it) }

View file

@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.update
@Stable
class Setting<T, P>(
val key: Preferences.Key<P>,
default: T,
private val default: T,
private val setSetting: (P) -> Unit,
private val serialize: (T) -> P,
private val deserialize: (P) -> T
@ -33,7 +33,10 @@ class Setting<T, P>(
}
fun init(value: P?) {
if (value == null) return
if (value == null) {
setSetting(serialize(default))
return
}
_value.update { deserialize(value) }
}
}

View file

@ -8,7 +8,7 @@ package ua.acclorite.book_story.domain.use_case.settings
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.core.language.Language
import ua.acclorite.book_story.data.settings.SettingsManager
import javax.inject.Inject
@ -17,7 +17,7 @@ class UpdateLanguageUseCase @Inject constructor(
) {
operator fun invoke(language: Language) {
val appLocale = LocaleListCompat.forLanguageTags(language.languageCode.code)
val appLocale = LocaleListCompat.forLanguageTags(language.locale.toLanguageTag())
AppCompatDelegate.setApplicationLocales(appLocale)
settings.language.update(language)
}

View file

@ -11,7 +11,7 @@ import android.net.Uri
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import ua.acclorite.book_story.core.ID
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.core.language.Language
import ua.acclorite.book_story.domain.model.library.Category
import ua.acclorite.book_story.presentation.library.model.LibrarySortOrder

View file

@ -10,7 +10,7 @@ import androidx.compose.foundation.background
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.core.language.Language
import ua.acclorite.book_story.presentation.navigator.StackEvent
import ua.acclorite.book_story.presentation.start.StartScreen
import ua.acclorite.book_story.ui.common.model.ListItem

View file

@ -7,7 +7,7 @@
package ua.acclorite.book_story.ui.start
import androidx.compose.runtime.Composable
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.core.language.Language
import ua.acclorite.book_story.presentation.navigator.StackEvent
import ua.acclorite.book_story.presentation.start.StartScreen
import ua.acclorite.book_story.ui.common.model.ListItem

View file

@ -17,7 +17,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.model.Language
import ua.acclorite.book_story.core.language.Language
import ua.acclorite.book_story.ui.common.components.dialog.SelectableDialogItem
import ua.acclorite.book_story.ui.common.model.ListItem
import ua.acclorite.book_story.ui.settings.components.SettingsSubcategoryTitle
@ -35,7 +35,7 @@ fun LazyListScope.StartSettingsLayoutGeneral(
Spacer(modifier = Modifier.height(12.dp))
}
items(languages, key = { it.item }) { item ->
items(languages) { item ->
SelectableDialogItem(
selected = item.selected,
title = item.title,