mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 22:02:57 +02:00
Translate the remaining hard-coded interface texts (#129)
* Translate the interface texts that stage 9 left in English The GUI tab checkboxes, the file chooser and dropdown placeholders, the profile dialog's Apply, the settings notice's OK, the chat clear button, the selected-station map button and "all", six column titles, the monitor window's title and heading, and the refusal and hint texts of the profiles, beacon and notification tab states now come from the translation files. Column titles go through CurrentStrings in a lambda like the existing ones, so the id stays untranslated and the stored layout keeps its keys. The tab states read CurrentStrings as StatusIndicatorState already does. The English values are unchanged, so the screen tests still find every control by its old label; a new test switches to German and checks one sample per path: a composed label, a column title and a tab-state refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Translate the settings notices, profile labels and alert button too Wires the settings.spot* and settings.write* keys that existed but were never used, translates the settings window title and its notice titles, the profile tab's "unknown", "Copy of" and database-mode labels, and lets the shared alert use alert.ok instead of a literal. The German for messages.qsoOfTheOther becomes "Fremd-QSOs", the word the menu already uses. "Copy of" is only the suggested display name; the profile id is derived from the entered name once at creation and stored, so a later language switch does not touch it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9d8c013ef
commit
c9b838e946
21 files changed
+278
-82
No files matched your search
@@ -362,7 +362,7 @@ tooltip.unknownGrid=unbekanntes Feld
|
|||||||
tooltip.grid=Feld {0}
|
tooltip.grid=Feld {0}
|
||||||
messages.public=Öffentliche Nachrichten
|
messages.public=Öffentliche Nachrichten
|
||||||
messages.dxCluster=DXCluster-Nachrichten
|
messages.dxCluster=DXCluster-Nachrichten
|
||||||
messages.qsoOfTheOther=QSO der Gegenstation
|
messages.qsoOfTheOther=Fremd-QSOs
|
||||||
chat.ownQrgTooltip=Die eigene Frequenz in der Hauptkategorie
|
chat.ownQrgTooltip=Die eigene Frequenz in der Hauptkategorie
|
||||||
chat.secondQrgTooltip=Die Frequenz für die zweite Chat-Kategorie von Hand eintragen
|
chat.secondQrgTooltip=Die Frequenz für die zweite Chat-Kategorie von Hand eintragen
|
||||||
chat.qtfTooltip=Die eigene Antennenrichtung (QTF); wird vom Rotor übernommen, wenn einer abgeglichen wird
|
chat.qtfTooltip=Die eigene Antennenrichtung (QTF); wird vom Rotor übernommen, wenn einer abgeglichen wird
|
||||||
@@ -379,3 +379,49 @@ contrast.textOnWindowSurface=Text auf der Fensterfläche
|
|||||||
contrast.textInsideField=Text im Feld
|
contrast.textInsideField=Text im Feld
|
||||||
contrast.accentOnWindowSurface=Akzent auf der Fensterfläche
|
contrast.accentOnWindowSurface=Akzent auf der Fensterfläche
|
||||||
contrast.textOnMenuStrip=Text auf der Menüzeile
|
contrast.textOnMenuStrip=Text auf der Menüzeile
|
||||||
|
gui.startInEveningDesign=Im Abenddesign starten
|
||||||
|
gui.filterPublic=Öffentliche Nachrichten
|
||||||
|
gui.filterPmToMe=Privatnachrichten an mich
|
||||||
|
gui.filterPmToOthers=Privatnachrichten an andere
|
||||||
|
gui.filterNothing=Kein Filter
|
||||||
|
gui.hintFreshCall=Ein zum ersten Mal gesehenes Rufzeichen markieren
|
||||||
|
gui.hintLargeGridWorked=Ein gearbeitetes Großfeld markieren
|
||||||
|
form.choose=Wählen...
|
||||||
|
form.notSelected=nicht gewählt
|
||||||
|
dialog.apply=Übernehmen
|
||||||
|
chat.clear=leeren
|
||||||
|
selected.showOnMap=🗺 Auf der Karte zeigen
|
||||||
|
selected.notQrvAll=alle
|
||||||
|
column.category=Kategorie
|
||||||
|
column.sender=Absender
|
||||||
|
column.receiver=Empfänger
|
||||||
|
column.lastQrg=Letzte QRG
|
||||||
|
column.airplane=AP [Minuten / Pot%]
|
||||||
|
column.score=Wertung
|
||||||
|
monitor.title=Cluster & Fremd-QSOs
|
||||||
|
profiles.createFailed=Das Profil konnte nicht angelegt werden. Die Profilliste konnte nicht geschrieben werden.
|
||||||
|
profiles.createdWithoutCredentials=Das Profil „{0}" wurde ohne Rufzeichen und Passwort angelegt. Nach dem Wechsel zu diesem Profil beides im Reiter „Station" eintragen.
|
||||||
|
profiles.duplicateFailed=Das Profil konnte nicht dupliziert werden.
|
||||||
|
profiles.rootNotDeletable=Das Standardprofil verwendet die Dateien der Installation selbst und kann nicht gelöscht werden.
|
||||||
|
profiles.activeNotDeletable=Das gerade benutzte Profil kann nicht gelöscht werden. Zuerst zu einem anderen Profil wechseln.
|
||||||
|
profiles.deleteFailed=Das Profil konnte nicht gelöscht werden.
|
||||||
|
profiles.deletionFolder=Der folgende Ordner wird endgültig entfernt:\n{0}
|
||||||
|
profiles.deletionKeepsShared=Die gemeinsamen gearbeiteten Stationen der Station werden nicht angefasst.
|
||||||
|
profiles.deletionRemovesOwn=Die gearbeiteten Stationen dieses Profils werden ebenfalls gelöscht.
|
||||||
|
profiles.rootAlwaysShared=Das Standardprofil verwendet immer die gemeinsamen gearbeiteten Stationen der Station, weil diese Datenbank die der Installation selbst ist.
|
||||||
|
profiles.changeAfterSwitch=Die Änderung wirkt erst nach einem erneuten Wechsel zu diesem Profil.
|
||||||
|
profiles.alreadyActive=Dieses Profil ist bereits aktiv.
|
||||||
|
profiles.selectFirst=Zuerst ein Profil in der Tabelle wählen.
|
||||||
|
beacon.invalidMessage=Der Bakentext ist ungültig: {0}
|
||||||
|
beacon.intervalTooShort=Einen ganzzahligen Bakenabstand von mindestens {0} Minute eingeben.
|
||||||
|
notification.invalidPort=„{0}" ist kein gültiger TCP-Port. Einen Wert von 1 bis 65535 eingeben.
|
||||||
|
notification.invalidSpotter=„{0}" ist kein gültiges Spotter-Rufzeichen.
|
||||||
|
notification.invalidCallsign=Bitte ein gültiges Rufzeichen eingeben.
|
||||||
|
notification.alreadyMonitored=Dieses Grundrufzeichen steht bereits in der Überwachungsliste.
|
||||||
|
settings.windowTitle=Client-Einstellungen ändern
|
||||||
|
settings.noticeInfo=Info
|
||||||
|
settings.noticeProblem=Einstellungen
|
||||||
|
profiles.unknown=unbekannt
|
||||||
|
profiles.copyOf=Kopie von {0}
|
||||||
|
profiles.sharedDatabase=gemeinsame Stationsdatenbank
|
||||||
|
profiles.ownDatabase=eigene Datenbank
|
||||||
@@ -388,3 +388,49 @@ contrast.textOnWindowSurface=Text on the window surface
|
|||||||
contrast.textInsideField=Text inside a field
|
contrast.textInsideField=Text inside a field
|
||||||
contrast.accentOnWindowSurface=Accent on the window surface
|
contrast.accentOnWindowSurface=Accent on the window surface
|
||||||
contrast.textOnMenuStrip=Text on the menu strip
|
contrast.textOnMenuStrip=Text on the menu strip
|
||||||
|
gui.startInEveningDesign=Start in the evening design
|
||||||
|
gui.filterPublic=Public messages
|
||||||
|
gui.filterPmToMe=Private messages to me
|
||||||
|
gui.filterPmToOthers=Private messages to others
|
||||||
|
gui.filterNothing=No filter
|
||||||
|
gui.hintFreshCall=Mark a callsign seen for the first time
|
||||||
|
gui.hintLargeGridWorked=Mark a worked large grid field
|
||||||
|
form.choose=Choose...
|
||||||
|
form.notSelected=not selected
|
||||||
|
dialog.apply=Apply
|
||||||
|
chat.clear=clear
|
||||||
|
selected.showOnMap=🗺 Show on map
|
||||||
|
selected.notQrvAll=all
|
||||||
|
column.category=Category
|
||||||
|
column.sender=Sender
|
||||||
|
column.receiver=Receiver
|
||||||
|
column.lastQrg=Last QRG
|
||||||
|
column.airplane=AP [minutes / pot%]
|
||||||
|
column.score=Score
|
||||||
|
monitor.title=Cluster & QSO of the other
|
||||||
|
profiles.createFailed=The profile could not be created. The profile registry could not be written.
|
||||||
|
profiles.createdWithoutCredentials=The profile "{0}" was created without callsign and password. Enter them on the Station tab after switching to it.
|
||||||
|
profiles.duplicateFailed=The profile could not be duplicated.
|
||||||
|
profiles.rootNotDeletable=The default profile uses the files of the installation itself and cannot be deleted.
|
||||||
|
profiles.activeNotDeletable=The profile currently in use cannot be deleted. Switch to another profile first.
|
||||||
|
profiles.deleteFailed=The profile could not be deleted.
|
||||||
|
profiles.deletionFolder=The following folder is removed permanently:\n{0}
|
||||||
|
profiles.deletionKeepsShared=The common station worked stations are not touched.
|
||||||
|
profiles.deletionRemovesOwn=The worked stations of this profile are deleted as well.
|
||||||
|
profiles.rootAlwaysShared=The default profile always uses the common station worked stations, because that database is the one of the installation itself.
|
||||||
|
profiles.changeAfterSwitch=The change takes effect after switching to this profile again.
|
||||||
|
profiles.alreadyActive=This profile is already active.
|
||||||
|
profiles.selectFirst=Select a profile in the table first.
|
||||||
|
beacon.invalidMessage=The beacon message is invalid: {0}
|
||||||
|
beacon.intervalTooShort=Enter a whole beacon interval of at least {0} minute.
|
||||||
|
notification.invalidPort="{0}" is not a valid TCP port. Enter a value from 1 to 65535.
|
||||||
|
notification.invalidSpotter="{0}" is not a valid spotter callsign.
|
||||||
|
notification.invalidCallsign=Please enter a valid callsign.
|
||||||
|
notification.alreadyMonitored=This base callsign is already in the monitoring list.
|
||||||
|
settings.windowTitle=Change Client Settings
|
||||||
|
settings.noticeInfo=Info
|
||||||
|
settings.noticeProblem=Settings
|
||||||
|
profiles.unknown=unknown
|
||||||
|
profiles.copyOf=Copy of {0}
|
||||||
|
profiles.sharedDatabase=common station database
|
||||||
|
profiles.ownDatabase=own database
|
||||||
@@ -181,7 +181,7 @@ fun ChatInputRow(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Form.button(text = "TX", onClick = { state.send() })
|
Form.button(text = "TX", onClick = { state.send() })
|
||||||
Form.button(text = "clear", onClick = { state.clear() })
|
Form.button(text = kst4contest.view.i18n.LocalStrings.current.chatClear, onClick = { state.clear() })
|
||||||
|
|
||||||
VerticalRule()
|
VerticalRule()
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ object ComposeAlert {
|
|||||||
AlertContent(
|
AlertContent(
|
||||||
header = header,
|
header = header,
|
||||||
body = body,
|
body = body,
|
||||||
buttons = alertButtons("OK", null, ConfirmKind.OK_DONE, BUTTON_ORDER_LINUX),
|
buttons = alertButtons(CurrentStrings.get().alertOk, null, ConfirmKind.OK_DONE, BUTTON_ORDER_LINUX),
|
||||||
onPressed = { dialog.dispose() },
|
onPressed = { dialog.dispose() },
|
||||||
link = linkText to linkTarget,
|
link = linkText to linkTarget,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ object DirectedMessageColumns {
|
|||||||
),
|
),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "airscout",
|
id = "airscout",
|
||||||
title = "AP [minutes / pot%]",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnAirplane },
|
||||||
value = { AirplaneCell.format(it.sender?.airPlaneReflectInfo) },
|
value = { AirplaneCell.format(it.sender?.airPlaneReflectInfo) },
|
||||||
accent = { _, rendered -> AirplaneCell.accentFor(rendered) },
|
accent = { _, rendered -> AirplaneCell.accentFor(rendered) },
|
||||||
comparator = compareBy(AirplaneCell.order) { it.sender?.airPlaneReflectInfo },
|
comparator = compareBy(AirplaneCell.order) { it.sender?.airPlaneReflectInfo },
|
||||||
@@ -62,7 +62,7 @@ object DirectedMessageColumns {
|
|||||||
),
|
),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "category",
|
id = "category",
|
||||||
title = "Category",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnCategory },
|
||||||
value = { message ->
|
value = { message ->
|
||||||
message.chatCategory
|
message.chatCategory
|
||||||
?.let { it.getChatCategoryName(it.categoryNumber) }
|
?.let { it.getChatCategoryName(it.categoryNumber) }
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ object Form {
|
|||||||
.heightIn(min = Density.FIELD_MIN_HEIGHT),
|
.heightIn(min = Density.FIELD_MIN_HEIGHT),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
draft.value?.let(describe) ?: "not selected",
|
draft.value?.let(describe) ?: kst4contest.view.i18n.LocalStrings.current.formNotSelected,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
@@ -267,7 +267,7 @@ object Form {
|
|||||||
modifier = Modifier.heightIn(min = Density.FIELD_MIN_HEIGHT),
|
modifier = Modifier.heightIn(min = Density.FIELD_MIN_HEIGHT),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
draft.value?.let(describe) ?: "not selected",
|
draft.value?.let(describe) ?: kst4contest.view.i18n.LocalStrings.current.formNotSelected,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ object MonitorColumns {
|
|||||||
*/
|
*/
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "category",
|
id = "category",
|
||||||
title = "Category",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnCategory },
|
||||||
value = { message ->
|
value = { message ->
|
||||||
message.sender?.chatCategory
|
message.sender?.chatCategory
|
||||||
?.let { it.getChatCategoryName(it.categoryNumber) }
|
?.let { it.getChatCategoryName(it.categoryNumber) }
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ object MonitorWindow {
|
|||||||
onResized: (Float, Float) -> Unit,
|
onResized: (Float, Float) -> Unit,
|
||||||
) {
|
) {
|
||||||
host.show(
|
host.show(
|
||||||
title = { "Cluster & QSO of the other" },
|
title = { kst4contest.view.i18n.CurrentStrings.get().monitorTitle },
|
||||||
darkMode = darkMode,
|
darkMode = darkMode,
|
||||||
baseFontSizeSp = baseFontSizeSp,
|
baseFontSizeSp = baseFontSizeSp,
|
||||||
paletteStore = paletteStore,
|
paletteStore = paletteStore,
|
||||||
@@ -97,7 +97,7 @@ internal fun MonitorContent(
|
|||||||
HorizontalDivider(thickness = Density.HAIRLINE)
|
HorizontalDivider(thickness = Density.HAIRLINE)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"QSO of the other",
|
kst4contest.view.i18n.LocalStrings.current.messagesQsoOfTheOther,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
modifier = Modifier.padding(top = 4.dp),
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ object PublicMessageColumns {
|
|||||||
DataColumn(id = "name", title = { kst4contest.view.i18n.CurrentStrings.get().columnName }, value = { it.sender?.name.orEmpty() }),
|
DataColumn(id = "name", title = { kst4contest.view.i18n.CurrentStrings.get().columnName }, value = { it.sender?.name.orEmpty() }),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "last-qrg",
|
id = "last-qrg",
|
||||||
title = "Last QRG",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnLastQrg },
|
||||||
value = { MessageFormats.paddedQrg(MonitorColumns.frequencyOf(it.sender)) },
|
value = { MessageFormats.paddedQrg(MonitorColumns.frequencyOf(it.sender)) },
|
||||||
weight = 0.8f,
|
weight = 0.8f,
|
||||||
),
|
),
|
||||||
@@ -38,7 +38,7 @@ object PublicMessageColumns {
|
|||||||
),
|
),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "category",
|
id = "category",
|
||||||
title = "Category",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnCategory },
|
||||||
value = { message ->
|
value = { message ->
|
||||||
message.sender?.chatCategory
|
message.sender?.chatCategory
|
||||||
?.let { it.getChatCategoryName(it.categoryNumber) }
|
?.let { it.getChatCategoryName(it.categoryNumber) }
|
||||||
|
|||||||
+2
-2
@@ -16,13 +16,13 @@ object SelectedStationMessageColumns {
|
|||||||
),
|
),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "call-tx",
|
id = "call-tx",
|
||||||
title = "Sender",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnSender },
|
||||||
value = { MonitorColumns.callSignOf(it.sender) },
|
value = { MonitorColumns.callSignOf(it.sender) },
|
||||||
weight = 0.8f,
|
weight = 0.8f,
|
||||||
),
|
),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
id = "call-rx",
|
id = "call-rx",
|
||||||
title = "Receiver",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnReceiver },
|
||||||
value = { MonitorColumns.callSignOf(it.receiver) },
|
value = { MonitorColumns.callSignOf(it.receiver) },
|
||||||
weight = 0.8f,
|
weight = 0.8f,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ fun SelectedStationPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Form.check("all", activeNotQrvBands.all { state.isTaggedNotQrv(it) }) {
|
Form.check(strings.selectedNotQrvAll, activeNotQrvBands.all { state.isTaggedNotQrv(it) }) {
|
||||||
state.tagNotQrvAll(it)
|
state.tagNotQrvAll(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ fun SelectedStationPanel(
|
|||||||
Form.section(strings.selectedLookUpAndShow) {
|
Form.section(strings.selectedLookUpAndShow) {
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(Density.BUTTON_GAP)) {
|
FlowRow(horizontalArrangement = Arrangement.spacedBy(Density.BUTTON_GAP)) {
|
||||||
Form.button(strings.selectedShowPathInAs, onClick = onShowPathInAirScout)
|
Form.button(strings.selectedShowPathInAs, onClick = onShowPathInAirScout)
|
||||||
Form.button("🗺 Show on map", onClick = onShowOnMap)
|
Form.button(strings.selectedShowOnMap, onClick = onShowOnMap)
|
||||||
Form.button(
|
Form.button(
|
||||||
strings.selectedTurnAntenna(state.selected?.callSignRaw.orEmpty()),
|
strings.selectedTurnAntenna(state.selected?.callSignRaw.orEmpty()),
|
||||||
enabled = state.canTurnAntenna,
|
enabled = state.canTurnAntenna,
|
||||||
|
|||||||
@@ -264,18 +264,18 @@ fun buildSettingsTabs(
|
|||||||
*/
|
*/
|
||||||
private fun deliverTestSpot(controller: ChatController, spot: ChatMember): String? {
|
private fun deliverTestSpot(controller: ChatController, spot: ChatMember): String? {
|
||||||
if (!controller.isConnectedAndLoggedIn || !controller.chatPreferences.isNotify_dxClusterServerEnabled()) {
|
if (!controller.isConnectedAndLoggedIn || !controller.chatPreferences.isNotify_dxClusterServerEnabled()) {
|
||||||
return "Connect KST4Contest and enable the local DX Cluster server first."
|
return kst4contest.view.i18n.CurrentStrings.get().settingsSpotNeedsConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
val server = controller.dxClusterServer
|
val server = controller.dxClusterServer
|
||||||
?: return "Connect KST4Contest and enable the local DX Cluster server first."
|
?: return kst4contest.view.i18n.CurrentStrings.get().settingsSpotNeedsConnection
|
||||||
|
|
||||||
if (!server.hasConnectedClients()) {
|
if (!server.hasConnectedClients()) {
|
||||||
return "No DX Cluster client is connected to KST4Contest."
|
return kst4contest.view.i18n.CurrentStrings.get().settingsSpotNoClient
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!server.broadcastSingleDXClusterEntryToLoggers(spot)) {
|
if (!server.broadcastSingleDXClusterEntryToLoggers(spot)) {
|
||||||
return "The test spot could not be delivered."
|
return kst4contest.view.i18n.CurrentStrings.get().settingsSpotNotDelivered
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -318,14 +318,9 @@ fun settingsButtons(
|
|||||||
onSave = {
|
onSave = {
|
||||||
val target = host.savePreferencesFromSettings()
|
val target = host.savePreferencesFromSettings()
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
notices.problem(
|
notices.problem(kst4contest.view.i18n.CurrentStrings.get().settingsWriteFailed)
|
||||||
"The configuration file could not be written. The settings stay " +
|
|
||||||
"active for this session."
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
notices.info(
|
notices.info(kst4contest.view.i18n.CurrentStrings.get().settingsWrittenTo(target))
|
||||||
"Settings are stored as default to the xml config file:\n" + target
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onApplyAndClose = close,
|
onApplyAndClose = close,
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ object SettingsWindow {
|
|||||||
buttons: @Composable (close: () -> Unit) -> Unit,
|
buttons: @Composable (close: () -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
host.show(
|
host.show(
|
||||||
title = { "Change Client Settings" },
|
title = { kst4contest.view.i18n.CurrentStrings.get().settingsWindowTitle },
|
||||||
darkMode = darkMode,
|
darkMode = darkMode,
|
||||||
baseFontSizeSp = baseFontSizeSp,
|
baseFontSizeSp = baseFontSizeSp,
|
||||||
paletteStore = paletteStore,
|
paletteStore = paletteStore,
|
||||||
@@ -241,13 +241,13 @@ internal fun SettingsContent(
|
|||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
when (notice) {
|
when (notice) {
|
||||||
is SettingsNotice.Info -> "Info"
|
is SettingsNotice.Info -> kst4contest.view.i18n.LocalStrings.current.settingsNoticeInfo
|
||||||
is SettingsNotice.Problem -> "Settings"
|
is SettingsNotice.Problem -> kst4contest.view.i18n.LocalStrings.current.settingsNoticeProblem
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = { Text(notice.message) },
|
text = { Text(notice.message) },
|
||||||
confirmButton = { TextButton(onClick = notices::dismiss) { Text("OK") } },
|
confirmButton = { TextButton(onClick = notices::dismiss) { Text(kst4contest.view.i18n.LocalStrings.current.dialogOk) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ object StationColumns {
|
|||||||
add(DataColumn(id = "qrg", title = "QRG", value = { it.frequency?.get().orEmpty() }, weight = 0.8f))
|
add(DataColumn(id = "qrg", title = "QRG", value = { it.frequency?.get().orEmpty() }, weight = 0.8f))
|
||||||
add(DataColumn(
|
add(DataColumn(
|
||||||
id = "airscout",
|
id = "airscout",
|
||||||
title = "AP [minutes / pot%]",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnAirplane },
|
||||||
value = { AirplaneCell.format(it.airPlaneReflectInfo) },
|
value = { AirplaneCell.format(it.airPlaneReflectInfo) },
|
||||||
accent = { _, rendered -> AirplaneCell.accentFor(rendered) },
|
accent = { _, rendered -> AirplaneCell.accentFor(rendered) },
|
||||||
comparator = compareBy(AirplaneCell.order) { it.airPlaneReflectInfo },
|
comparator = compareBy(AirplaneCell.order) { it.airPlaneReflectInfo },
|
||||||
@@ -74,7 +74,7 @@ object StationColumns {
|
|||||||
add(DataColumn(id = "tropo", title = "Tropo", value = tropoOf, weight = 0.7f))
|
add(DataColumn(id = "tropo", title = "Tropo", value = tropoOf, weight = 0.7f))
|
||||||
add(DataColumn(
|
add(DataColumn(
|
||||||
id = "score",
|
id = "score",
|
||||||
title = "Score",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnScore },
|
||||||
value = { priorityScoreOf(it).toInt().toString() },
|
value = { priorityScoreOf(it).toInt().toString() },
|
||||||
comparator = compareBy { priorityScoreOf(it) },
|
comparator = compareBy { priorityScoreOf(it) },
|
||||||
weight = 0.6f,
|
weight = 0.6f,
|
||||||
@@ -113,7 +113,7 @@ object StationColumns {
|
|||||||
add(DataColumn(id = "not-qrv", title = "NOT QRV @", value = { qrvBands(it, activeBands) }, weight = 1.6f))
|
add(DataColumn(id = "not-qrv", title = "NOT QRV @", value = { qrvBands(it, activeBands) }, weight = 1.6f))
|
||||||
add(DataColumn(
|
add(DataColumn(
|
||||||
id = "category",
|
id = "category",
|
||||||
title = "Category",
|
title = { kst4contest.view.i18n.CurrentStrings.get().columnCategory },
|
||||||
value = { member ->
|
value = { member ->
|
||||||
member.chatCategory?.let { it.getChatCategoryName(it.categoryNumber) }.orEmpty()
|
member.chatCategory?.let { it.getChatCategoryName(it.categoryNumber) }.orEmpty()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kst4contest.view.compose.tabs
|
|||||||
|
|
||||||
import kst4contest.controller.ChatController
|
import kst4contest.controller.ChatController
|
||||||
import kst4contest.model.ChatPreferences
|
import kst4contest.model.ChatPreferences
|
||||||
|
import kst4contest.view.i18n.CurrentStrings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State of the beacon tab: the two CQ templates and the one interval they share.
|
* State of the beacon tab: the two CQ templates and the one interval they share.
|
||||||
@@ -69,7 +70,7 @@ class BeaconTabState(
|
|||||||
* so a refused template that replaced a working one would silently stop
|
* so a refused template that replaced a working one would silently stop
|
||||||
* the beacon instead of reporting anything.
|
* the beacon instead of reporting anything.
|
||||||
*/
|
*/
|
||||||
return "The beacon message is invalid: " + refusal.message
|
return CurrentStrings.get().beaconInvalidMessage(refusal.message.orEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
store(template)
|
store(template)
|
||||||
@@ -85,8 +86,7 @@ class BeaconTabState(
|
|||||||
val minutes = entered.trim().toIntOrNull()
|
val minutes = entered.trim().toIntOrNull()
|
||||||
|
|
||||||
if (minutes == null || minutes < ChatController.MIN_BEACON_INTERVAL_MINUTES) {
|
if (minutes == null || minutes < ChatController.MIN_BEACON_INTERVAL_MINUTES) {
|
||||||
return "Enter a whole beacon interval of at least " +
|
return CurrentStrings.get().beaconIntervalTooShort(ChatController.MIN_BEACON_INTERVAL_MINUTES.toString())
|
||||||
ChatController.MIN_BEACON_INTERVAL_MINUTES + " minute."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.setBcn_beaconIntervalInMinutesMainCat(minutes)
|
prefs.setBcn_beaconIntervalInMinutesMainCat(minutes)
|
||||||
|
|||||||
+1
-1
@@ -103,7 +103,7 @@ internal fun FilePathRow(
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
Form.button("Choose...") { choosing = true }
|
Form.button(kst4contest.view.i18n.LocalStrings.current.formChoose) { choosing = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,31 +25,31 @@ fun GuiOptionsTab(state: GuiOptionsTabState) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
Form.section(strings.guiStartupDesign) {
|
Form.section(strings.guiStartupDesign) {
|
||||||
Form.check("Start in the evening design", state.gUI_darkModeActiveByDefault) {
|
Form.check(strings.guiStartInEveningDesign, state.gUI_darkModeActiveByDefault) {
|
||||||
state.gUI_darkModeActiveByDefault = it
|
state.gUI_darkModeActiveByDefault = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Form.flowingSection(strings.guiStartupFilters) {
|
Form.flowingSection(strings.guiStartupFilters) {
|
||||||
Form.check("Public messages", state.guiOptions_defaultFilterPublicMsgs) {
|
Form.check(strings.guiFilterPublic, state.guiOptions_defaultFilterPublicMsgs) {
|
||||||
state.guiOptions_defaultFilterPublicMsgs = it
|
state.guiOptions_defaultFilterPublicMsgs = it
|
||||||
}
|
}
|
||||||
Form.check("Private messages to me", state.guiOptions_defaultFilterPmToMe) {
|
Form.check(strings.guiFilterPmToMe, state.guiOptions_defaultFilterPmToMe) {
|
||||||
state.guiOptions_defaultFilterPmToMe = it
|
state.guiOptions_defaultFilterPmToMe = it
|
||||||
}
|
}
|
||||||
Form.check("Private messages to others", state.guiOptions_defaultFilterPmToOther) {
|
Form.check(strings.guiFilterPmToOthers, state.guiOptions_defaultFilterPmToOther) {
|
||||||
state.guiOptions_defaultFilterPmToOther = it
|
state.guiOptions_defaultFilterPmToOther = it
|
||||||
}
|
}
|
||||||
Form.check("No filter", state.guiOptions_defaultFilterNothing) {
|
Form.check(strings.guiFilterNothing, state.guiOptions_defaultFilterNothing) {
|
||||||
state.guiOptions_defaultFilterNothing = it
|
state.guiOptions_defaultFilterNothing = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Form.flowingSection(strings.guiBandColumnHints) {
|
Form.flowingSection(strings.guiBandColumnHints) {
|
||||||
Form.check("Mark a callsign seen for the first time", state.guiOptions_showFreshCallHintInBandColumns) {
|
Form.check(strings.guiHintFreshCall, state.guiOptions_showFreshCallHintInBandColumns) {
|
||||||
state.guiOptions_showFreshCallHintInBandColumns = it
|
state.guiOptions_showFreshCallHintInBandColumns = it
|
||||||
}
|
}
|
||||||
Form.check("Mark a worked large grid field", state.guiOptions_showGrossFieldWorkedHintInBandColumns) {
|
Form.check(strings.guiHintLargeGridWorked, state.guiOptions_showGrossFieldWorkedHintInBandColumns) {
|
||||||
state.guiOptions_showGrossFieldWorkedHintInBandColumns = it
|
state.guiOptions_showGrossFieldWorkedHintInBandColumns = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import kst4contest.view.GuiUtils
|
|||||||
import kst4contest.view.compose.CommitOutcome
|
import kst4contest.view.compose.CommitOutcome
|
||||||
import kst4contest.view.compose.EditableListState
|
import kst4contest.view.compose.EditableListState
|
||||||
import kst4contest.view.compose.EntryRule
|
import kst4contest.view.compose.EntryRule
|
||||||
|
import kst4contest.view.i18n.CurrentStrings
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,8 +107,7 @@ class NotificationTabState(
|
|||||||
val port = entered.trim().toIntOrNull()
|
val port = entered.trim().toIntOrNull()
|
||||||
|
|
||||||
if (port == null || port < 1 || port > 65535) {
|
if (port == null || port < 1 || port > 65535) {
|
||||||
return "\"" + entered.trim() + "\" is not a valid TCP port. " +
|
return CurrentStrings.get().notificationInvalidPort(entered.trim())
|
||||||
"Enter a value from 1 to 65535."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.setNotify_dxclusterServerPort(port)
|
prefs.setNotify_dxclusterServerPort(port)
|
||||||
@@ -130,7 +130,7 @@ class NotificationTabState(
|
|||||||
val spotterCallSign = entered.trim().uppercase(Locale.ROOT)
|
val spotterCallSign = entered.trim().uppercase(Locale.ROOT)
|
||||||
|
|
||||||
if (!GuiUtils.isCallSignSyntax(spotterCallSign)) {
|
if (!GuiUtils.isCallSignSyntax(spotterCallSign)) {
|
||||||
return "\"" + spotterCallSign + "\" is not a valid spotter callsign."
|
return CurrentStrings.get().notificationInvalidSpotter(spotterCallSign)
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.setNotify_DXCSrv_SpottersCallSign(spotterCallSign)
|
prefs.setNotify_DXCSrv_SpottersCallSign(spotterCallSign)
|
||||||
@@ -151,7 +151,7 @@ class NotificationTabState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (verdict !is CommitOutcome.Stored) {
|
if (verdict !is CommitOutcome.Stored) {
|
||||||
return "Please enter a valid callsign."
|
return CurrentStrings.get().notificationInvalidCallsign
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -202,11 +202,11 @@ class NotificationTabState(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (baseCall == null || !GuiUtils.isCallSignSyntax(baseCall)) {
|
if (baseCall == null || !GuiUtils.isCallSignSyntax(baseCall)) {
|
||||||
return CommitOutcome.Refused("Please enter a valid callsign.")
|
return CommitOutcome.Refused(CurrentStrings.get().notificationInvalidCallsign)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (others.any { it != null && it.equals(baseCall, ignoreCase = true) }) {
|
if (others.any { it != null && it.equals(baseCall, ignoreCase = true) }) {
|
||||||
return CommitOutcome.Refused("This base callsign is already in the monitoring list.")
|
return CommitOutcome.Refused(CurrentStrings.get().notificationAlreadyMonitored)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommitOutcome.Stored(baseCall)
|
return CommitOutcome.Stored(baseCall)
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ private fun ProfileDialogs(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = { TextButton(onClick = { onWorkedData(shared) }) { Text("Apply") } },
|
confirmButton = { TextButton(onClick = { onWorkedData(shared) }) { Text(strings.dialogApply) } },
|
||||||
dismissButton = { TextButton(onClick = onDismiss) { Text(strings.dialogCancel) } },
|
dismissButton = { TextButton(onClick = onDismiss) { Text(strings.dialogCancel) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import kst4contest.controller.OperatorProfileManagementService
|
import kst4contest.controller.OperatorProfileManagementService
|
||||||
import kst4contest.model.OperatorProfile
|
import kst4contest.model.OperatorProfile
|
||||||
import kst4contest.model.OperatorProfileSelection
|
import kst4contest.model.OperatorProfileSelection
|
||||||
|
import kst4contest.view.i18n.CurrentStrings
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
@@ -57,7 +58,7 @@ class ProfilesTabState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val activeProfileName: String
|
val activeProfileName: String
|
||||||
get() = activeSelection()?.profile?.displayName ?: "unknown"
|
get() = activeSelection()?.profile?.displayName ?: CurrentStrings.get().profilesUnknown
|
||||||
|
|
||||||
val preferencesPath: String
|
val preferencesPath: String
|
||||||
get() = activeSelection()?.preferencesAbsolutePath ?: ""
|
get() = activeSelection()?.preferencesAbsolutePath ?: ""
|
||||||
@@ -90,9 +91,7 @@ class ProfilesTabState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val created = service.createProfile(name.trim(), sharedWorkedDatabase)
|
val created = service.createProfile(name.trim(), sharedWorkedDatabase)
|
||||||
?: return ProfileNotice.Error(
|
?: return ProfileNotice.Error(CurrentStrings.get().profilesCreateFailed)
|
||||||
"The profile could not be created. The profile registry could not be written."
|
|
||||||
)
|
|
||||||
|
|
||||||
refresh()
|
refresh()
|
||||||
selected = created
|
selected = created
|
||||||
@@ -101,10 +100,7 @@ class ProfilesTabState(
|
|||||||
* Said explicitly because a new profile that cannot log in looks broken
|
* Said explicitly because a new profile that cannot log in looks broken
|
||||||
* otherwise: callsign and password are deliberately not copied or invented.
|
* otherwise: callsign and password are deliberately not copied or invented.
|
||||||
*/
|
*/
|
||||||
return ProfileNotice.Info(
|
return ProfileNotice.Info(CurrentStrings.get().profilesCreatedWithoutCredentials(created.displayName))
|
||||||
"The profile \"" + created.displayName + "\" was created without callsign and " +
|
|
||||||
"password. Enter them on the Station tab after switching to it."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun duplicateSelected(name: String): ProfileNotice? {
|
fun duplicateSelected(name: String): ProfileNotice? {
|
||||||
@@ -115,7 +111,7 @@ class ProfilesTabState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val duplicate = service.duplicateProfile(profile, name)
|
val duplicate = service.duplicateProfile(profile, name)
|
||||||
?: return ProfileNotice.Error("The profile could not be duplicated.")
|
?: return ProfileNotice.Error(CurrentStrings.get().profilesDuplicateFailed)
|
||||||
|
|
||||||
refresh()
|
refresh()
|
||||||
selected = duplicate
|
selected = duplicate
|
||||||
@@ -124,7 +120,7 @@ class ProfilesTabState(
|
|||||||
|
|
||||||
/** The suggested name of a duplicate, the one the JavaFX dialog pre-filled. */
|
/** The suggested name of a duplicate, the one the JavaFX dialog pre-filled. */
|
||||||
fun duplicateNameSuggestion(): String =
|
fun duplicateNameSuggestion(): String =
|
||||||
selected?.let { "Copy of " + it.displayName } ?: ""
|
selected?.let { CurrentStrings.get().profilesCopyOf(it.displayName) } ?: ""
|
||||||
|
|
||||||
fun renameSelected(name: String): ProfileNotice? {
|
fun renameSelected(name: String): ProfileNotice? {
|
||||||
val profile = selected ?: return notASelection()
|
val profile = selected ?: return notASelection()
|
||||||
@@ -142,20 +138,16 @@ class ProfilesTabState(
|
|||||||
val profile = selected ?: return notASelection()
|
val profile = selected ?: return notASelection()
|
||||||
|
|
||||||
if (profile.isRootProfile) {
|
if (profile.isRootProfile) {
|
||||||
return ProfileNotice.Error(
|
return ProfileNotice.Error(CurrentStrings.get().profilesRootNotDeletable)
|
||||||
"The default profile uses the files of the installation itself and cannot be deleted."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive(profile)) {
|
if (isActive(profile)) {
|
||||||
return ProfileNotice.Error(
|
return ProfileNotice.Error(CurrentStrings.get().profilesActiveNotDeletable)
|
||||||
"The profile currently in use cannot be deleted. Switch to another profile first."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!service.deleteProfile(profile)) {
|
if (!service.deleteProfile(profile)) {
|
||||||
refresh()
|
refresh()
|
||||||
return ProfileNotice.Error("The profile could not be deleted.")
|
return ProfileNotice.Error(CurrentStrings.get().profilesDeleteFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh()
|
refresh()
|
||||||
@@ -164,22 +156,18 @@ class ProfilesTabState(
|
|||||||
|
|
||||||
/** The folder the deletion confirmation names, and what happens to worked stations. */
|
/** The folder the deletion confirmation names, and what happens to worked stations. */
|
||||||
fun describeDeletion(profile: OperatorProfile): String =
|
fun describeDeletion(profile: OperatorProfile): String =
|
||||||
"The following folder is removed permanently:\n" +
|
CurrentStrings.get().profilesDeletionFolder(service.getProfileDirectory(profile).toString()) + "\n\n" +
|
||||||
service.getProfileDirectory(profile) + "\n\n" +
|
|
||||||
if (profile.isSharedWorkedDatabase) {
|
if (profile.isSharedWorkedDatabase) {
|
||||||
"The common station worked stations are not touched."
|
CurrentStrings.get().profilesDeletionKeepsShared
|
||||||
} else {
|
} else {
|
||||||
"The worked stations of this profile are deleted as well."
|
CurrentStrings.get().profilesDeletionRemovesOwn
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeWorkedDataModeOfSelected(sharedWorkedDatabase: Boolean): ProfileNotice? {
|
fun changeWorkedDataModeOfSelected(sharedWorkedDatabase: Boolean): ProfileNotice? {
|
||||||
val profile = selected ?: return notASelection()
|
val profile = selected ?: return notASelection()
|
||||||
|
|
||||||
if (profile.isRootProfile) {
|
if (profile.isRootProfile) {
|
||||||
return ProfileNotice.Error(
|
return ProfileNotice.Error(CurrentStrings.get().profilesRootAlwaysShared)
|
||||||
"The default profile always uses the common station worked stations, because " +
|
|
||||||
"that database is the one of the installation itself."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedWorkedDatabase == profile.isSharedWorkedDatabase) {
|
if (sharedWorkedDatabase == profile.isSharedWorkedDatabase) {
|
||||||
@@ -190,7 +178,7 @@ class ProfilesTabState(
|
|||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
if (isActive(profile)) {
|
if (isActive(profile)) {
|
||||||
return ProfileNotice.Info("The change takes effect after switching to this profile again.")
|
return ProfileNotice.Info(CurrentStrings.get().profilesChangeAfterSwitch)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -200,7 +188,7 @@ class ProfilesTabState(
|
|||||||
val profile = selected ?: return notASelection()
|
val profile = selected ?: return notASelection()
|
||||||
|
|
||||||
if (isActive(profile)) {
|
if (isActive(profile)) {
|
||||||
return ProfileNotice.Info("This profile is already active.")
|
return ProfileNotice.Info(CurrentStrings.get().profilesAlreadyActive)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestActivation(profile)
|
requestActivation(profile)
|
||||||
@@ -217,7 +205,7 @@ class ProfilesTabState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notASelection(): ProfileNotice =
|
private fun notASelection(): ProfileNotice =
|
||||||
ProfileNotice.Info("Select a profile in the table first.")
|
ProfileNotice.Info(CurrentStrings.get().profilesSelectFirst)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@@ -230,9 +218,9 @@ class ProfilesTabState(
|
|||||||
*/
|
*/
|
||||||
fun describeWorkedDataMode(profile: OperatorProfile): String =
|
fun describeWorkedDataMode(profile: OperatorProfile): String =
|
||||||
if (profile.isRootProfile || profile.isSharedWorkedDatabase) {
|
if (profile.isRootProfile || profile.isSharedWorkedDatabase) {
|
||||||
"common station database"
|
CurrentStrings.get().profilesSharedDatabase
|
||||||
} else {
|
} else {
|
||||||
"own database"
|
CurrentStrings.get().profilesOwnDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Blank for a profile never used, rather than an epoch date. */
|
/** Blank for a profile never used, rather than an epoch date. */
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
package kst4contest.view.i18n
|
||||||
|
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.runComposeUiTest
|
||||||
|
import kst4contest.model.ChatPreferences
|
||||||
|
import kst4contest.view.compose.SettingsContent
|
||||||
|
import kst4contest.view.compose.SettingsNotices
|
||||||
|
import kst4contest.view.compose.SettingsTab
|
||||||
|
import kst4contest.view.compose.StationColumns
|
||||||
|
import kst4contest.view.compose.tabs.BeaconTabState
|
||||||
|
import kst4contest.view.compose.tabs.GuiOptionsTab
|
||||||
|
import kst4contest.view.compose.tabs.GuiOptionsTabState
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The texts that stayed English after stage 9 now follow the language as well.
|
||||||
|
*
|
||||||
|
* One sample from each of the three ways a text reaches the operator: a label read inside a
|
||||||
|
* composition, a column title built outside one, and a refusal produced by a tab state. All
|
||||||
|
* three have to switch, and each goes through a different door.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalTestApi::class)
|
||||||
|
class RemainingTextsFollowTheLanguageTest {
|
||||||
|
|
||||||
|
/* Never the no-argument ChatPreferences: it would touch the operator's real profile. */
|
||||||
|
@TempDir
|
||||||
|
lateinit var directory: Path
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun theMovedTextsAppearInGermanAfterTheSwitch() = runComposeUiTest {
|
||||||
|
var stored = "en"
|
||||||
|
val store = LanguageStore({ stored }, { stored = it }, Locale.UK)
|
||||||
|
val prefs = ChatPreferences(directory.resolve("preferences.xml"))
|
||||||
|
val state = GuiOptionsTabState(prefs, store)
|
||||||
|
val beacon = BeaconTabState(
|
||||||
|
prefs,
|
||||||
|
validateBeaconTemplate = {},
|
||||||
|
restartBeaconTimer = {},
|
||||||
|
mainCategoryName = "Microwave",
|
||||||
|
secondCategoryName = "144/432 MHz",
|
||||||
|
)
|
||||||
|
val score = StationColumns.all(
|
||||||
|
tropoOf = { "" },
|
||||||
|
priorityScoreOf = { 0.0 },
|
||||||
|
formatBand = { _, _, _ -> "" },
|
||||||
|
).single { it.id == "score" }
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
CompositionLocalProvider(LocalStrings provides store.strings) {
|
||||||
|
GuiOptionsTab(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waitForIdle()
|
||||||
|
|
||||||
|
onNodeWithText("Start in the evening design").assertExists()
|
||||||
|
assertEquals("Score", score.title())
|
||||||
|
|
||||||
|
try {
|
||||||
|
runOnIdle { store.use("de") }
|
||||||
|
waitForIdle()
|
||||||
|
|
||||||
|
onNodeWithText("Im Abenddesign starten").assertExists()
|
||||||
|
onNodeWithText("Kein Filter").assertExists()
|
||||||
|
onNodeWithText("Ein gearbeitetes Großfeld markieren").assertExists()
|
||||||
|
|
||||||
|
assertEquals("Wertung", score.title(), "the column title stayed in the old language")
|
||||||
|
assertEquals(
|
||||||
|
"Einen ganzzahligen Bakenabstand von mindestens 1 Minute eingeben.",
|
||||||
|
beacon.commitBeaconInterval("0"),
|
||||||
|
"the refusal from the tab state stayed in the old language",
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
// use() also sets the JVM-wide CurrentStrings; leave the next test the base language.
|
||||||
|
runOnIdle { store.use("en") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settings window's notice: its title is read in the composition, its message is
|
||||||
|
* produced outside one by the save action -- the same split as above, one window further.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun theSettingsNoticeFollowsTheLanguage() = runComposeUiTest {
|
||||||
|
var stored = "en"
|
||||||
|
val store = LanguageStore({ stored }, { stored = it }, Locale.UK)
|
||||||
|
val notices = SettingsNotices()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
CompositionLocalProvider(LocalStrings provides store.strings) {
|
||||||
|
SettingsContent(
|
||||||
|
tabs = listOf(SettingsTab("Tab") {}),
|
||||||
|
notices = notices,
|
||||||
|
buttons = {},
|
||||||
|
close = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
runOnIdle {
|
||||||
|
store.use("de")
|
||||||
|
notices.problem(CurrentStrings.get().settingsWriteFailed)
|
||||||
|
}
|
||||||
|
waitForIdle()
|
||||||
|
|
||||||
|
onNodeWithText("Einstellungen").assertExists()
|
||||||
|
onNodeWithText(
|
||||||
|
"Die Konfigurationsdatei konnte nicht geschrieben werden. " +
|
||||||
|
"Die Einstellungen bleiben für diese Sitzung aktiv.",
|
||||||
|
).assertExists()
|
||||||
|
} finally {
|
||||||
|
runOnIdle { store.use("en") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user