View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011946 | Taler | wallet (iOS App) | public | 2026-11-05 00:42 | 2026-11-05 00:42 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | tweak | Reproducibility | always |
| Status | new | Resolution | open | ||
| Summary | 0011946: Five buttons are never translated: a literal passed through a String loses its LocalizedStringKey | ||||
| Description | Five buttons show English text in every other language, although Localizable.xcstrings has a translation for all five of them in all five shipped languages. Seen live: running the app with -AppleLanguages (pl), 112 labels on screen rendered in Polish and exactly one did not -- the "Finished" button, which the catalogue translates as "Zakończono". So this is a lookup that does not happen, not a translation that is missing. Cause ----- SwiftUI localises `Button("Finished")` because the literal binds to LocalizedStringKey. Bind the same literal to a variable first and the variable is a String, the LocalizedStringKey overload is no longer selected, and the text is shown verbatim: let title = "Finished" Button(title) { ... } // String overload -- never looked up The five places, all compiled into the shipping GNU_Taler target: Views/Sheets/DevExperimentView.swift:42 "Finished" Views/Settings/DebugSettingsView.swift:145 "Withdraw" Views/Settings/DebugSettingsView.swift:159 "Withdraw" Views/Settings/DebugSettingsView.swift:175 "Withdraw" Views/Settings/DebugSettingsView.swift:191 "Refresh" The catalogue has de/es/fr/pl/uk for "Finished", "Withdraw" and "Refresh", all marked translated, so each of these is a translation that was written, paid for in review time, and then not used. How the list was made --------------------- A scan over every .swift file for a local `let`/`var` assigned a bare string literal that is then passed as the first argument of Text/Button/Label/ Toggle/navigationTitle, keeping only those whose literal appears in Localizable.xcstrings. Five occurrences; these are all of them. Other `let title = "..."` lines in DebugSettingsView ("Demo 1", "Loop", ...) are not affected because those strings are not in the catalogue at all and are developer labels. Suggested fix (patch attached) ------------------------------ Inline the literal so it binds to LocalizedStringKey again. No other change. `Button(LocalizedStringKey(title))` would work equally well where a variable is genuinely wanted. These are developer-facing rows, so the user-visible impact is small. The reason to fix it is that the same mistake is invisible at the call site and silently discards work that was already done. How to reproduce ---------------- 1. Run the app with a non-English language, e.g. xcrun simctl launch --console <device> com.taler-systems.talerwallet-1 \ -AppleLanguages '(pl)' -AppleLocale pl -developerMode YES 2. Settings -> Developer, or open a taler://dev-experiment/ URI. 3. The buttons read "Withdraw", "Refresh" and "Finished" in English while everything around them is translated. Found by the UI fuzzing harness (branch dev/phoenix-bot/fuzzbang), which runs every sixth episode in a shipped translation; its l10n oracle compares what is on screen against Localizable.xcstrings and reported: on 'Finished' the app shows English although the catalogue has a translated pl unit: 'Zakonczono' 112 other labels on the same screen were in Polish, so the language was applied; only these were not looked up. Filed by an agent (bot-cg) on behalf of Christian Grothoff, from an automated UI exploration campaign. Left unassigned deliberately. | ||||
| Tags | No tags attached. | ||||
| Attached Files | l10n-literals.diff (3,216 bytes)
diff --git a/TalerWallet1/Views/Settings/DebugSettingsView.swift b/TalerWallet1/Views/Settings/DebugSettingsView.swift
index f3621f2a..73b1677e 100644
--- a/TalerWallet1/Views/Settings/DebugSettingsView.swift
+++ b/TalerWallet1/Views/Settings/DebugSettingsView.swift
@@ -141,8 +141,7 @@ struct DebugSettingsView: View {
} // showDevelopItems
SettingsItem(name: String("DEMO"), id1: "demo1with",
description: String("Get money for testing")) {
- let title = "Withdraw"
- Button(title) {
+ Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
symLog.log("Withdraw DEMO KUDOS")
@@ -155,8 +154,7 @@ struct DebugSettingsView: View {
}.id("demo1withdraw")
SettingsItem(name: String("TEST"), id1: "test1with",
description: String("Get money for testing")) {
- let title = "Withdraw"
- Button(title) {
+ Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
symLog.log("Withdraw TESTKUDOS")
@@ -171,8 +169,7 @@ struct DebugSettingsView: View {
if showDevelopItems {
SettingsItem(name: String("HEAD"), id1: "head1with",
description: String("Get money for testing")) {
- let title = "Withdraw"
- Button(title) {
+ Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
symLog.log("Withdraw HEAD KUDOS")
@@ -187,8 +184,7 @@ struct DebugSettingsView: View {
SettingsItem(name: String("Run Dev Experiment Refresh"),
id1: "applyDevExperiment",
description: "dev-experiment/insert-pending-refresh") {
- let title = "Refresh"
- Button(title) {
+ Button("Refresh") {
Task { // runs on MainActor
symLog.log("running applyDevExperiment Refresh")
try await model.setConfig(setTesting: true)
diff --git a/TalerWallet1/Views/Sheets/DevExperimentView.swift b/TalerWallet1/Views/Sheets/DevExperimentView.swift
index b57bbd8c..7f629c82 100755
--- a/TalerWallet1/Views/Sheets/DevExperimentView.swift
+++ b/TalerWallet1/Views/Sheets/DevExperimentView.swift
@@ -38,8 +38,7 @@ struct DevExperimentView: View {
.talerFont(.title2)
.padding()
Spacer()
- let title = "Finished"
- Button(title) {
+ Button("Finished") {
dismiss()
}
Spacer()
inline-the-literal.diff (3,216 bytes)
diff --git a/TalerWallet1/Views/Settings/DebugSettingsView.swift b/TalerWallet1/Views/Settings/DebugSettingsView.swift
index f3621f2a..73b1677e 100644
--- a/TalerWallet1/Views/Settings/DebugSettingsView.swift
+++ b/TalerWallet1/Views/Settings/DebugSettingsView.swift
@@ -141,8 +141,7 @@ struct DebugSettingsView: View {
} // showDevelopItems
SettingsItem(name: String("DEMO"), id1: "demo1with",
description: String("Get money for testing")) {
- let title = "Withdraw"
- Button(title) {
+ Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
symLog.log("Withdraw DEMO KUDOS")
@@ -155,8 +154,7 @@ struct DebugSettingsView: View {
}.id("demo1withdraw")
SettingsItem(name: String("TEST"), id1: "test1with",
description: String("Get money for testing")) {
- let title = "Withdraw"
- Button(title) {
+ Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
symLog.log("Withdraw TESTKUDOS")
@@ -171,8 +169,7 @@ struct DebugSettingsView: View {
if showDevelopItems {
SettingsItem(name: String("HEAD"), id1: "head1with",
description: String("Get money for testing")) {
- let title = "Withdraw"
- Button(title) {
+ Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task { // runs on MainActor
symLog.log("Withdraw HEAD KUDOS")
@@ -187,8 +184,7 @@ struct DebugSettingsView: View {
SettingsItem(name: String("Run Dev Experiment Refresh"),
id1: "applyDevExperiment",
description: "dev-experiment/insert-pending-refresh") {
- let title = "Refresh"
- Button(title) {
+ Button("Refresh") {
Task { // runs on MainActor
symLog.log("running applyDevExperiment Refresh")
try await model.setConfig(setTesting: true)
diff --git a/TalerWallet1/Views/Sheets/DevExperimentView.swift b/TalerWallet1/Views/Sheets/DevExperimentView.swift
index b57bbd8c..7f629c82 100755
--- a/TalerWallet1/Views/Sheets/DevExperimentView.swift
+++ b/TalerWallet1/Views/Sheets/DevExperimentView.swift
@@ -38,8 +38,7 @@ struct DevExperimentView: View {
.talerFont(.title2)
.padding()
Spacer()
- let title = "Finished"
- Button(title) {
+ Button("Finished") {
dismiss()
}
Spacer()
| ||||