Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create app localization core module #240

Merged
merged 1 commit into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ java {
dependencies {
implementation(project(":core:analytics"))
implementation(project(":core:event"))
implementation(project(":core:localization"))
implementation(project(":domain:app:data"))
implementation(project(":domain:app:core"))
implementation(project(":feature:folder-detail:event"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import br.alexandregpereira.hunter.folder.detail.di.folderDetailModule
import br.alexandregpereira.hunter.folder.insert.di.folderInsertModule
import br.alexandregpereira.hunter.folder.list.di.folderListModule
import br.alexandregpereira.hunter.folder.preview.di.folderPreviewModule
import br.alexandregpereira.hunter.localization.di.localizationModule
import br.alexandregpereira.hunter.monster.compendium.di.monsterCompendiumModule
import br.alexandregpereira.hunter.monster.content.di.monsterContentManagerModule
import br.alexandregpereira.hunter.monster.content.preview.di.monsterContentPreviewModule
Expand Down Expand Up @@ -95,7 +96,8 @@ class HunterApplication : Application() {
searchModule,
settingsModule,
spellDetailModule,
bottomBarEventModule
bottomBarEventModule,
localizationModule,
)
}
}
Expand Down
19 changes: 19 additions & 0 deletions core/localization/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
kotlin("multiplatform")
}

configureJvmTargets()

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.koin.core)
implementation(libs.kotlin.coroutines.core)
}
}
if (isMac()) {
val iosMain by getting
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package br.alexandregpereira.hunter.localization

interface AppLocalization {

fun getLanguage(): Language
}

interface MutableAppLocalization : AppLocalization {

fun setLanguage(language: String)
}

internal class AppLocalizationImpl : MutableAppLocalization {

private var language: Language = Language.ENGLISH

override fun getLanguage(): Language {
return language
}

override fun setLanguage(language: String) {
this.language = Language.entries.firstOrNull { it.code == language } ?: Language.ENGLISH
}
}

enum class Language(val code: String) {
ENGLISH("en-us"),
PORTUGUESE("pt-br")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package br.alexandregpereira.hunter.localization.di

import br.alexandregpereira.hunter.localization.AppLocalization
import br.alexandregpereira.hunter.localization.AppLocalizationImpl
import br.alexandregpereira.hunter.localization.MutableAppLocalization
import org.koin.dsl.module

val localizationModule = module {
single { AppLocalizationImpl() }
factory<MutableAppLocalization> { get<AppLocalizationImpl>() }
factory<AppLocalization> { get<AppLocalizationImpl>() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,3 @@ private class DefaultStateHolderWithRecovery<State>(
}
}
}

internal fun <State> StateHolder<State>.setState(block: State.() -> State) {
(this as MutableStateHolder).setState(block)
}
1 change: 1 addition & 0 deletions domain/settings/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":core:localization"))
implementation(libs.koin.core)
implementation(libs.kotlin.coroutines.core)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@

package br.alexandregpereira.hunter.domain.settings

import br.alexandregpereira.hunter.localization.MutableAppLocalization
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach

