Skip to content

Commit

Permalink
Create app localization core module (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandregpereira authored Jan 24, 2024
1 parent 75e63aa commit e146912
Show file tree
Hide file tree
Showing 20 changed files with 138 additions and 19 deletions.
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

0 comments on commit e146912

Please sign in to comment.