Generate webHtmlApp's strings, collapse a redundant string key
Strings.kt is now generated at build time (:webHtmlApp:generateStrings, in webHtmlApp/build.gradle.kts) by parsing shared's strings.xml directly, rather than a hand-maintained duplicate that could silently drift from it. Names are mechanical camelCase of the XML key, so a few call sites got more verbose (e.g. moveUp -> moveCurrencyUpContentDescription) in exchange for zero manual sync. Only covers the base English locale. Also collapses currency_option_label_local into currency_option_label: the two had identical English text and existed only as a hook for a future translation to phrase the user's own currency differently, which no locale currently does. Removes the info.code == localeCurrencyCode branching (and the now-pointless localeCurrencyCode parameter/field) from all three UI implementations - PriceScreen.kt, CurrencyPickerSheet.swift, and HtmlCurrencyPicker.kt - plus IosConverterState.localeCurrencyCode, which existed solely to feed that branch on the Swift side. Fixes a real regression surfaced while verifying the above by actually building the Xcode project (not done since currencyFlagEmoji() gained its supportsFlagEmoji parameter): Kotlin/Native doesn't generate default-parameter overloads for Swift/ObjC-exported functions, so both Swift call sites needed the argument passed explicitly, via the correct exported facade name for an expect/actual pair - the actual file's (SystemCurrencies_appleKt), not the common one (SystemCurrenciesKt). Also drops "(DOM)" from webHtmlApp's page title. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,12 +112,18 @@ the pattern to copy: it returns a locale-formatted `String?` (using
|
|||||||
locale-aware formatting via platform APIs, not translated text), and each
|
locale-aware formatting via platform APIs, not translated text), and each
|
||||||
UI layer wraps it with its own localized "Updated %1$s" string.
|
UI layer wraps it with its own localized "Updated %1$s" string.
|
||||||
|
|
||||||
`webHtmlApp` is the one UI layer that deliberately breaks this pattern: its
|
`webHtmlApp` is the one UI layer that deliberately breaks this pattern: it
|
||||||
`Strings.kt` hardcodes English rather than calling `stringResource()`,
|
can't call `stringResource()` because moko-resources' Compose integration
|
||||||
because moko-resources' Compose integration (`moko-resourcesCompose`) pulls
|
(`moko-resourcesCompose`) pulls in `compose.foundation`/`compose.ui`
|
||||||
in `compose.foundation`/`compose.ui` transitively - exactly the dependency
|
transitively - exactly the dependency `webHtmlApp` exists to avoid (see
|
||||||
`webHtmlApp` exists to avoid (see "Module layout" above). Not a template to
|
"Module layout" above). Its `Strings.kt` is instead **generated** at build
|
||||||
follow elsewhere; a one-off tradeoff specific to that module.
|
time (`:webHtmlApp:generateStrings`, in `webHtmlApp/build.gradle.kts`) by
|
||||||
|
parsing `shared/.../moko-resources/base/strings.xml` directly, so that file
|
||||||
|
stays the single source of truth instead of a manually maintained,
|
||||||
|
silently driftable duplicate - edit the XML, not the generated file. Only
|
||||||
|
covers the base (English) locale, since it skips moko's per-locale
|
||||||
|
resolution entirely. Not a template to follow elsewhere; a one-off
|
||||||
|
tradeoff specific to that module.
|
||||||
|
|
||||||
## Locale-aware formatting
|
## Locale-aware formatting
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,6 @@ struct ContentView: View {
|
|||||||
unselectedCurrencies: state.unselectedCurrencies,
|
unselectedCurrencies: state.unselectedCurrencies,
|
||||||
pricedCurrencyCodes: state.pricedCurrencyCodes,
|
pricedCurrencyCodes: state.pricedCurrencyCodes,
|
||||||
sourceName: state.sourceName,
|
sourceName: state.sourceName,
|
||||||
localeCurrencyCode: state.localeCurrencyCode,
|
|
||||||
selectedCount: state.selectedCurrencyCodes.count,
|
selectedCount: state.selectedCurrencyCodes.count,
|
||||||
onToggle: { viewModel.onFiatCurrencyToggled($0) },
|
onToggle: { viewModel.onFiatCurrencyToggled($0) },
|
||||||
onReset: { viewModel.onSelectedCurrenciesReset() }
|
onReset: { viewModel.onSelectedCurrenciesReset() }
|
||||||
@@ -404,7 +403,7 @@ private struct NumericField: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func currencyFieldLabel(for code: String) -> String {
|
private func currencyFieldLabel(for code: String) -> String {
|
||||||
if let flag = CurrencyFlagKt.currencyFlagEmoji(code: code) {
|
if let flag = CurrencyFlagKt.currencyFlagEmoji(code: code, supportsFlagEmoji: SystemCurrencies_appleKt.supportsFlagEmoji()) {
|
||||||
return "\(flag) \(code)"
|
return "\(flag) \(code)"
|
||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ struct CurrencyPickerSheet: View {
|
|||||||
let unselectedCurrencies: [CurrencyInfo]
|
let unselectedCurrencies: [CurrencyInfo]
|
||||||
let pricedCurrencyCodes: [String]
|
let pricedCurrencyCodes: [String]
|
||||||
let sourceName: String
|
let sourceName: String
|
||||||
let localeCurrencyCode: String?
|
|
||||||
let selectedCount: Int
|
let selectedCount: Int
|
||||||
let onToggle: (String) -> Void
|
let onToggle: (String) -> Void
|
||||||
let onReset: () -> Void
|
let onReset: () -> Void
|
||||||
@@ -142,19 +141,11 @@ struct CurrencyPickerSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func currencyLabel(for info: CurrencyInfo) -> String {
|
private func currencyLabel(for info: CurrencyInfo) -> String {
|
||||||
let text: String
|
let text = IosLocalizationKt.localizedFormattedString(
|
||||||
if info.code == localeCurrencyCode {
|
|
||||||
text = IosLocalizationKt.localizedFormattedString(
|
|
||||||
resource: MR.strings.shared.currency_option_label_local,
|
|
||||||
args: [info.code, info.displayName]
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
text = IosLocalizationKt.localizedFormattedString(
|
|
||||||
resource: MR.strings.shared.currency_option_label,
|
resource: MR.strings.shared.currency_option_label,
|
||||||
args: [info.code, info.displayName]
|
args: [info.code, info.displayName]
|
||||||
)
|
)
|
||||||
}
|
if let flag = CurrencyFlagKt.currencyFlagEmoji(code: info.code, supportsFlagEmoji: SystemCurrencies_appleKt.supportsFlagEmoji()) {
|
||||||
if let flag = CurrencyFlagKt.currencyFlagEmoji(code: info.code) {
|
|
||||||
return "\(flag) \(text)"
|
return "\(flag) \(text)"
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ data class IosConverterState(
|
|||||||
val unselectedCurrencies: List<CurrencyInfo>,
|
val unselectedCurrencies: List<CurrencyInfo>,
|
||||||
val selectedCurrencyCodes: List<String>,
|
val selectedCurrencyCodes: List<String>,
|
||||||
val pricedCurrencyCodes: List<String>,
|
val pricedCurrencyCodes: List<String>,
|
||||||
val localeCurrencyCode: String?,
|
|
||||||
val defaultCurrencyCode: String,
|
val defaultCurrencyCode: String,
|
||||||
val sourceName: String,
|
val sourceName: String,
|
||||||
val isManualSource: Boolean,
|
val isManualSource: Boolean,
|
||||||
@@ -81,7 +80,6 @@ private fun ConverterUiState.toIosState(): IosConverterState = IosConverterState
|
|||||||
unselectedCurrencies = unselectedCurrencies(),
|
unselectedCurrencies = unselectedCurrencies(),
|
||||||
selectedCurrencyCodes = selectedFiatCurrencies,
|
selectedCurrencyCodes = selectedFiatCurrencies,
|
||||||
pricedCurrencyCodes = pricedCurrencyCodes.toList(),
|
pricedCurrencyCodes = pricedCurrencyCodes.toList(),
|
||||||
localeCurrencyCode = localeCurrencyCode,
|
|
||||||
defaultCurrencyCode = defaultCurrencyCode,
|
defaultCurrencyCode = defaultCurrencyCode,
|
||||||
sourceName = sourceName,
|
sourceName = sourceName,
|
||||||
isManualSource = isManualSource,
|
isManualSource = isManualSource,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<string name="current_currency_section_title">Current Currency</string>
|
<string name="current_currency_section_title">Current Currency</string>
|
||||||
<string name="currency_not_priced">Not priced by %1$s</string>
|
<string name="currency_not_priced">Not priced by %1$s</string>
|
||||||
<string name="currency_option_label">%1$s - %2$s</string>
|
<string name="currency_option_label">%1$s - %2$s</string>
|
||||||
<string name="currency_option_label_local">%1$s - %2$s</string>
|
|
||||||
<string name="currency_options_content_description">Options for %1$s</string>
|
<string name="currency_options_content_description">Options for %1$s</string>
|
||||||
<string name="currency_to_sats">%1$s to Sats</string>
|
<string name="currency_to_sats">%1$s to Sats</string>
|
||||||
<string name="done">Done</string>
|
<string name="done">Done</string>
|
||||||
|
|||||||
@@ -605,7 +605,6 @@ private fun CurrencyPickerScreen(
|
|||||||
isSelected = true,
|
isSelected = true,
|
||||||
isPriced = state.isPriced(currentCurrency.code),
|
isPriced = state.isPriced(currentCurrency.code),
|
||||||
sourceName = state.sourceName,
|
sourceName = state.sourceName,
|
||||||
localeCurrencyCode = state.localeCurrencyCode,
|
|
||||||
onClick = null,
|
onClick = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -625,7 +624,6 @@ private fun CurrencyPickerScreen(
|
|||||||
isSelected = true,
|
isSelected = true,
|
||||||
isPriced = state.isPriced(info.code),
|
isPriced = state.isPriced(info.code),
|
||||||
sourceName = state.sourceName,
|
sourceName = state.sourceName,
|
||||||
localeCurrencyCode = state.localeCurrencyCode,
|
|
||||||
onClick = { onToggle(info.code) },
|
onClick = { onToggle(info.code) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -649,7 +647,6 @@ private fun CurrencyPickerScreen(
|
|||||||
isSelected = false,
|
isSelected = false,
|
||||||
isPriced = true,
|
isPriced = true,
|
||||||
sourceName = state.sourceName,
|
sourceName = state.sourceName,
|
||||||
localeCurrencyCode = state.localeCurrencyCode,
|
|
||||||
onClick = { onToggle(info.code) },
|
onClick = { onToggle(info.code) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -671,7 +668,6 @@ private fun CurrencyPickerScreen(
|
|||||||
isSelected = false,
|
isSelected = false,
|
||||||
isPriced = false,
|
isPriced = false,
|
||||||
sourceName = state.sourceName,
|
sourceName = state.sourceName,
|
||||||
localeCurrencyCode = state.localeCurrencyCode,
|
|
||||||
onClick = { onToggle(info.code) },
|
onClick = { onToggle(info.code) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -689,14 +685,9 @@ private fun CurrencyRow(
|
|||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
isPriced: Boolean,
|
isPriced: Boolean,
|
||||||
sourceName: String,
|
sourceName: String,
|
||||||
localeCurrencyCode: String?,
|
|
||||||
onClick: (() -> Unit)?,
|
onClick: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
val text = if (info.code == localeCurrencyCode) {
|
val text = stringResource(MR.strings.currency_option_label, info.code, info.displayName)
|
||||||
stringResource(MR.strings.currency_option_label_local, info.code, info.displayName)
|
|
||||||
} else {
|
|
||||||
stringResource(MR.strings.currency_option_label, info.code, info.displayName)
|
|
||||||
}
|
|
||||||
val label = currencyFlagEmoji(info.code)?.let { flag -> "$flag $text" } ?: text
|
val label = currencyFlagEmoji(info.code)?.let { flag -> "$flag $text" } ?: text
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -1,9 +1,92 @@
|
|||||||
|
import org.w3c.dom.Element
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.composeMultiplatform)
|
alias(libs.plugins.composeMultiplatform)
|
||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// webHtmlApp can't use moko-resources' Compose integration (stringResource()) without pulling
|
||||||
|
// in compose.foundation/ui transitively - exactly what this module exists to avoid (see
|
||||||
|
// AGENTS.md's "Module layout"). Generating a plain Strings.kt from the same strings.xml moko
|
||||||
|
// reads keeps that file as the single source of truth without a manually maintained, silently
|
||||||
|
// driftable duplicate. Only covers the base (English) locale - see AGENTS.md.
|
||||||
|
val stringsXmlFile = rootProject.file("shared/src/commonMain/moko-resources/base/strings.xml")
|
||||||
|
|
||||||
|
val generateStrings by tasks.registering {
|
||||||
|
val outputDir = layout.buildDirectory.dir("generated/strings")
|
||||||
|
// Captured as a plain local (rather than read from the doLast closure directly) so the task
|
||||||
|
// action doesn't hold a reference to the build script object itself, which the configuration
|
||||||
|
// cache refuses to serialize.
|
||||||
|
val xmlFile = stringsXmlFile
|
||||||
|
inputs.file(xmlFile)
|
||||||
|
outputs.dir(outputDir)
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile)
|
||||||
|
val stringNodes = document.getElementsByTagName("string")
|
||||||
|
val placeholderPattern = Regex("""%(\d+)\$([sd])""")
|
||||||
|
|
||||||
|
fun String.toCamelCase(): String = split("_").mapIndexed { index, word ->
|
||||||
|
if (index == 0) word else word.replaceFirstChar { it.uppercase() }
|
||||||
|
}.joinToString("")
|
||||||
|
|
||||||
|
val source = buildString {
|
||||||
|
appendLine("// GENERATED FILE - do not edit directly.")
|
||||||
|
appendLine("// Regenerated by :webHtmlApp:generateStrings from")
|
||||||
|
appendLine("// shared/src/commonMain/moko-resources/base/strings.xml - edit that file instead.")
|
||||||
|
appendLine("package xyz.tyiu.satsprice")
|
||||||
|
appendLine()
|
||||||
|
appendLine("object Strings {")
|
||||||
|
for (i in 0 until stringNodes.length) {
|
||||||
|
val element = stringNodes.item(i) as Element
|
||||||
|
val propertyName = element.getAttribute("name").toCamelCase()
|
||||||
|
// moko/Android string-resource XML escapes a literal apostrophe as \' inside the
|
||||||
|
// (unquoted) text content - unescape that before anything else so it doesn't get
|
||||||
|
// mistaken for a Kotlin escape sequence below.
|
||||||
|
val rawValue = element.textContent.replace("\\'", "'")
|
||||||
|
|
||||||
|
val placeholders = placeholderPattern.findAll(rawValue)
|
||||||
|
.map { it.groupValues[1].toInt() to it.groupValues[2] }
|
||||||
|
.distinct()
|
||||||
|
.sortedBy { it.first }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
// Swap each %N$s/%N$d placeholder for a control-character sentinel first, so the
|
||||||
|
// literal '$' in "%1$s" itself doesn't get caught by the blanket '$' -> "\$"
|
||||||
|
// escaping below - then escape, then turn the sentinels into "${argN}".
|
||||||
|
var withSentinels = rawValue
|
||||||
|
for ((index, _) in placeholders) {
|
||||||
|
withSentinels = withSentinels.replace(Regex("""%$index\$[sd]"""), "$index")
|
||||||
|
}
|
||||||
|
val escaped = withSentinels
|
||||||
|
.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("$", "\\$")
|
||||||
|
var templateBody = escaped
|
||||||
|
for ((index, _) in placeholders) {
|
||||||
|
templateBody = templateBody.replace("$index", "\${arg$index}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (placeholders.isEmpty()) {
|
||||||
|
appendLine(" const val $propertyName: String = \"$templateBody\"")
|
||||||
|
} else {
|
||||||
|
val params = placeholders.joinToString(", ") { (index, type) ->
|
||||||
|
"arg$index: ${if (type == "d") "Int" else "String"}"
|
||||||
|
}
|
||||||
|
appendLine(" fun $propertyName($params): String = \"$templateBody\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appendLine("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputFile = outputDir.get().file("xyz/tyiu/satsprice/Strings.kt").asFile
|
||||||
|
outputFile.parentFile.mkdirs()
|
||||||
|
outputFile.writeText(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compose HTML (org.jetbrains.compose.html:html-core) only publishes js and jvm target
|
// Compose HTML (org.jetbrains.compose.html:html-core) only publishes js and jvm target
|
||||||
// variants as of Compose Multiplatform 1.11.1 - no wasmJs artifact exists yet, unlike the
|
// variants as of Compose Multiplatform 1.11.1 - no wasmJs artifact exists yet, unlike the
|
||||||
// Skia-based Compose Multiplatform UI toolkit webApp uses (which targets both). So this
|
// Skia-based Compose Multiplatform UI toolkit webApp uses (which targets both). So this
|
||||||
@@ -15,11 +98,14 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {
|
commonMain {
|
||||||
|
kotlin.srcDir(generateStrings)
|
||||||
|
dependencies {
|
||||||
implementation(project(":shared"))
|
implementation(project(":shared"))
|
||||||
|
|
||||||
implementation(libs.compose.runtime)
|
implementation(libs.compose.runtime)
|
||||||
implementation(libs.compose.html.core)
|
implementation(libs.compose.html.core)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ fun HtmlApp() {
|
|||||||
} else {
|
} else {
|
||||||
Button(attrs = {
|
Button(attrs = {
|
||||||
classes("icon-button")
|
classes("icon-button")
|
||||||
attr("aria-label", Strings.refresh)
|
attr("aria-label", Strings.refreshContentDescription)
|
||||||
attr("title", Strings.refresh)
|
attr("title", Strings.refreshContentDescription)
|
||||||
onClick { viewModel.refresh() }
|
onClick { viewModel.refresh() }
|
||||||
}) { Text("↻") }
|
}) { Text("↻") }
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ private fun CurrencyRowMenu(
|
|||||||
Div({ classes("menu-anchor") }) {
|
Div({ classes("menu-anchor") }) {
|
||||||
Button(attrs = {
|
Button(attrs = {
|
||||||
classes("icon-button")
|
classes("icon-button")
|
||||||
attr("aria-label", Strings.currencyOptions(code))
|
attr("aria-label", Strings.currencyOptionsContentDescription(code))
|
||||||
attr("aria-haspopup", "true")
|
attr("aria-haspopup", "true")
|
||||||
attr("aria-expanded", if (expanded) "true" else "false")
|
attr("aria-expanded", if (expanded) "true" else "false")
|
||||||
onClick { expanded = true }
|
onClick { expanded = true }
|
||||||
@@ -235,12 +235,15 @@ private fun CurrencyRowMenu(
|
|||||||
onClick { expanded = false }
|
onClick { expanded = false }
|
||||||
})
|
})
|
||||||
Div({ classes("menu-popup") }) {
|
Div({ classes("menu-popup") }) {
|
||||||
MenuItem(Strings.moveToTop, enabled = canMoveUp) { expanded = false; onMoveToTop() }
|
MenuItem(Strings.moveCurrencyToTopContentDescription, enabled = canMoveUp) { expanded = false; onMoveToTop() }
|
||||||
MenuItem(Strings.moveUp, enabled = canMoveUp) { expanded = false; onMoveUp() }
|
MenuItem(Strings.moveCurrencyUpContentDescription, enabled = canMoveUp) { expanded = false; onMoveUp() }
|
||||||
MenuItem(Strings.moveDown, enabled = canMoveDown) { expanded = false; onMoveDown() }
|
MenuItem(Strings.moveCurrencyDownContentDescription, enabled = canMoveDown) { expanded = false; onMoveDown() }
|
||||||
MenuItem(Strings.moveToBottom, enabled = canMoveDown) { expanded = false; onMoveToBottom() }
|
MenuItem(Strings.moveCurrencyToBottomContentDescription, enabled = canMoveDown) {
|
||||||
|
expanded = false
|
||||||
|
onMoveToBottom()
|
||||||
|
}
|
||||||
if (canRemove) {
|
if (canRemove) {
|
||||||
MenuItem(Strings.remove, destructive = true) { expanded = false; onRemove() }
|
MenuItem(Strings.removeCurrencyContentDescription, destructive = true) { expanded = false; onRemove() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ internal fun HtmlCurrencyPicker(
|
|||||||
if (showResetConfirmation) {
|
if (showResetConfirmation) {
|
||||||
Div({ classes("modal-overlay") }) {
|
Div({ classes("modal-overlay") }) {
|
||||||
Div({ classes("modal") }) {
|
Div({ classes("modal") }) {
|
||||||
H2 { Text(Strings.resetConfirmationTitle) }
|
H2 { Text(Strings.resetSelectedCurrenciesConfirmationTitle) }
|
||||||
Div { Text(Strings.resetConfirmationMessage(state.defaultCurrencyCode)) }
|
Div { Text(Strings.resetSelectedCurrenciesConfirmationMessage(state.defaultCurrencyCode)) }
|
||||||
Div({ classes("row", "modal-actions") }) {
|
Div({ classes("row", "modal-actions") }) {
|
||||||
Button(attrs = { onClick { showResetConfirmation = false } }) { Text(Strings.cancel) }
|
Button(attrs = { onClick { showResetConfirmation = false } }) { Text(Strings.cancel) }
|
||||||
Button(attrs = {
|
Button(attrs = {
|
||||||
@@ -40,7 +40,7 @@ internal fun HtmlCurrencyPicker(
|
|||||||
showResetConfirmation = false
|
showResetConfirmation = false
|
||||||
onReset()
|
onReset()
|
||||||
}
|
}
|
||||||
}) { Text(Strings.resetButton) }
|
}) { Text(Strings.resetSelectedCurrenciesButton) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,9 @@ internal fun HtmlCurrencyPicker(
|
|||||||
H2 { Text(Strings.currenciesSectionTitle) }
|
H2 { Text(Strings.currenciesSectionTitle) }
|
||||||
Div({ classes("row") }) {
|
Div({ classes("row") }) {
|
||||||
if (state.selectedFiatCurrencies.size > 1) {
|
if (state.selectedFiatCurrencies.size > 1) {
|
||||||
Button(attrs = { onClick { showResetConfirmation = true } }) { Text(Strings.resetButton) }
|
Button(attrs = { onClick { showResetConfirmation = true } }) {
|
||||||
|
Text(Strings.resetSelectedCurrenciesButton)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Button(attrs = { onClick { onDone() } }) { Text(Strings.done) }
|
Button(attrs = { onClick { onDone() } }) { Text(Strings.done) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package xyz.tyiu.satsprice
|
|
||||||
|
|
||||||
/**
|
|
||||||
* English-only string constants mirroring shared/src/commonMain/moko-resources/base/strings.xml.
|
|
||||||
* moko-resources-compose's `stringResource()` pulls in Compose Foundation/UI (for its
|
|
||||||
* `LocalComposition`/`Modifier` plumbing) transitively, which would drag Skia-oriented
|
|
||||||
* dependencies into a module whose whole point is avoiding them, so this DOM build skips
|
|
||||||
* moko-resources rather than fight that dependency and stays English-only as a result.
|
|
||||||
*/
|
|
||||||
object Strings {
|
|
||||||
const val addCurrency = "Add currency"
|
|
||||||
const val bitcoinSectionTitle = "Bitcoin"
|
|
||||||
const val btcLabel = "BTC"
|
|
||||||
fun btcToCurrency(code: String) = "BTC to $code"
|
|
||||||
const val cancel = "Cancel"
|
|
||||||
const val clearSearch = "Clear search"
|
|
||||||
const val currenciesSectionTitle = "Currencies"
|
|
||||||
fun currenciesSelectedCount(count: Int) = "$count selected"
|
|
||||||
const val currentCurrencySectionTitle = "Current Currency"
|
|
||||||
fun currencyNotPriced(sourceName: String) = "Not priced by $sourceName"
|
|
||||||
fun currencyOptions(code: String) = "Options for $code"
|
|
||||||
fun currencyOptionLabel(code: String, displayName: String) = "$code - $displayName"
|
|
||||||
fun currencyToSats(code: String) = "$code to Sats"
|
|
||||||
const val done = "Done"
|
|
||||||
fun exceedsMaxSupply(maxSupplyText: String) = "Exceeds the $maxSupplyText maximum supply"
|
|
||||||
const val loadingRatesStatus = "Loading rates…"
|
|
||||||
const val moveDown = "Move down"
|
|
||||||
const val moveToBottom = "Move to bottom"
|
|
||||||
const val moveToTop = "Move to top"
|
|
||||||
const val moveUp = "Move up"
|
|
||||||
const val priceSource = "Price Source"
|
|
||||||
const val pricedCurrenciesSectionTitle = "Priced Currencies"
|
|
||||||
const val refresh = "Refresh"
|
|
||||||
const val remove = "Remove"
|
|
||||||
const val resetButton = "Reset"
|
|
||||||
fun resetConfirmationMessage(code: String) =
|
|
||||||
"This keeps $code, but removes every other currency you've added."
|
|
||||||
const val resetConfirmationTitle = "Remove all selected currencies?"
|
|
||||||
const val retry = "Retry"
|
|
||||||
const val satsLabel = "Sats"
|
|
||||||
const val searchCurrenciesPlaceholder = "Search currencies"
|
|
||||||
const val selectedCurrenciesSectionTitle = "Selected Currencies"
|
|
||||||
const val unpricedCurrenciesSectionTitle = "Unpriced Currencies"
|
|
||||||
fun updatedStatus(time: String) = "Updated $time"
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>SatsPrice (DOM)</title>
|
<title>SatsPrice</title>
|
||||||
<meta name="description" content="SatsPrice converts Bitcoin, satoshis, and fiat currency in real time — Compose HTML build.">
|
<meta name="description" content="SatsPrice converts Bitcoin, satoshis, and fiat currency in real time — Compose HTML build.">
|
||||||
<link type="text/css" rel="stylesheet" href="styles.css">
|
<link type="text/css" rel="stylesheet" href="styles.css">
|
||||||
<link rel="icon" href="favicon.ico" sizes="any">
|
<link rel="icon" href="favicon.ico" sizes="any">
|
||||||
|
|||||||
Reference in New Issue
Block a user