class GetLanguageUseCase(
private val settingsRepository: SettingsRepository
private val settingsRepository: SettingsRepository,
private val mutableAppLanguage: MutableAppLocalization,
) {

operator fun invoke(): Flow<String> {
return settingsRepository.getValue(
key = SETTING_LANGUAGE_KEY,
defaultValue = "en-us"
)
).onEach {
mutableAppLanguage.setLanguage(it)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package br.alexandregpereira.hunter.domain.settings

import br.alexandregpereira.hunter.localization.Language

object IsLanguageSupported {

private val supportedLanguage = listOf("en-us", "pt-br")
private val supportedLanguage = Language.entries.map { it.code }

operator fun invoke(lang: String): Boolean {
return supportedLanguage.contains(lang)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@
package br.alexandregpereira.hunter.domain.settings

import br.alexandregpereira.hunter.domain.settings.GetLanguageUseCase.Companion.SETTING_LANGUAGE_KEY
import br.alexandregpereira.hunter.localization.MutableAppLocalization
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf

class SaveLanguageUseCase(
private val settingsRepository: SettingsRepository
private val settingsRepository: SettingsRepository,
private val mutableAppLanguage: MutableAppLocalization,
) {

@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(lang: String): Flow<Unit> {
if (IsLanguageSupported(lang).not()) return flowOf(Unit)
return settingsRepository.saveSettings(mapOf(SETTING_LANGUAGE_KEY to lang))
return flowOf {
mutableAppLanguage.setLanguage(lang)
}.flatMapLatest {
settingsRepository.saveSettings(mapOf(SETTING_LANGUAGE_KEY to lang))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import org.koin.dsl.module
val settingsDomainModule = module {
factory { GetAlternativeSourceJsonUrlUseCase(get()) }
factory { GetContentVersionUseCase(get()) }
factory { GetLanguageUseCase(get()) }
factory { GetLanguageUseCase(get(), get()) }
factory { GetMonsterImageJsonUrlUseCase(get()) }
factory { SaveContentVersionUseCase(get()) }
factory { SaveLanguageUseCase(get()) }
factory { SaveLanguageUseCase(get(), get()) }
factory { SaveUrlsUseCase(get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal fun SpellCompendiumScreen(
) {
AppTextField(
text = state.searchText,
label = "Search",
label = state.searchTextLabel,
capitalize = false,
onValueChange = intent::onSearchTextChange
)
Expand Down
1 change: 1 addition & 0 deletions feature/spell-compendium/state-holder/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ kotlin {
dependencies {
implementation(project(":core:state-holder"))
implementation(project(":core:event"))
implementation(project(":core:localization"))
implementation(project(":domain:spell:core"))
implementation(project(":feature:spell-compendium:event"))
implementation(libs.kotlin.coroutines.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ data class SpellCompendiumState(
val isShowing: Boolean = false,
val spellsGroupByLevel: Map<String, List<SpellCompendiumItemState>> = emptyMap(),
val searchText: String = "",
val searchTextLabel: String = "",
val initialItemIndex: Int = 0,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package br.alexandregpereira.hunter.spell.compendium
import br.alexandregpereira.hunter.domain.spell.model.Spell
import br.alexandregpereira.hunter.event.EventDispatcher
import br.alexandregpereira.hunter.event.EventListener
import br.alexandregpereira.hunter.localization.AppLocalization
import br.alexandregpereira.hunter.spell.compendium.domain.GetSpellsUseCase
import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumEvent
import br.alexandregpereira.hunter.spell.compendium.event.SpellCompendiumResult
Expand All @@ -24,12 +25,14 @@ class SpellCompendiumStateHolder internal constructor(
private val stateHandler: MutableStateHolder<SpellCompendiumState>,
private val eventListener: EventListener<SpellCompendiumEvent>,
private val resultDispatcher: EventDispatcher<SpellCompendiumResult>,
private val appLocalization: AppLocalization,
) : ScopeManager(),
SpellCompendiumIntent,
StateHolder<SpellCompendiumState> by stateHandler {

private val searchQuery = MutableStateFlow(state.value.searchText)
private val originalSpellsGroupByLevel = mutableMapOf<String, List<SpellCompendiumItemState>>()
private var strings: SpellCompendiumStrings = getSpellCompendiumStrings(appLocalization.getLanguage())

init {
debounceSearch()
Expand All @@ -43,6 +46,8 @@ class SpellCompendiumStateHolder internal constructor(
spellIndex: String? = null,
selectedSpellIndexes: List<String> = emptyList(),
) {
strings = getSpellCompendiumStrings(appLocalization.getLanguage())
stateHandler.setState { copy(searchText = "", searchTextLabel = strings.searchLabel) }
getSpellsUseCase()
.onEach { spells ->
val spellsGroupByLevel = spells.groupByLevel(selectedSpellIndexes)
Expand All @@ -56,7 +61,7 @@ class SpellCompendiumStateHolder internal constructor(
stateHandler.setState {
copy(
spellsGroupByLevel = spellsGroupByLevel,
initialItemIndex = compendiumIndex?.takeIf { it >= 0 } ?: 0
initialItemIndex = compendiumIndex?.takeIf { it >= 0 } ?: 0,
)
}
}
Expand All @@ -69,9 +74,9 @@ class SpellCompendiumStateHolder internal constructor(
.onEach { text ->
stateHandler.setState {
val spellsGroupByLevel = if (text.isNotBlank()){
val spellsFiltered = spellsGroupByLevel.values.flatten()
val spellsFiltered = originalSpellsGroupByLevel.values.flatten()
.filter { it.name.contains(text, ignoreCase = true) }
mapOf("${spellsFiltered.size} results" to spellsFiltered)
mapOf(strings.searchResults(spellsFiltered.size) to spellsFiltered)
} else originalSpellsGroupByLevel

copy(spellsGroupByLevel = spellsGroupByLevel)
Expand Down Expand Up @@ -123,8 +128,8 @@ class SpellCompendiumStateHolder internal constructor(

private fun Int.getSpellLevelText(): String {
return when (this) {
0 -> "Cantrip"
else -> "Level $this"
0 -> strings.cantrips
else -> strings.level(this)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package br.alexandregpereira.hunter.spell.compendium

import br.alexandregpereira.hunter.localization.Language

internal class SpellCompendiumEnStrings : SpellCompendiumStrings {
override val searchResults: (Int) -> String = { count -> "$count results" }
override val cantrips: String = "Cantrips"
override val level: (Int) -> String = { level -> "Level $level" }
override val searchLabel: String = "Search"
}

internal class SpellCompendiumPtStrings : SpellCompendiumStrings {
override val searchResults: (Int) -> String = { count -> "$count resultados" }
override val cantrips: String = "Truques"
override val level: (Int) -> String = { level -> "${level}º Círculo" }
override val searchLabel: String = "Buscar"
}

internal interface SpellCompendiumStrings {
val searchResults: (Int) -> String
val cantrips: String
val level: (Int) -> String
val searchLabel: String
}

internal fun getSpellCompendiumStrings(lang: Language): SpellCompendiumStrings {
return when (lang) {
Language.ENGLISH -> SpellCompendiumEnStrings()
Language.PORTUGUESE -> SpellCompendiumPtStrings()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ val spellCompendiumStateModule = module {
getSpellsUseCase = get(),
stateHandler = MutableStateHolder(SpellCompendiumState()),
eventListener = get<SpellCompendiumEventManager>(),
resultDispatcher = get<SpellCompendiumEventManager>()
resultDispatcher = get<SpellCompendiumEventManager>(),
appLocalization = get(),
)
}
}
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include ':app'

include ':core:analytics'
include ':core:event'
include ':core:localization'
include ':core:state-holder'
include ':core:uuid'

Expand Down
1 change: 1 addition & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(project(":core:analytics"))
implementation(project(":core:localization"))
implementation(project(":domain:app:data"))
implementation(project(":domain:app:core"))
implementation(project(":domain:sync:core"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import br.alexandregpereira.hunter.event.folder.insert.emptyFolderInsertEventDis
import br.alexandregpereira.hunter.event.monster.lore.detail.emptyMonsterLoreDetailEventDispatcher
import br.alexandregpereira.hunter.folder.preview.event.emptyFolderPreviewEventDispatcher
import br.alexandregpereira.hunter.folder.preview.event.emptyFolderPreviewResultListener
import br.alexandregpereira.hunter.localization.di.localizationModule
import br.alexandregpereira.hunter.monster.compendium.state.di.monsterCompendiumStateModule
import br.alexandregpereira.hunter.monster.detail.di.monsterDetailStateModule
import br.alexandregpereira.hunter.monster.registration.event.emptyMonsterRegistrationEventDispatcher
Expand All @@ -38,6 +39,7 @@ fun appModules(): List<Module> = domainModules + dataModules +
monsterDetailStateModule +
syncStateModule +
analyticsModule +
localizationModule +
module {
factory { Dispatchers.Default }
factory {
Expand Down
Loading