Skip to content

Commit c3569db

Browse files
authored
Merge pull request #40 from Paulanerus/dev
Dev
2 parents 87db4bf + 5e6fcbf commit c3569db

14 files changed

Lines changed: 630 additions & 162 deletions

File tree

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ coroutines.version=1.10.2
1212

1313
api.version=1.10.0
1414
core.version=1.13.0
15-
ui.version=1.13.1
16-
app.version=1.3.4
15+
ui.version=1.14.1
16+
app.version=1.4.0

ui/src/main/kotlin/dev/paulee/ui/App.kt

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import androidx.compose.runtime.getValue
99
import androidx.compose.runtime.mutableStateOf
1010
import androidx.compose.runtime.setValue
1111
import androidx.compose.ui.Modifier
12+
import java.awt.Color
13+
import java.util.*
1214

13-
object App {
15+
internal object App {
1416
const val NAME = "TextVariant Explorer"
1517

1618
const val APP_DIR = ".textexplorer"
@@ -25,22 +27,41 @@ object App {
2527

2628
val VERSION_STRING = "v$VERSION (API - $apiVersion, Core - $coreVersion, UI - $uiVersion)"
2729

30+
enum class SupportedLanguage(val tag: String) {
31+
Deutsch("de"),
32+
English("en");
33+
34+
val locale: Locale = Locale.forLanguageTag(tag)
35+
36+
companion object {
37+
fun fromTag(tag: String?): SupportedLanguage = entries.firstOrNull { it.tag == tag } ?: English
38+
}
39+
}
40+
41+
object Language {
42+
var current by mutableStateOf(SupportedLanguage.fromTag(Config.lang))
43+
private set
44+
45+
fun set(lang: SupportedLanguage) {
46+
this.current = lang
47+
Config.lang = lang.tag
48+
}
49+
}
50+
2851
object Colors {
2952

30-
val GREEN_HIGHLIGHT: java.awt.Color
31-
get() = java.awt.Color(0, 200, 83)
53+
val GREEN_HIGHLIGHT: Color
54+
get() = Color(0, 200, 83)
3255

33-
val RED_HIGHLIGHT: java.awt.Color
34-
get() = java.awt.Color(200, 0, 0)
56+
val RED_HIGHLIGHT: Color
57+
get() = Color(200, 0, 0)
3558

3659
}
3760

3861
enum class ThemeMode { Light, Dark, System }
3962

4063
object Theme {
41-
var mode by mutableStateOf(
42-
runCatching { ThemeMode.valueOf(Config.theme) }.getOrElse { ThemeMode.System }
43-
)
64+
var mode by mutableStateOf(runCatching { ThemeMode.valueOf(Config.theme) }.getOrElse { ThemeMode.System })
4465
private set
4566

4667
fun set(mode: ThemeMode) {
@@ -62,7 +83,9 @@ object App {
6283

6384
MaterialTheme {
6485
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
65-
content()
86+
WithLangScope(Language.current.locale) {
87+
content()
88+
}
6689
}
6790
}
6891
}

ui/src/main/kotlin/dev/paulee/ui/Config.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ object Config {
1616

1717
var theme by mutableStateOf("Light")
1818

19+
var lang by mutableStateOf("en")
20+
1921
var noWidthRestriction by mutableStateOf(false)
2022

2123
var exactHighlighting by mutableStateOf(true)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dev.paulee.ui
2+
3+
import androidx.compose.runtime.*
4+
import java.text.MessageFormat
5+
import java.util.*
6+
7+
internal data class I18n(private val resolver: (String, Array<out Any?>) -> String) {
8+
operator fun get(key: String, vararg args: Any?) = resolver(key, args)
9+
}
10+
11+
internal val LocalI18n = staticCompositionLocalOf<I18n> { error("No I18n provided") }
12+
13+
@Composable
14+
internal fun WithLangScope(
15+
locale: Locale,
16+
bundleName: String = "i18n/strings",
17+
content: @Composable () -> Unit
18+
) {
19+
val current by remember(locale) { mutableStateOf(ResourceBundle.getBundle(bundleName, locale)) }
20+
21+
val fallback by remember { mutableStateOf(ResourceBundle.getBundle(bundleName, Locale.ENGLISH)) }
22+
23+
val provider = remember(locale, current) {
24+
I18n { key, args ->
25+
val pattern = runCatching { current.getString(key) }
26+
.recoverCatching { fallback.getString(key) }
27+
.getOrDefault(key)
28+
29+
if (args.isEmpty()) pattern else MessageFormat(pattern, locale).format(args)
30+
}
31+
}
32+
33+
CompositionLocalProvider(LocalI18n provides provider, content = content)
34+
}

ui/src/main/kotlin/dev/paulee/ui/TextExplorerUI.kt

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class TextExplorerUI(
7474

7575
@Composable
7676
private fun content() {
77+
val locale = LocalI18n.current
78+
7779
var textField by remember { mutableStateOf(TextFieldValue("")) }
7880
var selectedRows by remember { mutableStateOf(listOf<Map<String, String>>()) }
7981
var openWindow by remember { mutableStateOf(Window.NONE) }
@@ -96,7 +98,7 @@ class TextExplorerUI(
9698
var selectedText = remember(this.poolSelected) {
9799
val (pool, field) = dataService.getSelectedPool().split(".", limit = 2)
98100

99-
if (pool == "null") "No source available"
101+
if (pool == "null") locale["main.no_source"]
100102
else "$pool ($field)"
101103
}
102104

@@ -176,15 +178,15 @@ class TextExplorerUI(
176178
}
177179
}
178180

179-
DropDownMenu(
181+
IconDropDown(
180182
modifier = Modifier.align(Alignment.TopEnd),
181-
items = listOf("Load Plugin", "Load Data", "Plugin Info", "---", "Settings"),
183+
items = listOf("setting.load_plugin", "setting.load_data", "plugin.title", "---", "settings.title"),
182184
) {
183185
when (it) {
184-
"Load Plugin" -> openWindow = Window.LOAD_PLUGIN
185-
"Load Data" -> openWindow = Window.LOAD_DATA
186-
"Settings" -> openWindow = Window.SETTINGS
187-
"Plugin Info" -> openWindow = Window.PLUGIN_INFO
186+
"setting.load_plugin" -> openWindow = Window.LOAD_PLUGIN
187+
"setting.load_data" -> openWindow = Window.LOAD_DATA
188+
"plugin.title" -> openWindow = Window.PLUGIN_INFO
189+
"settings.title" -> openWindow = Window.SETTINGS
188190
}
189191
}
190192

@@ -222,7 +224,7 @@ class TextExplorerUI(
222224
showSuggestions = false
223225
}
224226
},
225-
placeholder = { Text("Search...") },
227+
placeholder = { Text(locale["main.search.placeholder"]) },
226228
modifier = Modifier
227229
.width(600.dp)
228230
.background(
@@ -289,7 +291,7 @@ class TextExplorerUI(
289291
showSuggestions = false
290292
showTable = false
291293
}) {
292-
Icon(Icons.Default.Close, contentDescription = "Close")
294+
Icon(Icons.Default.Close, contentDescription = locale["main.icon.close"])
293295
}
294296
}
295297
)
@@ -323,7 +325,7 @@ class TextExplorerUI(
323325
modifier = Modifier.height(70.dp).padding(horizontal = 10.dp),
324326
enabled = textField.text.isNotBlank() && dataService.hasSelectedPool()
325327
) {
326-
Icon(Icons.Default.Search, contentDescription = "Search")
328+
Icon(Icons.Default.Search, contentDescription = locale["main.icon.search"])
327329
}
328330
}
329331

@@ -335,7 +337,7 @@ class TextExplorerUI(
335337
) inner@{
336338
if (totalPages == 0L) {
337339
Text(
338-
"No results were found.",
340+
locale["main.no_results"],
339341
fontSize = 24.sp,
340342
modifier = Modifier.align(Alignment.CenterHorizontally).padding(16.dp)
341343
)
@@ -395,7 +397,7 @@ class TextExplorerUI(
395397
)
396398

397399
if (totalPages < 2) {
398-
Text("Total: $amount", modifier = Modifier.align(Alignment.CenterHorizontally))
400+
Text(locale["main.total", amount], modifier = Modifier.align(Alignment.CenterHorizontally))
399401
return@inner
400402
}
401403

@@ -420,10 +422,10 @@ class TextExplorerUI(
420422
}
421423
}, enabled = currentPage > 0
422424
) {
423-
Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft, contentDescription = "Left")
425+
Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft, contentDescription = locale["main.nav.left"])
424426
}
425427

426-
Text("Page ${currentPage + 1} of $totalPages (Total: $amount)")
428+
Text(locale["main.page_info", currentPage + 1, totalPages, amount])
427429

428430
IconButton(
429431
onClick = {
@@ -439,7 +441,7 @@ class TextExplorerUI(
439441
}
440442
}, enabled = currentPage < totalPages - 1
441443
) {
442-
Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Right")
444+
Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = locale["main.nav.right"])
443445
}
444446
}
445447
}
@@ -480,8 +482,8 @@ class TextExplorerUI(
480482
) {
481483
Icon(
482484
imageVector = Icons.Filled.CheckCircle,
483-
contentDescription = "Success",
484-
tint = Color(0xFF388E3C),
485+
contentDescription =locale["main.status.success"],
486+
tint = App.Colors.GREEN_HIGHLIGHT.toComposeColor(),
485487
modifier = Modifier.size(24.dp)
486488
)
487489

@@ -506,14 +508,14 @@ class TextExplorerUI(
506508
) {
507509
Icon(
508510
imageVector = Icons.Filled.Warning,
509-
contentDescription = "Error",
511+
contentDescription = locale["main.status.error"],
510512
tint = Color(0xFFD32F2F),
511513
modifier = Modifier.size(24.dp)
512514
)
513515

514516
Text(
515517
text = (loadState as LoadState.Error).message,
516-
color = Color(0xFFD32F2F)
518+
color = App.Colors.RED_HIGHLIGHT.toComposeColor()
517519
)
518520
}
519521
LaunchedEffect(loadState) {
@@ -550,12 +552,12 @@ class TextExplorerUI(
550552
if (dataInfo == null) return@DataLoaderWindow
551553

552554
scope.launch {
553-
loadState = LoadState.Loading("Loading data pool")
555+
loadState = LoadState.Loading(locale["main.loading"])
554556

555557
val poolsEmpty = dataService.getAvailablePools().isEmpty()
556558

557559
val success = dataService.createDataPool(dataInfo, dataDir) { progress ->
558-
loadState = LoadState.Loading("Loading data pool ($progress %)")
560+
loadState = LoadState.Loading(locale["main.loading_progress", progress])
559561
}
560562

561563
loadState = if (success) {
@@ -566,8 +568,8 @@ class TextExplorerUI(
566568
poolSelected = !poolSelected
567569
}
568570

569-
LoadState.Success("Successfully loaded data pool '${dataInfo.name}'")
570-
} else LoadState.Error("Failed to create data pool")
571+
LoadState.Success(locale["main.success_load", dataInfo.name])
572+
} else LoadState.Error(locale["main.error_load"])
571573
}
572574
}
573575

0 commit comments

Comments
 (0)