Add multiple languages

This commit is contained in:
2025-02-09 18:31:51 +01:00
parent 47e1b47028
commit 66617923b7
11 changed files with 141 additions and 20 deletions

View File

@@ -69,6 +69,7 @@ kotlin {
implementation(compose.runtime) implementation(compose.runtime)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material) implementation(compose.material)
implementation(compose.material3)
implementation(compose.ui) implementation(compose.ui)
implementation(compose.components.resources) implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview) implementation(compose.components.uiToolingPreview)
@@ -86,8 +87,7 @@ kotlin {
implementation(libs.composeIcons.tablerIcons) implementation(libs.composeIcons.tablerIcons)
implementation(libs.composeIcons.fontAwesome) implementation(libs.composeIcons.fontAwesome)
implementation(libs.korau) implementation(libs.korau)
implementation(libs.flagkit)
} }
desktopMain.dependencies { desktopMain.dependencies {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)

View File

@@ -21,6 +21,8 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import kotlinx.coroutines.flow.observeOn import kotlinx.coroutines.flow.observeOn
import org.koin.compose.koinInject import org.koin.compose.koinInject
@@ -45,6 +47,13 @@ import models.Term
import models.TermFull import models.TermFull
import kotlinx.coroutines.* import kotlinx.coroutines.*
import service.DatabaseFactory.getTranslationForLanguages import service.DatabaseFactory.getTranslationForLanguages
import service.DatabaseFactory.getLanguageForCode
import service.DatabaseFactory.getDictionaies
import service.DatabaseFactory.getLanguageForId
import dev.carlsen.flagkit.FlagKit
import dev.carlsen.flagkit.FlagIcons
import models.Dictionary
@Composable @Composable
fun RowScope.TableCell( fun RowScope.TableCell(
@@ -61,6 +70,42 @@ fun RowScope.TableCell(
) )
} }
@Composable
fun LangugeDropdown(onChangeClick: (dict: Dictionary) -> Unit,dictionary: Dictionary?) {
var expanded by remember { mutableStateOf(false) }
IconButton(onClick = {
expanded = !expanded
}) {
getLanguageForId(dictionary!!.lang1Id)?.let{ l ->
FlagKit.getFlag(countryCode = l.alphaCode!!)?.let {
Image(
imageVector = it,
contentDescription = "English",
)
}
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
for ((id, dict) in getDictionaies()) {
getLanguageForId(dict.lang1Id)?.let { l ->
DropdownMenuItem(
leadingIcon = {
Image(
imageVector = FlagKit.getFlag(countryCode = l.alphaCode!!)!!,
contentDescription = l.name
)
},
text = { Text(l.name) },
onClick = { onChangeClick(dict); expanded = false }
)
}
}
}
}
}
fun groupByString(terms: List<TermFull>, direction: Int): MutableList<MutableList<TermFull>> { fun groupByString(terms: List<TermFull>, direction: Int): MutableList<MutableList<TermFull>> {
val fullList: MutableList<MutableList<TermFull>> = mutableListOf() val fullList: MutableList<MutableList<TermFull>> = mutableListOf()
@@ -92,7 +137,9 @@ fun SearchBarTextField(viewModel: MainModelView) {
val query = remember { mutableStateOf("") } val query = remember { mutableStateOf("") }
var columnViewType = remember { mutableStateOf(false) } var columnViewType = remember { mutableStateOf(false) }
var tDirection = remember { mutableStateOf(1) } var tDirection = remember { mutableStateOf(1) }
Column { Column {
val dict by viewModel.dictionary.collectAsState()
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.End, // horizontalArrangement = Arrangement.End,
@@ -103,9 +150,14 @@ fun SearchBarTextField(viewModel: MainModelView) {
value = query.value, value = query.value,
onValueChange = { onValueChange = {
query.value = it query.value = it
val trans = if (tDirection.value == 1) getTranslationForLanguages("an","sl") else getTranslationForLanguages("sl","an") if (dict == null) return@TextField
if (trans == null) return@TextField val l1 = getLanguageForId(dict!!.lang1Id)
viewModel.getTerms(query.value,trans) val l2 = getLanguageForId(dict!!.lang2Id)
if (l1 != null && l2 != null) {
val trans = if (tDirection.value == 1) getTranslationForLanguages(l1.shortName,l2.shortName) else getTranslationForLanguages(l2.shortName,l1.shortName)
if (trans == null) return@TextField
viewModel.getTerms(query.value, trans)
}
}, },
placeholder = { Text("Prelož...") }, placeholder = { Text("Prelož...") },
singleLine = true, singleLine = true,
@@ -122,7 +174,7 @@ fun SearchBarTextField(viewModel: MainModelView) {
}, },
modifier = Modifier.weight(3f) modifier = Modifier.weight(3f)
) )
Row(Modifier.width(155.dp).border(width = 2.dp, Color.Black)) { Row(Modifier.width(200.dp).border(width = 2.dp, Color.Black)) {
Spacer(modifier = Modifier.padding(2.dp)) Spacer(modifier = Modifier.padding(2.dp))
IconButton(onClick = { IconButton(onClick = {
columnViewType.value = !columnViewType.value columnViewType.value = !columnViewType.value
@@ -157,6 +209,10 @@ fun SearchBarTextField(viewModel: MainModelView) {
modifier = Modifier.size(width = 50.dp, height = 50.dp) modifier = Modifier.size(width = 50.dp, height = 50.dp)
) )
} }
LangugeDropdown(
onChangeClick = viewModel::changeDictionary,
dictionary = dict
)
Spacer(modifier = Modifier.padding(2.dp)) Spacer(modifier = Modifier.padding(2.dp))
Icon( Icon(
Icons.Filled.Settings, Icons.Filled.Settings,
@@ -191,8 +247,14 @@ fun SearchBarTextField(viewModel: MainModelView) {
) else Color.hsl(hue = 217f, saturation = .77f, lightness = .68f, alpha = 1f) ) else Color.hsl(hue = 217f, saturation = .77f, lightness = .68f, alpha = 1f)
Row(Modifier.fillMaxWidth().background(color)) { Row(Modifier.fillMaxWidth().background(color)) {
TableCell(text = if (tDirection.value == 1) t.string1 else t.string2, weight = column1Weight) TableCell(
TableCell(text = if (tDirection.value == 1) t.string2 else t.string1, weight = column2Weight) text = if (tDirection.value == 1) t.string1 else t.string2,
weight = column1Weight
)
TableCell(
text = if (tDirection.value == 1) t.string2 else t.string1,
weight = column2Weight
)
} }
@@ -215,7 +277,10 @@ fun SearchBarTextField(viewModel: MainModelView) {
Row(Modifier.fillMaxWidth().background(color = rowBackground)) { Row(Modifier.fillMaxWidth().background(color = rowBackground)) {
Column { Column {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( if (tDirection.value == 1) t.string1 else t.string2, fontWeight = FontWeight.Bold) Text(
if (tDirection.value == 1) t.string1 else t.string2,
fontWeight = FontWeight.Bold
)
for (p in t.pronunciations!!) { for (p in t.pronunciations!!) {
Spacer(modifier = Modifier.padding(2.dp)) Spacer(modifier = Modifier.padding(2.dp))
if (p.ipa != null) { if (p.ipa != null) {
@@ -246,7 +311,10 @@ fun SearchBarTextField(viewModel: MainModelView) {
} }
} }
val str2 = if (tDirection.value == 1) t2.joinToString(separator = ", ") { it.string2 } else t2.joinToString(separator = ", ") { it.string1 } val str2 =
if (tDirection.value == 1) t2.joinToString(separator = ", ") { it.string2 } else t2.joinToString(
separator = ", "
) { it.string1 }
Text(text = str2, modifier = Modifier.padding(start = 20.dp)) Text(text = str2, modifier = Modifier.padding(start = 20.dp))
Spacer( Spacer(
modifier = Modifier modifier = Modifier

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import korlibs.audio.sound.* import korlibs.audio.sound.*
import korlibs.io.file.std.resourcesVfs import korlibs.io.file.std.resourcesVfs
import korlibs.io.file.std.ZipVfs
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -13,12 +14,20 @@ import service.DatabaseFactory.getTranslationForLanguages
import service.SearchType import service.SearchType
import service.TermServiceImpl import service.TermServiceImpl
import korlibs.audio.format.* import korlibs.audio.format.*
import korlibs.io.compression.zip.ZipEntry
import korlibs.io.file.VfsFile
import korlibs.io.file.std.openAsZip
import models.Dictionary
import models.Translation import models.Translation
import service.DatabaseFactory.getDictionaies
class MainModelView(private val repository: TermServiceImpl): ViewModel() { class MainModelView(private val repository: TermServiceImpl): ViewModel() {
val terms : StateFlow<List<TermFull>?> get() = _terms val terms : StateFlow<List<TermFull>?> get() = _terms
private val _terms = MutableStateFlow<List<TermFull>?>(null) private val _terms = MutableStateFlow<List<TermFull>?>(null)
val dictionary: StateFlow<Dictionary?> get() = _dictionary
private val _dictionary = MutableStateFlow<Dictionary?>(getDictionaies()[1])
fun getTerms(term: String, trans: Translation) { fun getTerms(term: String, trans: Translation) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
var sType = SearchType.START var sType = SearchType.START
@@ -28,6 +37,11 @@ class MainModelView(private val repository: TermServiceImpl): ViewModel() {
} }
} }
fun changeDictionary(dict: Dictionary) {
_dictionary.value = dict
}
fun clearSearch() { fun clearSearch() {
_terms.value = listOf() _terms.value = listOf()

View File

@@ -17,6 +17,7 @@ assertjVersion = "3.26.3"
compose-multiplatform = "1.7.0" compose-multiplatform = "1.7.0"
coreKtx = "1.9.0" coreKtx = "1.9.0"
exposedVersion = "0.55.0" exposedVersion = "0.55.0"
flagkit = "1.1.0"
flywayVersion = "10.20.1" flywayVersion = "10.20.1"
h2 = "2.3.232" h2 = "2.3.232"
hikariCpVersion = "5.1.0" hikariCpVersion = "5.1.0"
@@ -67,6 +68,7 @@ lifecycleViewmodelKtx = "2.6.1"
lifecycleViewmodelCompose = "2.8.4" lifecycleViewmodelCompose = "2.8.4"
logback = "1.5.12" logback = "1.5.12"
material = "1.7.6" material = "1.7.6"
okio = "3.10.2"
psqlVersion = "42.7.4" psqlVersion = "42.7.4"
restAssuredVersion = "5.5.0" restAssuredVersion = "5.5.0"
runtimeLivedata = "1.7.6" runtimeLivedata = "1.7.6"
@@ -89,6 +91,7 @@ composeIcons-linea = { module = "br.com.devsrsouza.compose.icons:linea", version
composeIcons-octicons = { module = "br.com.devsrsouza.compose.icons:octicons", version.ref = "composeIcons" } composeIcons-octicons = { module = "br.com.devsrsouza.compose.icons:octicons", version.ref = "composeIcons" }
composeIcons-simpleIcons = { module = "br.com.devsrsouza.compose.icons:simple-icons", version.ref = "composeIcons" } composeIcons-simpleIcons = { module = "br.com.devsrsouza.compose.icons:simple-icons", version.ref = "composeIcons" }
composeIcons-tablerIcons = { module = "br.com.devsrsouza.compose.icons:tabler-icons", version.ref = "composeIcons" } composeIcons-tablerIcons = { module = "br.com.devsrsouza.compose.icons:tabler-icons", version.ref = "composeIcons" }
flagkit = { module = "dev.carlsen.flagkit:flagkit", version.ref = "flagkit" }
korau = { module = "com.soywiz.korlibs.korau:korau", version.ref = "korau" } korau = { module = "com.soywiz.korlibs.korau:korau", version.ref = "korau" }
androidx-core-ktx-v190 = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } androidx-core-ktx-v190 = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
@@ -182,6 +185,7 @@ ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "kto
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" }
ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" }
material = { module = "androidx.compose.material:material", version.ref = "material" } material = { module = "androidx.compose.material:material", version.ref = "material" }
okio = { module = "com.squareup.okio:okio-bom", version.ref = "okio" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "psqlVersion" } postgresql = { module = "org.postgresql:postgresql", version.ref = "psqlVersion" }
rest-assured = { module = "io.rest-assured:rest-assured", version.ref = "restAssuredVersion" } rest-assured = { module = "io.rest-assured:rest-assured", version.ref = "restAssuredVersion" }
symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "symbolProcessingApi" } symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "symbolProcessingApi" }

View File

@@ -9,10 +9,12 @@ class LanguageDao(id: EntityID<Int>) : IntEntity(id) {
var name by Languages.name var name by Languages.name
var shortName by Languages.shortName var shortName by Languages.shortName
var alphaCode by Languages.alphaCode
fun toModel() : Language = Language( fun toModel() : Language = Language(
id = id.value, id = id.value,
name = name, name = name,
shortName = shortName shortName = shortName,
alphaCode = alphaCode
) )
} }

View File

@@ -9,7 +9,8 @@ import org.jetbrains.exposed.sql.Table
data class Language( data class Language(
val id: Int, val id: Int,
val name: String, val name: String,
val shortName: String val shortName: String,
val alphaCode: String?
) )

View File

@@ -13,6 +13,8 @@ import javax.sql.DataSource
import tables.* import tables.*
import dao.* import dao.*
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import models.Dictionary
import models.Language
import models.Translation import models.Translation
object DatabaseFactory { object DatabaseFactory {
@@ -21,7 +23,9 @@ object DatabaseFactory {
private val dbs : MutableMap<Int,Database> = mutableMapOf() private val dbs : MutableMap<Int,Database> = mutableMapOf()
private val hdb : MutableMap<String,Database> = mutableMapOf() private val hdb : MutableMap<String,Database> = mutableMapOf()
private val htrans: MutableMap<String,Translation> = mutableMapOf() private val htrans: MutableMap<String,Translation> = mutableMapOf()
private val halpha: MutableMap<String,Language> = mutableMapOf()
private val dictById: MutableMap<Int,Dictionary> = mutableMapOf()
private val hLang1: MutableMap<Int,Language> = mutableMapOf()
fun connectAndMigrate() { fun connectAndMigrate() {
log.info("Initialising database") log.info("Initialising database")
val pool = hikari() val pool = hikari()
@@ -43,10 +47,17 @@ object DatabaseFactory {
return HikariDataSource(config) return HikariDataSource(config)
} }
fun getTranslationForLanguages(l1: String, l2: String): Translation? = htrans["${l1}${l2}"]?: null fun getTranslationForLanguages(l1: String, l2: String): Translation? = htrans["${l1.lowercase()}${l2.lowercase()}"]?: null
fun getLanguageForCode(l: String): Language? = halpha[l.uppercase()]?: null
fun getLanguageForId(id: Int): Language? = hLang1[id]?: null
fun getDictionaies(): Map<Int,Dictionary> = dictById
fun getAllLanguages(): List<Language> {
return halpha.values.toList()
}
fun connectAll() {
if (getDictionaies().any()) return
fun connectAll(): Map<Int,Database> {
val hMap : MutableMap<Int, Database> = mutableMapOf()
transaction { transaction {
for (trans in TranslationDao.all()) { for (trans in TranslationDao.all()) {
val l1 = trans.lang1.shortName.lowercase() val l1 = trans.lang1.shortName.lowercase()
@@ -55,6 +66,11 @@ object DatabaseFactory {
htrans["${l1}${l2}"] = trans.toModel() htrans["${l1}${l2}"] = trans.toModel()
} }
for (lang in LanguageDao.all()) {
halpha[lang.shortName] = lang.toModel()
hLang1[lang.id.value] = lang.toModel()
}
for (dict in DictionaryDao.all()) { for (dict in DictionaryDao.all()) {
val cfg = ConfigFactory.load().getConfig("h2") val cfg = ConfigFactory.load().getConfig("h2")
val config = HikariConfig().apply { val config = HikariConfig().apply {
@@ -68,10 +84,14 @@ object DatabaseFactory {
val db = HikariDataSource(config) val db = HikariDataSource(config)
val dbc = Database.connect(db) val dbc = Database.connect(db)
dbs[dict.id.value] = dbc dbs[dict.id.value] = dbc
dictById[dict.id.value] = dict.toModel()
hdb["${dict.lang1.shortName.lowercase()}${dict.lang2.shortName.lowercase()}"] = dbc hdb["${dict.lang1.shortName.lowercase()}${dict.lang2.shortName.lowercase()}"] = dbc
} }
} }
return hMap println("DBS PRINT")
println(dbs)
} }
private fun runFlyway(datasource: DataSource) { private fun runFlyway(datasource: DataSource) {

View File

@@ -6,4 +6,7 @@ import models.Language
interface LanguageService { interface LanguageService {
fun getLanguage(id: Int): Language? fun getLanguage(id: Int): Language?
fun getAllLanguages(): List<Language> fun getAllLanguages(): List<Language>
fun getAlphaCode4String(code: String): Language?
fun getAlphaCode4Lang(lang: Language): Language?
} }

View File

@@ -3,6 +3,7 @@ package service
import dao.LanguageDao import dao.LanguageDao
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import service.DatabaseFactory.dbExecId import service.DatabaseFactory.dbExecId
import service.DatabaseFactory.getLanguageForCode
import models.Language import models.Language
class LanguageServiceImpl : LanguageService { class LanguageServiceImpl : LanguageService {
@@ -14,4 +15,12 @@ class LanguageServiceImpl : LanguageService {
LanguageDao.all().map { it.toModel() } LanguageDao.all().map { it.toModel() }
} }
override fun getAlphaCode4String(code: String): Language? {
return getLanguageForCode(code)
}
override fun getAlphaCode4Lang(lang: Language): Language? {
return getLanguageForCode(lang.shortName)
}
} }

View File

@@ -8,5 +8,5 @@ object Languages : IntIdTable() {
val name = varchar("name", 255) val name = varchar("name", 255)
val shortName = varchar("short_name", 255) val shortName = varchar("short_name", 255)
val alphaCode = char("alpha_code",2).nullable()
} }

View File

@@ -6,8 +6,8 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import tables.Suffixes import tables.Suffixes
object Terms : IntIdTable() { object Terms : IntIdTable() {
val string1 = varchar("string1", 255) val string1 = varchar("string1", 255).index()
val string2 = varchar("string2", 255) val string2 = varchar("string2", 255).index()
val suffix1 = reference("suffix1_id",Suffixes).nullable() val suffix1 = reference("suffix1_id",Suffixes).nullable()
val suffix2 = reference("suffix2_id",Suffixes).nullable() val suffix2 = reference("suffix2_id",Suffixes).nullable()
val type = reference("type_id",DictTypes).nullable() val type = reference("type_id",DictTypes).nullable()