gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-ios] branch master updated (18d895c -> c4761b4)


From: gnunet
Subject: [taler-taler-ios] branch master updated (18d895c -> c4761b4)
Date: Tue, 28 May 2024 09:05:43 +0200

This is an automated email from the git hooks/post-receive script.

marc-stibane pushed a change to branch master
in repository taler-ios.

    from 18d895c  Bump version to 0.10.2 (0.10.7)
     new 584c084  Bank links
     new 35d07ff  currencyInfo instead of readableDescription
     new 56c0058  checkInternetConnection
     new f03a19f  cleanup
     new a2df784  CurrencyFormatter for manual withdrawals
     new 78157b3  non-breaking space
     new afc1a89  crash fix 8780
     new 5564918  canonical
     new 95ed69a  debugging
     new 1979eb6  prepare pending tx reload on errors
     new abb3cf7  cleanup
     new 256c0e1  Bump version to 0.10.3 (0.10.8)
     new 479eadc  addingPercentEncoding
     new cbcb924  merge-kyc translation
     new 417a07d  Decode payee
     new bcc52d9  Networking: hintNetworkAvailability()
     new e309b6b  make announce() passable in closures
     new 9181daa  don't announce debugView
     new a6dff37  checkPayForTemplate preparation
     new 10f6373  RelativeTime
     new ca158ae  Localization hints
     new 33421ae  localizedState
     new 815c71b  escape "\t"
     new e522d43  Logging
     new 1f0c84a  checkPayForTemplate
     new 7c515cf  German localization
     new 1797026  Spanish localization
     new 292eec0  prepare bankIntegrated without amount
     new c4761b4  Bump version to 0.11.0 (0.11.1)

The 29 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 TalerWallet.xcodeproj/project.pbxproj              |   25 +-
 TalerWallet1/Backend/WalletBackendError.swift      |    2 +-
 TalerWallet1/Backend/WalletBackendRequest.swift    |    1 +
 TalerWallet1/Backend/WalletCore.swift              |   41 +-
 TalerWallet1/Controllers/Controller.swift          |   38 +-
 TalerWallet1/Controllers/DebugViewC.swift          |   16 +-
 TalerWallet1/Controllers/PublicConstants.swift     |    9 +-
 TalerWallet1/GNU_Taler InfoPlist.xcstrings         |   78 +
 TalerWallet1/Helper/CurrencySpecification.swift    |   18 +-
 TalerWallet1/Helper/LocalizedAlertError.swift      |    2 +-
 TalerWallet1/Helper/TalerStrings.swift             |   10 +
 TalerWallet1/Localizable.xcstrings                 | 1991 +++++++++++++++++++-
 TalerWallet1/Model/Model+Exchange.swift            |    4 +-
 TalerWallet1/Model/Model+Payment.swift             |   70 +-
 TalerWallet1/Model/Model+Withdraw.swift            |    5 +-
 TalerWallet1/Model/Transaction.swift               |    7 +-
 TalerWallet1/Model/WalletModel.swift               |   43 +-
 TalerWallet1/Quickjs/QuickDataTask.swift           |   30 +-
 TalerWallet1/Quickjs/quickjs.swift                 |    6 +-
 TalerWallet1/Taler_Wallet InfoPlist.xcstrings      |   78 +
 TalerWallet1/Views/Balances/BalancesListView.swift |    5 +-
 TalerWallet1/Views/Balances/PendingRowView.swift   |    4 +-
 TalerWallet1/Views/Banking/DepositAmountV.swift    |   25 +-
 TalerWallet1/Views/Banking/ExchangeListView.swift  |    6 +-
 TalerWallet1/Views/HelperViews/AmountInputV.swift  |  125 +-
 TalerWallet1/Views/HelperViews/AmountV.swift       |    9 +-
 .../Views/HelperViews/LaunchAnimationView.swift    |    3 +-
 .../Views/HelperViews/QRCodeDetailView.swift       |    8 +-
 TalerWallet1/Views/HelperViews/SubjectInputV.swift |   70 +-
 .../Views/HelperViews/View+fitsSideBySide.swift    |    2 +-
 TalerWallet1/Views/Main/MainView.swift             |    9 +-
 TalerWallet1/Views/Peer2peer/SendAmount.swift      |   39 +-
 TalerWallet1/Views/Settings/AboutView.swift        |    7 +-
 TalerWallet1/Views/Settings/SettingsView.swift     |   12 +-
 TalerWallet1/Views/Sheets/ErrorSheet.swift         |    4 +-
 .../Views/Sheets/P2P_Sheets/P2pPayURIView.swift    |   10 +-
 .../Sheets/P2P_Sheets/P2pReceiveURIView.swift      |    8 +-
 .../Views/Sheets/Payment/PayTemplateV.swift        |  186 +-
 .../Views/Sheets/Payment/PaymentView.swift         |   78 +-
 TalerWallet1/Views/Sheets/QRSheet.swift            |   14 +-
 TalerWallet1/Views/Sheets/Sheet.swift              |    2 +-
 TalerWallet1/Views/Sheets/URLSheet.swift           |    8 +-
 .../WithdrawBankIntegrated/WithdrawURIView.swift   |   27 +-
 .../Views/Transactions/ManualDetailsV.swift        |   43 +-
 .../Views/Transactions/ThreeAmountsV.swift         |   13 +-
 .../Views/Transactions/TransactionRowView.swift    |    2 +-
 .../Views/Transactions/TransactionSummaryV.swift   |   17 +-
 TestFlight/WhatToTest.en-US.txt                    |   12 +
 taler-swift/Sources/taler-swift/Amount.swift       |    5 +-
 taler-swift/Sources/taler-swift/Time.swift         |   45 +
 50 files changed, 2843 insertions(+), 429 deletions(-)
 create mode 100644 TalerWallet1/GNU_Taler InfoPlist.xcstrings
 create mode 100644 TalerWallet1/Taler_Wallet InfoPlist.xcstrings

diff --git a/TalerWallet.xcodeproj/project.pbxproj 
b/TalerWallet.xcodeproj/project.pbxproj
index df17b89..34d5ddb 100644
--- a/TalerWallet.xcodeproj/project.pbxproj
+++ b/TalerWallet.xcodeproj/project.pbxproj
@@ -257,6 +257,8 @@
                4EE171882B49635800BF9FF5 /* MarkdownUI in Frameworks */ = {isa 
= PBXBuildFile; productRef = 4EE171872B49635800BF9FF5 /* MarkdownUI */; };
                4EE171902B49FE2B00BF9FF5 /* OrderedCollections in Frameworks */ 
= {isa = PBXBuildFile; productRef = 4EE1718F2B49FE2B00BF9FF5 /* 
OrderedCollections */; };
                4EE171922B49FE4E00BF9FF5 /* OrderedCollections in Frameworks */ 
= {isa = PBXBuildFile; productRef = 4EE171912B49FE4E00BF9FF5 /* 
OrderedCollections */; };
+               4EE77E7D2C0280E5007C9064 /* GNU_Taler InfoPlist.xcstrings in 
Resources */ = {isa = PBXBuildFile; fileRef = 4EE77E7C2C0280E5007C9064 /* 
GNU_Taler InfoPlist.xcstrings */; };
+               4EE77E7F2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings in 
Resources */ = {isa = PBXBuildFile; fileRef = 4EE77E7E2C0280E5007C9064 /* 
Taler_Wallet InfoPlist.xcstrings */; };
                4EEC118D2B83DE4800146CFF /* AmountInputV.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift 
*/; };
                4EEC118E2B83DE4800146CFF /* AmountInputV.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift 
*/; };
                4EEC11932B83FB7A00146CFF /* SubjectInputV.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift 
*/; };
@@ -448,6 +450,8 @@
                4ED80E8A2B8F60E7008BD576 /* Atomic.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Atomic.swift; sourceTree = "<group>"; };
                4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= QuickDataTask.swift; sourceTree = "<group>"; };
                4EDBDCD82AB787CB00925C02 /* CallStack.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= CallStack.swift; sourceTree = "<group>"; };
+               4EE77E7C2C0280E5007C9064 /* GNU_Taler InfoPlist.xcstrings */ = 
{isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = 
"GNU_Taler InfoPlist.xcstrings"; sourceTree = "<group>"; };
+               4EE77E7E2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings */ 
= {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = 
"Taler_Wallet InfoPlist.xcstrings"; sourceTree = "<group>"; };
                4EEC118C2B83DE4700146CFF /* AmountInputV.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= AmountInputV.swift; sourceTree = "<group>"; };
                4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SubjectInputV.swift; sourceTree = "<group>"; };
                4EEC11952B840F1100146CFF /* PayTemplateV.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= PayTemplateV.swift; sourceTree = "<group>"; };
@@ -589,6 +593,8 @@
                                4E2254942A822B8100E41D29 /* Sounds */,
                                4E3EAEA62AA12750009F1BE8 /* Fonts */,
                                4EFFDD6A2A501121000C1C6A /* 
Localizable.xcstrings */,
+                               4EE77E7C2C0280E5007C9064 /* GNU_Taler 
InfoPlist.xcstrings */,
+                               4EE77E7E2C0280E5007C9064 /* Taler_Wallet 
InfoPlist.xcstrings */,
                                4E363CBF2A24754200D7E98C /* Settings.bundle */,
                                4EB094F529897A9A0043A8A1 /* Preview Content */,
                                4E3208562BB550CA00211E9E /* 
PrivacyInfo.xcprivacy */,
@@ -1023,6 +1029,7 @@
                                en,
                                Base,
                                de,
+                               es,
                        );
                        mainGroup = D14AFD1424D232B300C51073;
                        packageReferences = (
@@ -1059,6 +1066,7 @@
                                4E3EAE9C2AA12467009F1BE8 /* Nunito-Regular.ttf 
in Resources */,
                                4E605D922AA8B407002FB9A7 /* 
Nunito-BlackItalic.ttf in Resources */,
                                4E3EAE9E2AA12467009F1BE8 /* Nunito-Bold.ttf in 
Resources */,
+                               4EE77E7D2C0280E5007C9064 /* GNU_Taler 
InfoPlist.xcstrings in Resources */,
                                4E3EAEA42AA12582009F1BE8 /* 
Nunito-BoldItalic.ttf in Resources */,
                                4E605D902AA8B407002FB9A7 /* Nunito-Black.ttf in 
Resources */,
                                4E3EAEA22AA12582009F1BE8 /* Nunito-Italic.ttf 
in Resources */,
@@ -1077,6 +1085,7 @@
                                4E8C17202A6509BB005B2392 /* 
Atkinson-Hyperlegible-Regular-102.otf in Resources */,
                                4E8C17222A6509BB005B2392 /* 
Atkinson-Hyperlegible-Bold-102.otf in Resources */,
                                4E8C17232A6509BB005B2392 /* 
Atkinson-Hyperlegible-BoldItalic-102.otf in Resources */,
+                               4EE77E7F2C0280E5007C9064 /* Taler_Wallet 
InfoPlist.xcstrings in Resources */,
                                4E8C17212A6509BB005B2392 /* 
Atkinson-Hyperlegible-Italic-102.otf in Resources */,
                                4E3EAE9D2AA12467009F1BE8 /* Nunito-Regular.ttf 
in Resources */,
                                4E605D932AA8B407002FB9A7 /* 
Nunito-BlackItalic.ttf in Resources */,
@@ -1389,7 +1398,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 0.10.7;
+                               CURRENT_PROJECT_VERSION = 0.11.1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1407,7 +1416,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.10.2;
+                               MARKETING_VERSION = 0.11.0;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-1";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1431,7 +1440,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 0.10.7;
+                               CURRENT_PROJECT_VERSION = 0.11.1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1449,7 +1458,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.10.2;
+                               MARKETING_VERSION = 0.11.0;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-1";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1592,7 +1601,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 0.10.7;
+                               CURRENT_PROJECT_VERSION = 0.11.1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1610,7 +1619,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.10.2;
+                               MARKETING_VERSION = 0.11.0;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-2";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1634,7 +1643,7 @@
                                CODE_SIGN_ENTITLEMENTS = 
"$(TARGET_NAME).entitlements";
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 0.10.7;
+                               CURRENT_PROJECT_VERSION = 0.11.1;
                                DEVELOPMENT_TEAM = GUDDQ9428Y;
                                ENABLE_PREVIEWS = YES;
                                GENERATE_INFOPLIST_FILE = YES;
@@ -1652,7 +1661,7 @@
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
-                               MARKETING_VERSION = 0.10.2;
+                               MARKETING_VERSION = 0.11.0;
                                PRODUCT_BUNDLE_IDENTIFIER = 
"com.taler-systems.talerwallet-2";
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/TalerWallet1/Backend/WalletBackendError.swift 
b/TalerWallet1/Backend/WalletBackendError.swift
index 0a2c693..f2cfec9 100644
--- a/TalerWallet1/Backend/WalletBackendError.swift
+++ b/TalerWallet1/Backend/WalletBackendError.swift
@@ -32,7 +32,7 @@ struct WalletBackendResponseError: Codable {
     /// Numeric error code defined defined in the GANA gnu-taler-error-codes 
registry.
     var code: Int
 
-    var when: Timestamp
+    var when: Timestamp?
 
     /// English description of the error code.
     var hint: String
diff --git a/TalerWallet1/Backend/WalletBackendRequest.swift 
b/TalerWallet1/Backend/WalletBackendRequest.swift
index a0d9a75..1319f76 100644
--- a/TalerWallet1/Backend/WalletBackendRequest.swift
+++ b/TalerWallet1/Backend/WalletBackendRequest.swift
@@ -29,6 +29,7 @@ struct ScopeInfo: Codable, Hashable {
         case global
         case exchange
         case auditor
+        case madeUp     // => type unknown, currency name taken from amount
     }
     var type: ScopeInfoType
     var noFees: Bool?           // only for "global". Regional have this field 
per Exchange
diff --git a/TalerWallet1/Backend/WalletCore.swift 
b/TalerWallet1/Backend/WalletCore.swift
index cec27c2..28a09a5 100644
--- a/TalerWallet1/Backend/WalletCore.swift
+++ b/TalerWallet1/Backend/WalletCore.swift
@@ -1,5 +1,5 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
 /**
@@ -168,7 +168,9 @@ extension WalletCore {
     }
 
     private func handlePendingProcessed(_ payload: Payload) throws {
-        guard let id = payload.id else { throw 
WalletBackendError.deserializationError }
+        guard let id = payload.id else {
+            throw WalletBackendError.deserializationError
+        }
         let pendingOp = Notification.Name.PendingOperationProcessed.rawValue
         if id.hasPrefix("exchange-update:") {
             // Bla Bla Bla
@@ -194,16 +196,15 @@ extension WalletCore {
     @MainActor private func handleStateTransition(_ jsonData: Data) throws {
         do {
             let decoded = try JSONDecoder().decode(TransactionTransition.self, 
from: jsonData)
+            if let errorInfo = decoded.errorInfo {
+                // reload pending transaction list to add error badge
+                postNotification(.TransactionError, userInfo: 
[NOTIFICATIONERROR: WalletBackendError.walletCoreError(errorInfo)])
+            }
             guard decoded.newTxState != decoded.oldTxState else {
-                // TODO: Same state usually means that an error is transmitted
                 logger.info("No State change: \(decoded.transactionId, 
privacy: .private(mask: .hash))")
                 return
             }
 
-            if decoded.errorInfo == nil {
-                postNotification(.Error, userInfo: [NOTIFICATIONERROR: 
decoded.errorInfo])
-            }
-
             let components = decoded.transactionId.components(separatedBy: ":")
             if components.count >= 3 {  // txn:$txtype:$uid
                 if let type = TransactionType(rawValue: components[1]) {
@@ -267,14 +268,28 @@ extension WalletCore {
                     } // switch
                 } // type
             } // 3 components
-        } catch {       // rethrows
-            symLog.log(jsonData)       // TODO: .error
-            throw WalletBackendError.deserializationError
+            return
+        } catch DecodingError.dataCorrupted(let context) {
+            print(context)
+        } catch DecodingError.keyNotFound(let key, let context) {
+            print("Key '\(key)' not found:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+        } catch DecodingError.valueNotFound(let value, let context) {
+            print("Value '\(value)' not found:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+        } catch DecodingError.typeMismatch(let type, let context) {
+            print("Type '\(type)' mismatch:", context.debugDescription)
+            print("codingPath:", context.codingPath)
+        } catch let error {       // rethrows
+            symLog.log(error)       // TODO: .error
         }
+        throw WalletBackendError.walletCoreError(nil)       // TODO: error?
     }
 
     @MainActor private func handleNotification(_ anyCodable: AnyCodable?) 
throws {
-        guard let anyPayload = anyCodable else { throw 
WalletBackendError.deserializationError }
+        guard let anyPayload = anyCodable else {
+            throw WalletBackendError.deserializationError
+        }
         do {
             let jsonData = try JSONEncoder().encode(anyPayload)
             let payload = try JSONDecoder().decode(Payload.self, from: 
jsonData)
@@ -305,8 +320,8 @@ extension WalletCore {
                     postNotification(.ProposalDownloaded, userInfo: nil)
                 case Notification.Name.TaskObservabilityEvent.rawValue,
                      Notification.Name.RequestObservabilityEvent.rawValue:
-                    symLog.log(anyPayload)
                     if isObserving != 0 {
+                        symLog.log(anyPayload)
                         let timestamp = TalerDater.dateString()
                         if let event = payload.event, let json = 
event.toJSON() {
                             let type = event["type"]?.value as? String
@@ -322,7 +337,7 @@ extension WalletCore {
 //                     "refresh-revealed", "refresh-unwarranted":
 //                    break
                 default:
-print("\n❗️ WalletCore.swift:251 Notification: ", anyPayload, "\n")        // 
this is a new notification I haven't seen before
+print("\n❗️ WalletCore.swift:340 Notification: ", anyPayload, "\n")        // 
this is a new notification I haven't seen before
                     break
             }
         } catch let error {
diff --git a/TalerWallet1/Controllers/Controller.swift 
b/TalerWallet1/Controllers/Controller.swift
index a3b79ee..c3c551e 100644
--- a/TalerWallet1/Controllers/Controller.swift
+++ b/TalerWallet1/Controllers/Controller.swift
@@ -8,6 +8,7 @@ import SwiftUI
 import SymLog
 import os.log
 import CoreHaptics
+import Network
 
 enum BackendState {
     case none
@@ -36,6 +37,7 @@ class Controller: ObservableObject {
 
     @Published var backendState: BackendState = .none       // only used for 
launch animation
     @Published var currencyTicker: Int = 0                  // updates 
whenever a new currency is added
+    @Published var isConnected: Bool = true
     @AppStorage("useHaptics") var useHaptics: Bool = true   // extension 
mustn't define this, so it must be here
     @AppStorage("playSoundsI") var playSoundsI: Int = 1     // extension 
mustn't define this, so it must be here
     @AppStorage("playSoundsB") var playSoundsB: Bool = false
@@ -45,9 +47,42 @@ class Controller: ObservableObject {
     let player = AVQueuePlayer()
     let semaphore = AsyncSemaphore(value: 1)
     var currencyInfos: [CurrencyInfo]
-
     var messageForSheet: String? = nil
 
+    private let monitor = NWPathMonitor()
+
+    func checkInternetConnection() {
+        monitor.pathUpdateHandler = { path in
+            let status = switch path.status {
+                case .satisfied: "active"
+                case .unsatisfied: "inactive"
+                default: "unknown"
+            }
+            self.logger.log("Internet connection is \(status)")
+            DispatchQueue.main.async {
+                if path.status == .unsatisfied {
+                    self.isConnected = false
+                    Task.detached {
+                        await 
WalletModel.shared.hintNetworkAvailabilityT(false)
+                    }
+                } else {
+                    self.stopCheckingConnection()
+                    self.isConnected = true
+                    Task.detached {
+                        await WalletModel.shared.hintNetworkAvailabilityT(true)
+                    }
+                }
+            }
+        }
+        self.logger.log("Start monitoring internet connection")
+        let queue = DispatchQueue(label: "InternetMonitor")
+        monitor.start(queue: queue)
+    }
+    func stopCheckingConnection() {
+        self.logger.log("Stop monitoring internet connection")
+        monitor.cancel()
+    }
+
     init() {
 //        for family in UIFont.familyNames {
 //            print(family)
@@ -58,6 +93,7 @@ class Controller: ObservableObject {
         backendState = .instantiated
         currencyTicker = 0
         currencyInfos = []
+//        checkInternetConnection()
     }
 // MARK: -
     func hasInfo(for currency: String) -> CurrencyInfo? {
diff --git a/TalerWallet1/Controllers/DebugViewC.swift 
b/TalerWallet1/Controllers/DebugViewC.swift
index bdf8e55..45c884c 100644
--- a/TalerWallet1/Controllers/DebugViewC.swift
+++ b/TalerWallet1/Controllers/DebugViewC.swift
@@ -135,7 +135,7 @@ struct DebugViewV: View {
                 .font(.system(size: 11))        // no talerFont
                 .monospacedDigit()
                 .id("viewID")
-                .accessibilityLabel(Text("View.ID.", comment: 
"AccessibilityLabel"))
+                .accessibilityLabel(Text("View.ID.", comment: "VoiceOver"))
                 .accessibilityValue(viewIDString)
                 .accessibilityHint(String(localized: "Shows which view you 
currently are on."))
             Spacer()
@@ -157,21 +157,21 @@ class DebugViewC: ObservableObject {
     @Published var viewID: Int = 0
     @Published var sheetID: Int = 0
 
-    func announce(this: String) {
-        if UIAccessibility.isVoiceOverRunning {
-            UIAccessibility.post(notification: .announcement, argument: this)
-        }
-    }
+//    func announce(_ this: String) {
+//        if UIAccessibility.isVoiceOverRunning {
+//            UIAccessibility.post(notification: .announcement, argument: this)
+//        }
+//    }
 
     @MainActor func setViewID(_ newID: Int, stack: CallStack) -> Void {
         if developerMode {
             if viewID == 0 {
                 logger.log("switching ON, \(newID, privacy: .public)")
                 viewID = newID                              // publish ON
-//                announce(this: "Current view is: \(newID).")
+//                announce("Current view is: \(newID).")
             } else if viewID != newID {
                 logger.log("switching from \(self.viewID, privacy: .public) to 
\(newID, privacy: .public)")
-//                announce(this: "View switched from \(self.viewID) to 
\(newID).")
+//                announce("View switched from \(self.viewID) to \(newID).")
                 viewID = newID                              // publish new 
viewID
             } else {
                 logger.log("\(newID, privacy: .public) stays")
diff --git a/TalerWallet1/Controllers/PublicConstants.swift 
b/TalerWallet1/Controllers/PublicConstants.swift
index 9515947..fe00bc2 100644
--- a/TalerWallet1/Controllers/PublicConstants.swift
+++ b/TalerWallet1/Controllers/PublicConstants.swift
@@ -16,6 +16,7 @@ public let SEVENDAYS: UInt = 7      // 3..9
 public let THIRTYDAYS: UInt = 30    // 10..30
 
 public let EMPTYSTRING = ""                         // avoid automatic 
translation of empty "" textLiterals in Text()
+public let NONBREAKING = "\u{00A0}"
 public let CONFIRM_BANK = "circle.fill"             // badge in PendingRow, 
TransactionRow and TransactionSummary
 public let NEEDS_KYC = "star.fill"                  // badge in PendingRow, 
TransactionRow and TransactionSummary
 public let PENDING_INCOMING = "plus.diamond"
@@ -26,7 +27,7 @@ public let DONE_OUTGOING = "minus.circle.fill"
 public let HTTPS = "https://";
 //public let DEMOBANK = HTTPS + "bAnK.dEmO.tAlEr.nEt"             // should be 
weird to read, but still work
 //public let DEMOEXCHANGE = HTTPS + "eXcHaNgE.dEmO.tAlEr.nEt"
-public let TALER = "taler.net"
+public let TALER = "taler.net/"
 public let TALER_NET = HTTPS + TALER
 public let DEMO = ".demo." + TALER
 public let TEST = ".test." + TALER
@@ -40,8 +41,8 @@ public let TESTSHOP = HTTPS + "shop" + TEST
 public let TESTBACKEND = HTTPS + "backend" + TEST
 public let TESTEXCHANGE = HTTPS + "exchange" + TEST
 
-public let ARS_AGE_EXCHANGE = HTTPS + "exchange-age.taler.ar"
-public let ARS_EXP_EXCHANGE = HTTPS + "exchange-expensive.taler.ar"
+public let ARS_AGE_EXCHANGE = HTTPS + "exchange-age.taler.ar/"
+public let ARS_EXP_EXCHANGE = HTTPS + "exchange-expensive.taler.ar/"
 public let DEMOCURRENCY = "KUDOS"
 public let TESTCURRENCY = "TESTKUDOS"
 //public let LONGCURRENCY = "gold-pressed Latinum"                // 20 
characters, with dash and space
@@ -83,7 +84,9 @@ extension Notification.Name {
     static let ProposalDownloaded = Notification.Name("proposal-downloaded")
     static let TaskObservabilityEvent = 
Notification.Name("task-observability-event")
     static let RequestObservabilityEvent = 
Notification.Name("request-observability-event")
+
     static let Error = Notification.Name("error")
+    static let TransactionError = Notification.Name("txError")
 }
 
 /// Notifications for internal synchronization
diff --git a/TalerWallet1/GNU_Taler InfoPlist.xcstrings 
b/TalerWallet1/GNU_Taler InfoPlist.xcstrings
new file mode 100644
index 0000000..19764dd
--- /dev/null
+++ b/TalerWallet1/GNU_Taler InfoPlist.xcstrings        
@@ -0,0 +1,78 @@
+{
+  "sourceLanguage" : "en",
+  "strings" : {
+    "CFBundleDisplayName" : {
+      "comment" : "Bundle display name",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "GNU Taler"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "GNU Taler"
+          }
+        }
+      }
+    },
+    "CFBundleName" : {
+      "comment" : "Bundle name",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "GNU_Taler"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "GNU_Taler"
+          }
+        }
+      }
+    },
+    "NSCameraUsageDescription" : {
+      "comment" : "Privacy - Camera Usage Description",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Scan QR Codes"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Escanear códigos QR"
+          }
+        }
+      }
+    },
+    "NSHumanReadableCopyright" : {
+      "comment" : "Copyright (human-readable)",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "© Taler-Systems.com"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "© Taler-Systems.com"
+          }
+        }
+      }
+    }
+  },
+  "version" : "1.0"
+}
\ No newline at end of file
diff --git a/TalerWallet1/Helper/CurrencySpecification.swift 
b/TalerWallet1/Helper/CurrencySpecification.swift
index 96a32ca..81af806 100644
--- a/TalerWallet1/Helper/CurrencySpecification.swift
+++ b/TalerWallet1/Helper/CurrencySpecification.swift
@@ -41,6 +41,14 @@ extension Amount {
         }
     }
 
+    func string(useSymbol: Bool = true) -> String {
+        let controller = Controller.shared
+        if let currencyInfo = controller.info(for: self.currencyStr) {
+            return self.string(currencyInfo, useSymbol: useSymbol)
+        }
+        return self.readableDescription
+    }
+
     func inputDigits(_ currencyInfo: CurrencyInfo) -> UInt {
         let inputDigits = currencyInfo.specs.fractionalInputDigits
         if inputDigits < 0 { return 0 }
@@ -133,7 +141,7 @@ public struct CurrencyInfo {
         formatter.setUseSymbol(useSymbol)
         let (integer, fraction) = valueTuple
         if let integerStr = formatter.string(for: integer) {
-            if fraction == 0 { return integerStr }              // formatter 
already added trailing zeroes
+            if fraction == 0 { return integerStr.nbs() }                       
 // formatter already added trailing zeroes
             if let fractionStr = formatter.string(for: fraction) {
                 if let decimalSeparator = formatter.currencyDecimalSeparator {
                     if let fractionIndex = fractionStr.endIndex(of: 
decimalSeparator) {
@@ -159,7 +167,7 @@ public struct CurrencyInfo {
                             }
                         }
 //            print(resultStr)
-                        return resultStr
+                        return resultStr.nbs()
                     }
                     // if we arrive here then fractionStr doesn't have a 
decimal separator. Yikes!
                 }
@@ -181,14 +189,10 @@ public struct CurrencyInfo {
         madeUpStr += Locale.current.decimalSeparator ?? "."     // 
currencyDecimalSeparator
         madeUpStr += String(String(fraction).dropFirst())       // remove the 
leading 0
         // TODO: fractionalNormalDigits, fractionalTrailingZeroDigits
-        return madeUpStr
+        return madeUpStr.nbs()
     }
 }
 
-public struct CurrencySpecification2: Codable, Sendable {
-    let currencySpecification: CurrencySpecification
-}
-
 public struct CurrencySpecification: Codable, Sendable {
     enum CodingKeys: String, CodingKey {
         case name = "name"
diff --git a/TalerWallet1/Helper/LocalizedAlertError.swift 
b/TalerWallet1/Helper/LocalizedAlertError.swift
index 513f93d..3fdaaf3 100644
--- a/TalerWallet1/Helper/LocalizedAlertError.swift
+++ b/TalerWallet1/Helper/LocalizedAlertError.swift
@@ -38,7 +38,7 @@ extension View {
 
     @ViewBuilder
     func errorAlert(error: Binding<Error?>,
-              buttonTitle: LocalizedStringKey = "xloc.generic.ok",
+              buttonTitle: LocalizedStringKey = "OK",
                    action: (() -> Void)? = nil) -> some View
     {
         let localizedAlertError = LocalizedAlertError(error: 
error.wrappedValue)
diff --git a/TalerWallet1/Helper/TalerStrings.swift 
b/TalerWallet1/Helper/TalerStrings.swift
index 10d4257..bdfa561 100644
--- a/TalerWallet1/Helper/TalerStrings.swift
+++ b/TalerWallet1/Helper/TalerStrings.swift
@@ -40,6 +40,16 @@ extension StringProtocol {
 }
 
 extension String {
+    func replacingOccurrences(of char1: String.Element, with char2: 
String.Element) -> String {
+        String(self.map {
+            $0 == char1 ? char2 : $0
+        })
+    }
+
+    func nbs() -> String {
+        self.replacingOccurrences(of: " ", with: NONBREAKING)
+    }
+
     func tabbed(oneLine: Bool) -> String {
         let fragments = self.components(separatedBy: "\t")
         if fragments.count > 1 {
diff --git a/TalerWallet1/Localizable.xcstrings 
b/TalerWallet1/Localizable.xcstrings
index a4bbaaf..24ab462 100644
--- a/TalerWallet1/Localizable.xcstrings
+++ b/TalerWallet1/Localizable.xcstrings
@@ -8,16 +8,29 @@
             "state" : "translated",
             "value" : "- %@ Gebühr"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "- %@ comisión"
+          }
         }
       }
     },
     ", canceled" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : ", abgebrochen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : ", cancelado"
+          }
         }
       }
     },
@@ -29,6 +42,12 @@
             "state" : "translated",
             "value" : ". Benötigt die Bestätigung der Bank"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : ". Necesita confirmación bancaria"
+          }
         }
       }
     },
@@ -40,6 +59,12 @@
             "state" : "translated",
             "value" : ". Benötigt K Y C"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : ". Necesita K Y C"
+          }
         }
       }
     },
@@ -50,6 +75,12 @@
             "state" : "translated",
             "value" : "**Alternative:** Wenn ihre Bank bereits Payto 
unterstützt, können Sie diesen Paytolink verwenden:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Alternativa:** Si tu banco ya soporta PayTo, puedes 
usar este PayTo-Link:"
+          }
         }
       }
     },
@@ -60,6 +91,12 @@
             "state" : "translated",
             "value" : "**Alternative:** Verwenden SIe diesen PayTo-Link:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Alternativa:** Usar este PayTo-Link:"
+          }
         }
       }
     },
@@ -70,6 +107,12 @@
             "state" : "translated",
             "value" : "**Schritt 1:** Kopieren Sie diesen Code und setzen ihn 
als Verwendungszweck in ihrer Banking-App oder Bank-Webseite ein:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 1:** Copiar este código y pegarlo en el campo 
asunto de tu aplicación bancaria:"
+          }
         }
       }
     },
@@ -80,6 +123,12 @@
             "state" : "translated",
             "value" : "**Schritt 1:** Kopieren Sie diesen Verwendungszweck"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 1:** Copia y pega este asunto:"
+          }
         }
       }
     },
@@ -90,6 +139,12 @@
             "state" : "translated",
             "value" : "**Schritt 2:** Kopieren Sie Empfänger und Konto in Ihre 
Banking-App oder Bank-Webseite:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 2:** Copia y pega el beneficiario y la cuenta en 
sus campos correspondientes en tu aplicación bancaria:"
+          }
         }
       }
     },
@@ -100,6 +155,12 @@
             "state" : "translated",
             "value" : "**Schritt 2:** Kopieren Sie Empfänger und Konto"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 2:** Copia y pega el beneficiario y cuenta:"
+          }
         }
       }
     },
@@ -110,6 +171,12 @@
             "state" : "translated",
             "value" : "**Schritt 2:** Kopieren Sie Empfänger und IBAN"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 2:** Copia y pega el beneficiario y IBAN:"
+          }
         }
       }
     },
@@ -120,6 +187,12 @@
             "state" : "translated",
             "value" : "**Schritt 2:** Wenn Sie es noch nicht in Ihrer 
Favoritenliste haben, kopieren Sie Empfänger und IBAN in Ihre Banking-App oder 
Bank-Webseite (und speichern den Kontakt als Favorit für das nächste Mal):"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 2:** Si todavía no la tienes en tu lista de 
favoritos, entonces copia y pega el beneficiario y IBAN en los campos 
correspondientes en tu aplicación bancaria (y guardalos en favoritos para la 
próxima vez):"
+          }
         }
       }
     },
@@ -130,6 +203,12 @@
             "state" : "translated",
             "value" : "**Schritt 3:** Schließen Sie die Überweisung von %@ in 
Ihrer Banking-App oder Bank-Webseite ab, dann wird diese Abhebung automatisch 
durchgeführt. Die Überweisung kann - je nach Bank - bis zu zwei Tage dauern. 
Bitte haben Sie etwas Geduld."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 3:** Completa la transferencia por %@ en tu 
aplicación bancaria, entonces esta extracción procedera automaticamente. 
Dependiendo de tu bank la transferencia puede tardar desde minutos hasta dos 
días hábiles, por favor se paciente."
+          }
         }
       }
     },
@@ -140,6 +219,12 @@
             "state" : "translated",
             "value" : "**Schritt 3:** Schließen Sie die Überweisung von %@ ab."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "**Paso 3:** Transferir %@."
+          }
         }
       }
     },
@@ -156,6 +241,12 @@
             "state" : "new",
             "value" : "%1$@ %2$@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "%1$@ %2$@"
+          }
         }
       }
     },
@@ -166,6 +257,12 @@
             "state" : "translated",
             "value" : "%lld Zeichen von 100"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "%lld caracteres de 100"
+          }
         }
       }
     },
@@ -176,6 +273,12 @@
             "state" : "translated",
             "value" : "Vor %lld Tagen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "hace %lld días"
+          }
         }
       }
     },
@@ -186,6 +289,12 @@
             "state" : "translated",
             "value" : "Vor %lld Stunden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "hace %lld horas"
+          }
         }
       }
     },
@@ -196,6 +305,12 @@
             "state" : "translated",
             "value" : "Vor %lld Minuten"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "hace %lld minutos"
+          }
         }
       }
     },
@@ -206,6 +321,12 @@
             "state" : "translated",
             "value" : "%lld Jahre"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "%lld años"
+          }
         }
       }
     },
@@ -217,6 +338,12 @@
             "state" : "translated",
             "value" : "%llu Tag"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "%llu Día"
+          }
         }
       }
     },
@@ -228,6 +355,12 @@
             "state" : "translated",
             "value" : "%llu Tage"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "%llu Días"
+          }
         }
       }
     },
@@ -238,6 +371,12 @@
             "state" : "translated",
             "value" : "+ %@ Gebühr"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "+ %@ comisión"
+          }
         }
       }
     },
@@ -248,6 +387,12 @@
             "state" : "translated",
             "value" : "Vor 1 Stunde"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "hace 1 hora"
+          }
         }
       }
     },
@@ -258,6 +403,12 @@
             "state" : "translated",
             "value" : "Vor 1 Minute"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "hace 1 minuto"
+          }
         }
       }
     },
@@ -269,6 +420,12 @@
             "state" : "translated",
             "value" : "Einzahlung abbrechen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Detener deposito"
+          }
         }
       }
     },
@@ -280,6 +437,12 @@
             "state" : "translated",
             "value" : "P2P Zahlung abbrechen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Detener Envío P2P"
+          }
         }
       }
     },
@@ -291,6 +454,12 @@
             "state" : "translated",
             "value" : "Zahlung abbrechen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Detener Pago"
+          }
         }
       }
     },
@@ -301,6 +470,12 @@
             "state" : "translated",
             "value" : "Über %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Hace %@"
+          }
         }
       }
     },
@@ -311,6 +486,12 @@
             "state" : "translated",
             "value" : "Vor ca. 1½ Stunden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Hace 1½ horas"
+          }
         }
       }
     },
@@ -321,6 +502,12 @@
             "state" : "translated",
             "value" : "Vor ca. 2 Stunden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "HAce 2 horas"
+          }
         }
       }
     },
@@ -331,6 +518,12 @@
             "state" : "translated",
             "value" : "Vor ca. 1 Stunde"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Hace una hora"
+          }
         }
       }
     },
@@ -341,6 +534,12 @@
             "state" : "translated",
             "value" : "Akzeptieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Aceptar"
+          }
         }
       }
     },
@@ -351,6 +550,12 @@
             "state" : "translated",
             "value" : "P2P Zahlungsempfang akzeptieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Aceptar Recepción P2P"
+          }
         }
       }
     },
@@ -362,6 +567,12 @@
             "state" : "translated",
             "value" : "Nutzungsbedingungen akzeptieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Aceptar Terminos de Servicio"
+          }
         }
       }
     },
@@ -372,16 +583,29 @@
             "state" : "translated",
             "value" : "Kontoinhaber:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dueño de la cuenta:"
+          }
         }
       }
     },
     "account of the payee" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Konto des Empfängers"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "cuenta del beneficiario"
+          }
         }
       }
     },
@@ -392,6 +616,12 @@
             "state" : "translated",
             "value" : "Konto"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Cuenta:"
+          }
         }
       }
     },
@@ -403,17 +633,29 @@
             "state" : "translated",
             "value" : "Hinzufügen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Agregar"
+          }
         }
       }
     },
     "Add Payment Service" : {
-      "comment" : "accessibilityLabel for the + button",
+      "comment" : "VoiceOver for the + button",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Zahlungsdienst hinzufügen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Agregar Servicio de Pagos"
+          }
         }
       }
     },
@@ -424,6 +666,12 @@
             "state" : "translated",
             "value" : "Adresse des Zahlungsdienstes"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dirección de Servicio de Pagos"
+          }
         }
       }
     },
@@ -434,6 +682,12 @@
             "state" : "translated",
             "value" : "Verfügbarer Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto disponible:"
+          }
         }
       }
     },
@@ -444,6 +698,12 @@
             "state" : "translated",
             "value" : "Auszugebender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a gastar:"
+          }
         }
       }
     },
@@ -454,6 +714,12 @@
             "state" : "translated",
             "value" : "Abzuhebender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a extraer:"
+          }
         }
       }
     },
@@ -464,6 +730,12 @@
             "state" : "translated",
             "value" : "Einzuzahlender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a depositar:"
+          }
         }
       }
     },
@@ -474,6 +746,12 @@
             "state" : "translated",
             "value" : "Zu bezahlender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a pagar:"
+          }
         }
       }
     },
@@ -484,6 +762,12 @@
             "state" : "translated",
             "value" : "Anzufordernder Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a pedir:"
+          }
         }
       }
     },
@@ -494,6 +778,12 @@
             "state" : "translated",
             "value" : "Zu sendender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a envíar:"
+          }
         }
       }
     },
@@ -504,6 +794,12 @@
             "state" : "translated",
             "value" : "Auszugebender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a gastar:"
+          }
         }
       }
     },
@@ -514,6 +810,12 @@
             "state" : "translated",
             "value" : "Abzuhebender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto a extraer:"
+          }
         }
       }
     },
@@ -524,6 +826,12 @@
             "state" : "translated",
             "value" : "Betrag zu groß für eine Abhebung!"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto demasiado grande para una única extracción!"
+          }
         }
       }
     },
@@ -534,6 +842,12 @@
             "state" : "translated",
             "value" : "Betrag zu klein!"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto demasiado pequeño!"
+          }
         }
       }
     },
@@ -544,6 +858,12 @@
             "state" : "translated",
             "value" : "Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto:"
+          }
         }
       }
     },
@@ -554,6 +874,12 @@
             "state" : "translated",
             "value" : "App-Version"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Versión App"
+          }
         }
       }
     },
@@ -564,6 +890,12 @@
             "state" : "translated",
             "value" : "Sind Sie sicher, dass Sie diese Transaktion abbrechen 
wollen?"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Seguro que quiere detener esta transacción?"
+          }
         }
       }
     },
@@ -574,6 +906,12 @@
             "state" : "translated",
             "value" : "Sind Sie sicher, dass Sie diese Transaktion löschen 
wollen?"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Seguro que quiere borrar esta transacción?"
+          }
         }
       }
     },
@@ -584,6 +922,12 @@
             "state" : "translated",
             "value" : "Sind Sie sicher, dass Sie diese Transaktion scheitern 
lassen wollen?"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Seguro que quiere fallar esta transacción?"
+          }
         }
       }
     },
@@ -595,6 +939,12 @@
             "state" : "translated",
             "value" : "Verfügbar:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Disponible:"
+          }
         }
       }
     },
@@ -605,6 +955,12 @@
             "state" : "translated",
             "value" : "Verfügbar: %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Disponible:\t%@"
+          }
         }
       }
     },
@@ -616,6 +972,12 @@
             "state" : "translated",
             "value" : "Backup wiederhergestellt"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Resguardo restaurado"
+          }
         }
       }
     },
@@ -627,6 +989,12 @@
             "state" : "translated",
             "value" : "Saldo:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Balance:"
+          }
         }
       }
     },
@@ -637,6 +1005,12 @@
             "state" : "translated",
             "value" : "Bank"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Banco"
+          }
         }
       }
     },
@@ -647,6 +1021,12 @@
             "state" : "translated",
             "value" : "aber Sie haben nur %@ zum Einzahlen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "pero sólo tiene %@ para depositar."
+          }
         }
       }
     },
@@ -657,6 +1037,12 @@
             "state" : "translated",
             "value" : "aber Sie haben nur %@ zum Versenden."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "pero sólo tiene %@ para enviar."
+          }
         }
       }
     },
@@ -667,6 +1053,12 @@
             "state" : "translated",
             "value" : "Abbrechen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Caneclar"
+          }
         }
       }
     },
@@ -677,6 +1069,12 @@
             "state" : "translated",
             "value" : "Link wird überprüft"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Verificando Link"
+          }
         }
       }
     },
@@ -687,6 +1085,12 @@
             "state" : "translated",
             "value" : "Wählen Sie die Verfallszeit"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Elija un tiempo de expiración"
+          }
         }
       }
     },
@@ -697,6 +1101,12 @@
             "state" : "translated",
             "value" : "Gewählter Betrag zum Abheben:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Elija un monto a extraer:"
+          }
         }
       }
     },
@@ -708,6 +1118,12 @@
             "state" : "translated",
             "value" : "Gewählt:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Elegido:"
+          }
         }
       }
     },
@@ -718,6 +1134,12 @@
             "state" : "translated",
             "value" : "Schließen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Cerrar"
+          }
         }
       }
     },
@@ -728,6 +1150,12 @@
             "state" : "translated",
             "value" : "Kamera beenden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Cerrando cámara"
+          }
         }
       }
     },
@@ -738,6 +1166,12 @@
             "state" : "translated",
             "value" : "Später bestätigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Confirmar después"
+          }
         }
       }
     },
@@ -748,6 +1182,12 @@
             "state" : "translated",
             "value" : "Jetzt bestätigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Confirmar ahora"
+          }
         }
       }
     },
@@ -759,6 +1199,12 @@
             "state" : "translated",
             "value" : "Zahlung bestätigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Confirmar Pago"
+          }
         }
       }
     },
@@ -769,6 +1215,12 @@
             "state" : "translated",
             "value" : "Mit der Bank bestätigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Confirmar con Banco"
+          }
         }
       }
     },
@@ -779,6 +1231,12 @@
             "state" : "translated",
             "value" : "Abhebung bestätigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Confirmar Extracción"
+          }
         }
       }
     },
@@ -789,36 +1247,63 @@
             "state" : "translated",
             "value" : "Kontaktiere..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Contactando..."
+          }
         }
       }
     },
     "Copy the account" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Konto kopieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar la cuenta"
+          }
         }
       }
     },
     "Copy the cryptocode" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Cryptocode kopieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar el cryptocode"
+          }
         }
       }
     },
     "Copy the error" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Fehler kopieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar el error"
+          }
         }
       }
     },
@@ -829,26 +1314,46 @@
             "state" : "translated",
             "value" : "Fehler-JSON kopieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar el error JSON"
+          }
         }
       }
     },
     "Copy the IBAN" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "IBAN kopieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar el IBAN"
+          }
         }
       }
     },
     "Copy the payee" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Empfänger kopieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar el beneficiario"
+          }
         }
       }
     },
@@ -867,6 +1372,12 @@
             "state" : "new",
             "value" : "Copy"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar"
+          }
         }
       }
     },
@@ -885,26 +1396,62 @@
             "state" : "new",
             "value" : "Copy"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Copiar"
+          }
         }
       }
     },
     "Cryptocode" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Cryptocode"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Cryptocode"
+          }
+        }
+      }
+    },
+    "Custom Amount" : {
+      "comment" : "pay merchant",
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Betrag ändern"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto personalizado"
+          }
         }
       }
     },
-    "Customize Order" : {
+    "Custom Summary" : {
       "comment" : "pay merchant",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
-            "value" : "Auftrag bearbeiten"
+            "value" : "Zweck ändern"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Descripción personalizado"
           }
         }
       }
@@ -916,6 +1463,12 @@
             "state" : "translated",
             "value" : "Demo: Mit Digitalem Bargeld ausprobieren wie Sie mit 
dem Geld der Zukunft bezahlen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Demo: recibir billetes digitales para experimentar como 
pagar con el dinero del futuro."
+          }
         }
       }
     },
@@ -927,6 +1480,12 @@
             "state" : "translated",
             "value" : "Einzahlen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar"
+          }
         }
       }
     },
@@ -938,6 +1497,12 @@
             "state" : "translated",
             "value" : "%@\teinzahlen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar\t%@"
+          }
         }
       }
     },
@@ -949,6 +1514,12 @@
             "state" : "translated",
             "value" : "%@ einzahlen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar %@"
+          }
         }
       }
     },
@@ -960,6 +1531,12 @@
             "state" : "translated",
             "value" : "Einzahlen:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar:"
+          }
         }
       }
     },
@@ -978,6 +1555,12 @@
             "state" : "new",
             "value" : "Deposit"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar"
+          }
         }
       }
     },
@@ -988,6 +1571,12 @@
             "state" : "translated",
             "value" : "Interner Deserialisierungsfehler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Error de deserialización"
+          }
         }
       }
     },
@@ -998,6 +1587,12 @@
             "state" : "translated",
             "value" : "Fertig"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Terminado"
+          }
         }
       }
     },
@@ -1009,6 +1604,12 @@
             "state" : "translated",
             "value" : "Netto:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Efectivo:"
+          }
         }
       }
     },
@@ -1026,6 +1627,12 @@
             "state" : "new",
             "value" : "Either provide this payment link"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Proporcione este link de pago"
+          }
         }
       }
     },
@@ -1043,6 +1650,12 @@
             "state" : "new",
             "value" : "to the payee, or"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "al beneficiario, o"
+          }
         }
       }
     },
@@ -1061,6 +1674,12 @@
             "state" : "new",
             "value" : "let the payee scan this QR code to receive %@."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "deje que el beneficiario escanee este código QR para 
recibir %@."
+          }
         }
       }
     },
@@ -1079,6 +1698,12 @@
             "state" : "new",
             "value" : "Either"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "También"
+          }
         }
       }
     },
@@ -1096,6 +1721,12 @@
             "state" : "new",
             "value" : "or scan this"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "o escanee este"
+          }
         }
       }
     },
@@ -1114,6 +1745,12 @@
             "state" : "new",
             "value" : "to receive %@."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "para reciribr %@."
+          }
         }
       }
     },
@@ -1131,6 +1768,12 @@
             "state" : "new",
             "value" : "Either provide this payment link"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Proporcione este link the pago"
+          }
         }
       }
     },
@@ -1148,6 +1791,12 @@
             "state" : "new",
             "value" : "to the payer, or"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "al pagador, o"
+          }
         }
       }
     },
@@ -1166,6 +1815,12 @@
             "state" : "new",
             "value" : "let the payer scan this QR code to pay %@."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "deje que el pagador escanee este código QR para pagar 
%@."
+          }
         }
       }
     },
@@ -1184,6 +1839,12 @@
             "state" : "new",
             "value" : "Either"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "También"
+          }
         }
       }
     },
@@ -1201,6 +1862,12 @@
             "state" : "new",
             "value" : "or scan this"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "o escanee este"
+          }
         }
       }
     },
@@ -1219,6 +1886,12 @@
             "state" : "new",
             "value" : "to pay %@."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "para pagar %@."
+          }
         }
       }
     },
@@ -1229,6 +1902,12 @@
             "state" : "translated",
             "value" : "aktiviert, wenn Kontoinhaber und IBAN eingegeben sind"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "habilitado cuando el dueño de la cuenta y el IBAN estan 
completos"
+          }
         }
       }
     },
@@ -1239,6 +1918,12 @@
             "state" : "translated",
             "value" : "aktiviert, wenn der Betrag ungleich Null ist, aber 
nicht höher als Ihr verfügbarer Betrag"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "habilitado cuando el monto no es cero, pero no mas alto 
del monto disponible"
+          }
         }
       }
     },
@@ -1249,6 +1934,12 @@
             "state" : "translated",
             "value" : "aktiviert, wenn Betreff und Ablaufdatum eingegeben sind"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "habilitado cuando el asunto y la expiración esta 
completos"
+          }
         }
       }
     },
@@ -1259,6 +1950,12 @@
             "state" : "translated",
             "value" : "Verwendungszweck eingeben:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ingrese asunto:"
+          }
         }
       }
     },
@@ -1269,6 +1966,12 @@
             "state" : "translated",
             "value" : "Fehler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Error"
+          }
         }
       }
     },
@@ -1279,16 +1982,29 @@
             "state" : "translated",
             "value" : "Läuft ab in:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Expira en:"
+          }
         }
       }
     },
     "Expires: %@" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Läuft ab: %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Expira: %@"
+          }
         }
       }
     },
@@ -1307,6 +2023,12 @@
             "state" : "new",
             "value" : "Fee:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Comisión:"
+          }
         }
       }
     },
@@ -1325,6 +2047,12 @@
             "state" : "new",
             "value" : "Fee:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Comisión:"
+          }
         }
       }
     },
@@ -1335,6 +2063,12 @@
             "state" : "translated",
             "value" : "Für Löschen, Abbrechen und Scheitern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Para botones Borrar, Fallar & Detener"
+          }
         }
       }
     },
@@ -1345,6 +2079,12 @@
             "state" : "translated",
             "value" : "von %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "desde %@"
+          }
         }
       }
     },
@@ -1355,6 +2095,12 @@
             "state" : "translated",
             "value" : "Bruttobetrag zu erhalten:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto bruto a recibir:"
+          }
         }
       }
     },
@@ -1365,16 +2111,29 @@
             "state" : "translated",
             "value" : "Haptik"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Hápticos"
+          }
         }
       }
     },
     "IBAN of the payee" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "IBAN des Empfängers"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "IBAN del beneficiario"
+          }
         }
       }
     },
@@ -1385,6 +2144,12 @@
             "state" : "translated",
             "value" : "IBAN:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "IBAN:"
+          }
         }
       }
     },
@@ -1395,16 +2160,29 @@
             "state" : "translated",
             "value" : "Wenn diese Wallet einem Kind oder Minderjährigen 
gehört, sollte das generierte Elektronic-Cash altersbeschränkt sein."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Si esta billetera pertenece a un niño/a o adolecente, 
el dinero electrónico generado deberá ser restringido por edad:"
+          }
         }
       }
     },
     "In progress" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "In Verarbeitung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "En progreso"
+          }
         }
       }
     },
@@ -1415,6 +2193,12 @@
             "state" : "translated",
             "value" : "Initialisierungsfehler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Error de inicialización"
+          }
         }
       }
     },
@@ -1425,6 +2209,12 @@
             "state" : "translated",
             "value" : "Interner Systemfehler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Código de error interno"
+          }
         }
       }
     },
@@ -1435,6 +2225,12 @@
             "state" : "translated",
             "value" : "KYC"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "KYC"
+          }
         }
       }
     },
@@ -1446,6 +2242,12 @@
             "state" : "translated",
             "value" : "Sprache:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Lenguaje:"
+          }
         }
       }
     },
@@ -1463,6 +2265,12 @@
             "state" : "new",
             "value" : "Demo Bank Website"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Sitio web Banco Demo"
+          }
         }
       }
     },
@@ -1480,6 +2288,12 @@
             "state" : "new",
             "value" : "Spend test money"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Gastar dinero de prueba"
+          }
         }
       }
     },
@@ -1497,6 +2311,12 @@
             "state" : "new",
             "value" : "Get demo money"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Obtener dinero de prueba"
+          }
         }
       }
     },
@@ -1507,6 +2327,12 @@
             "state" : "translated",
             "value" : "Listenstil:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Estilo de Lista:"
+          }
         }
       }
     },
@@ -1517,6 +2343,12 @@
             "state" : "translated",
             "value" : "Laden..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Cargando…"
+          }
         }
       }
     },
@@ -1526,7 +2358,13 @@
         "de" : {
           "stringUnit" : {
             "state" : "translated",
-            "value" : "Verlust:"
+            "value" : "Verlust:"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Perdido:"
           }
         }
       }
@@ -1538,6 +2376,12 @@
             "state" : "translated",
             "value" : "Hauptmenü"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Menu principal"
+          }
         }
       }
     },
@@ -1556,6 +2400,12 @@
             "state" : "new",
             "value" : "Aborted"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Detenido"
+          }
         }
       }
     },
@@ -1574,6 +2424,12 @@
             "state" : "new",
             "value" : "Aborting"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Deteniendo"
+          }
         }
       }
     },
@@ -1592,6 +2448,12 @@
             "state" : "new",
             "value" : "Deleted"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Borrado"
+          }
         }
       }
     },
@@ -1610,6 +2472,12 @@
             "state" : "new",
             "value" : "Dialog"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Diálogo"
+          }
         }
       }
     },
@@ -1628,6 +2496,12 @@
             "state" : "new",
             "value" : "Done"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Terminado"
+          }
         }
       }
     },
@@ -1646,6 +2520,12 @@
             "state" : "new",
             "value" : "Expired"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Expirado"
+          }
         }
       }
     },
@@ -1664,6 +2544,12 @@
             "state" : "new",
             "value" : "Failed"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Falló"
+          }
         }
       }
     },
@@ -1682,6 +2568,12 @@
             "state" : "new",
             "value" : "Pending"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pendiente"
+          }
         }
       }
     },
@@ -1692,6 +2584,12 @@
             "state" : "translated",
             "value" : "Zahlungsdienste verwalten"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Administrar proveedores de servicio de pago..."
+          }
         }
       }
     },
@@ -1703,6 +2601,12 @@
             "state" : "translated",
             "value" : "Händler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Comerciante"
+          }
         }
       }
     },
@@ -1713,6 +2617,12 @@
             "state" : "translated",
             "value" : "Minimalistisch"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Minimalistico"
+          }
         }
       }
     },
@@ -1731,6 +2641,12 @@
             "state" : "new",
             "value" : "AML required"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Requiere AML"
+          }
         }
       }
     },
@@ -1749,6 +2665,12 @@
             "state" : "new",
             "value" : "Waiting for bank transfer"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Esperando la transferencia bancaria"
+          }
         }
       }
     },
@@ -1767,23 +2689,11 @@
             "state" : "new",
             "value" : "KYC required"
           }
-        }
-      }
-    },
-    "MinorState.mergekyc" : {
-      "comment" : "TxMinorState heading",
-      "extractionState" : "extracted_with_value",
-      "localizations" : {
-        "de" : {
-          "stringUnit" : {
-            "state" : "translated",
-            "value" : "KYC benötigt"
-          }
         },
-        "en" : {
+        "es" : {
           "stringUnit" : {
-            "state" : "new",
-            "value" : "KYC required"
+            "state" : "translated",
+            "value" : "Requiere KYC"
           }
         }
       }
@@ -1796,6 +2706,12 @@
             "state" : "translated",
             "value" : "Geld verloren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dinero perdido"
+          }
         }
       }
     },
@@ -1806,6 +2722,12 @@
             "state" : "translated",
             "value" : "Geld verloren:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dinero perdido:"
+          }
         }
       }
     },
@@ -1816,6 +2738,12 @@
             "state" : "translated",
             "value" : "Mehr Info über GNU Taler im Allgemeinen..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Mas información acerca de GNU Taler en general..."
+          }
         }
       }
     },
@@ -1826,6 +2754,12 @@
             "state" : "translated",
             "value" : "Mehr Info über diese App..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Mas información acerca de esta aplicación..."
+          }
         }
       }
     },
@@ -1836,6 +2770,12 @@
             "state" : "translated",
             "value" : "Vor mehr als einer Woche"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Hace mas de una semana"
+          }
         }
       }
     },
@@ -1854,6 +2794,12 @@
             "state" : "new",
             "value" : "Deposit"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar"
+          }
         }
       }
     },
@@ -1872,6 +2818,12 @@
             "state" : "new",
             "value" : "Deposit %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Depositar %@"
+          }
         }
       }
     },
@@ -1890,6 +2842,12 @@
             "state" : "new",
             "value" : "Request %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedir %@"
+          }
         }
       }
     },
@@ -1908,6 +2866,12 @@
             "state" : "new",
             "value" : "Request %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedir %@"
+          }
         }
       }
     },
@@ -1926,6 +2890,12 @@
             "state" : "new",
             "value" : "Send %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Senviar %@"
+          }
         }
       }
     },
@@ -1944,6 +2914,12 @@
             "state" : "new",
             "value" : "Send %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Enviar %@"
+          }
         }
       }
     },
@@ -1962,6 +2938,12 @@
             "state" : "new",
             "value" : "Withdraw %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Extraer %@"
+          }
         }
       }
     },
@@ -1980,6 +2962,12 @@
             "state" : "new",
             "value" : "Withdraw %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Extraer %@"
+          }
         }
       }
     },
@@ -1990,6 +2978,12 @@
             "state" : "translated",
             "value" : "Nettobetrag erhalten:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto Neto a recirbir:"
+          }
         }
       }
     },
@@ -2001,6 +2995,12 @@
             "state" : "translated",
             "value" : "Weiter"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Siguiente"
+          }
         }
       }
     },
@@ -2012,6 +3012,12 @@
             "state" : "translated",
             "value" : "Kein Datum"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Sin fecha"
+          }
         }
       }
     },
@@ -2022,6 +3028,12 @@
             "state" : "translated",
             "value" : "Keine Gebühr"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Sin comisión de extracción"
+          }
         }
       }
     },
@@ -2032,6 +3044,12 @@
             "state" : "translated",
             "value" : "Hinweis: Es braucht eine ganze Weile um diesen Betrag 
abzuheben. Seien Sie noch geduldiger..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Nota: Tomará algo de tiempo para extraer este monto! 
Sea paciente..."
+          }
         }
       }
     },
@@ -2042,6 +3060,12 @@
             "state" : "translated",
             "value" : "Hinweis: Es braucht eine Weile um diesen Betrag 
abzuheben. Seien Sie geduldig..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Nota: Tomará algo de tiempo estraer este monto. Sea 
paciente..."
+          }
         }
       }
     },
@@ -2052,6 +3076,12 @@
             "state" : "translated",
             "value" : "WalleCore observieren"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Observe walletCore"
+          }
         }
       }
     },
@@ -2062,6 +3092,12 @@
             "state" : "translated",
             "value" : "Erhaltenen Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto obtenido:"
+          }
         }
       }
     },
@@ -2073,6 +3109,28 @@
             "state" : "translated",
             "value" : "Erhalten:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Obtenido:"
+          }
+        }
+      }
+    },
+    "OK" : {
+      "localizations" : {
+        "de" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "OK"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "OK"
+          }
         }
       }
     },
@@ -2083,6 +3141,12 @@
             "state" : "translated",
             "value" : "Möglichst wenig Text"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Omitir texto cuando es posible"
+          }
         }
       }
     },
@@ -2093,6 +3157,12 @@
             "state" : "translated",
             "value" : "on LocalConsole"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "en LocalConsole"
+          }
         }
       }
     },
@@ -2103,6 +3173,12 @@
             "state" : "translated",
             "value" : "KYC Webseite öffnen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Abrir sitio web KYC"
+          }
         }
       }
     },
@@ -2113,6 +3189,12 @@
             "state" : "translated",
             "value" : "Händler-Webseite öffnen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Abrir sitio web del comerciante"
+          }
         }
       }
     },
@@ -2123,6 +3205,12 @@
             "state" : "translated",
             "value" : "Einstellungen öffnen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Abrir Configuración"
+          }
         }
       }
     },
@@ -2133,6 +3221,12 @@
             "state" : "translated",
             "value" : "Kamera wird geöffnet"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Abrir Camara"
+          }
         }
       }
     },
@@ -2143,6 +3237,12 @@
             "state" : "translated",
             "value" : "P2P bereit"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "P2P Listo"
+          }
         }
       }
     },
@@ -2153,6 +3253,12 @@
             "state" : "translated",
             "value" : "P2P erhalten"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "P2P Recibir"
+          }
         }
       }
     },
@@ -2164,6 +3270,12 @@
             "state" : "translated",
             "value" : "Bezahlt"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagado"
+          }
         }
       }
     },
@@ -2174,6 +3286,12 @@
             "state" : "translated",
             "value" : "Bezahlter Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto pagado:"
+          }
         }
       }
     },
@@ -2185,6 +3303,12 @@
             "state" : "translated",
             "value" : "P2P bezahlt"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagado P2P"
+          }
         }
       }
     },
@@ -2196,6 +3320,12 @@
             "state" : "translated",
             "value" : "Anforderung bezahlt"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedido Pagado"
+          }
         }
       }
     },
@@ -2207,16 +3337,29 @@
             "state" : "translated",
             "value" : "Bezahlt:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagado:"
+          }
         }
       }
     },
     "Pay P2P" : {
+      "comment" : "Nav Title",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "P2P bezahlen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagar P2P"
+          }
         }
       }
     },
@@ -2228,6 +3371,12 @@
             "state" : "translated",
             "value" : "Pay Peer-Pull"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagar Peer-Pull"
+          }
         }
       }
     },
@@ -2239,6 +3388,12 @@
             "state" : "translated",
             "value" : "Pay Peer-Push"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagar Peer-Push"
+          }
         }
       }
     },
@@ -2250,6 +3405,12 @@
             "state" : "translated",
             "value" : "Zahlungsanforderung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedido pagado"
+          }
         }
       }
     },
@@ -2261,16 +3422,29 @@
             "state" : "translated",
             "value" : "Bezahlen:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pagar:"
+          }
         }
       }
     },
     "Payee" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Empfänger"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Beneficiario"
+          }
         }
       }
     },
@@ -2279,7 +3453,13 @@
         "de" : {
           "stringUnit" : {
             "state" : "translated",
-            "value" : "Empfänger:"
+            "value" : "Empfänger:"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Beneficiario:"
           }
         }
       }
@@ -2292,6 +3472,12 @@
             "state" : "translated",
             "value" : "Zahlung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pago"
+          }
         }
       }
     },
@@ -2302,6 +3488,12 @@
             "state" : "translated",
             "value" : "Zahlungsdienst"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Proveedor de pagos:"
+          }
         }
       }
     },
@@ -2313,6 +3505,12 @@
             "state" : "translated",
             "value" : "Ausstehend"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pendiente"
+          }
         }
       }
     },
@@ -2323,6 +3521,12 @@
             "state" : "translated",
             "value" : "Zu erhaltender Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto pendiente a obtener:"
+          }
         }
       }
     },
@@ -2334,6 +3538,12 @@
             "state" : "translated",
             "value" : "Ausstehend:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pendiente:"
+          }
         }
       }
     },
@@ -2344,6 +3554,12 @@
             "state" : "translated",
             "value" : "Zahlungs-Töne abspielen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Reproducir Sonidos de Pago"
+          }
         }
       }
     },
@@ -2354,6 +3570,12 @@
             "state" : "translated",
             "value" : "Bitte erlauben Sie den Zugriff auf die Kamera in den 
Systemeinstellungen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Por favor permita el acceso a la cámara desde 
configuración."
+          }
         }
       }
     },
@@ -2364,6 +3586,12 @@
             "state" : "translated",
             "value" : "Bitte geben Sie die URL ein"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Por favor ingrese la URL"
+          }
         }
       }
     },
@@ -2374,6 +3602,12 @@
             "state" : "translated",
             "value" : "Nettopreis:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Precio (neto):"
+          }
         }
       }
     },
@@ -2385,6 +3619,12 @@
             "state" : "translated",
             "value" : "Preis:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Precio:"
+          }
         }
       }
     },
@@ -2395,6 +3635,12 @@
             "state" : "translated",
             "value" : "QR Code"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Código QR"
+          }
         }
       }
     },
@@ -2405,6 +3651,12 @@
             "state" : "translated",
             "value" : "QR Code und Link können auch aus der Umsatzliste 
kopiert/geteilt werden."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "El código QR y el link también pueden ser escaneados o 
copiados / compartidos desde Transacciones después."
+          }
         }
       }
     },
@@ -2416,6 +3668,12 @@
             "state" : "translated",
             "value" : "Erhalten (brutto):"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Recibir (bruto):"
+          }
         }
       }
     },
@@ -2427,6 +3685,12 @@
             "state" : "translated",
             "value" : "Geld erhalten"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Recibir Dinero"
+          }
         }
       }
     },
@@ -2438,6 +3702,12 @@
             "state" : "translated",
             "value" : "Erhalten (netto):"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Recibir (neto):"
+          }
         }
       }
     },
@@ -2449,6 +3719,12 @@
             "state" : "translated",
             "value" : "Geld erhalten"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dinero recibido"
+          }
         }
       }
     },
@@ -2460,6 +3736,12 @@
             "state" : "translated",
             "value" : "P2P erhalten"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Recibido P2P"
+          }
         }
       }
     },
@@ -2470,6 +3752,12 @@
             "state" : "translated",
             "value" : "Aktuellste %lld Umsätze"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "%lld transacciones recientes"
+          }
         }
       }
     },
@@ -2480,6 +3768,12 @@
             "state" : "translated",
             "value" : "Aktueller Umsatz"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Transacciones recientes"
+          }
         }
       }
     },
@@ -2491,6 +3785,12 @@
             "state" : "translated",
             "value" : "Recoup"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Compensación"
+          }
         }
       }
     },
@@ -2502,6 +3802,12 @@
             "state" : "translated",
             "value" : "Recoup:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Compensación:"
+          }
         }
       }
     },
@@ -2513,6 +3819,12 @@
             "state" : "translated",
             "value" : "Erneuerung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Actualizar"
+          }
         }
       }
     },
@@ -2524,6 +3836,12 @@
             "state" : "translated",
             "value" : "Erstattung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Devolución"
+          }
         }
       }
     },
@@ -2534,6 +3852,12 @@
             "state" : "translated",
             "value" : "Erstatteter Betrag:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Monto devuelto:"
+          }
         }
       }
     },
@@ -2545,6 +3869,12 @@
             "state" : "translated",
             "value" : "Erstattet:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Devuelto:"
+          }
         }
       }
     },
@@ -2555,6 +3885,12 @@
             "state" : "translated",
             "value" : "Neu laden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Recargar"
+          }
         }
       }
     },
@@ -2566,6 +3902,12 @@
             "state" : "translated",
             "value" : "%@ anfordern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedir %@"
+          }
         }
       }
     },
@@ -2577,6 +3919,12 @@
             "state" : "translated",
             "value" : "Geld anfordern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedir dinero"
+          }
         }
       }
     },
@@ -2595,6 +3943,12 @@
             "state" : "new",
             "value" : "Request\tPayment"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedir\tPago"
+          }
         }
       }
     },
@@ -2613,6 +3967,12 @@
             "state" : "new",
             "value" : "Request"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pedir"
+          }
         }
       }
     },
@@ -2624,6 +3984,12 @@
             "state" : "translated",
             "value" : "Angefordertes Geld"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Dinero pedido"
+          }
         }
       }
     },
@@ -2634,6 +4000,12 @@
             "state" : "translated",
             "value" : "Zurücksetzen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Reset"
+          }
         }
       }
     },
@@ -2644,6 +4016,12 @@
             "state" : "translated",
             "value" : "Wallet zurücksetzen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Reiniciar Billetera"
+          }
         }
       }
     },
@@ -2654,6 +4032,12 @@
             "state" : "translated",
             "value" : "Beschränkung:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Restricción:"
+          }
         }
       }
     },
@@ -2664,6 +4048,12 @@
             "state" : "translated",
             "value" : "Gerade eben"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ahora mismo"
+          }
         }
       }
     },
@@ -2674,6 +4064,12 @@
             "state" : "translated",
             "value" : "Sichern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Guardar"
+          }
         }
       }
     },
@@ -2684,6 +4080,12 @@
             "state" : "translated",
             "value" : "QR Code scannen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Escanear códigos QR"
+          }
         }
       }
     },
@@ -2694,6 +4096,12 @@
             "state" : "translated",
             "value" : "Um QR Codes zu scannen wird Zugriff auf die Kamera 
benötigt"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Escaneando códigos QR requiere acceso a la cámara"
+          }
         }
       }
     },
@@ -2705,6 +4113,12 @@
             "state" : "translated",
             "value" : "Geplant"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Programado"
+          }
         }
       }
     },
@@ -2715,6 +4129,12 @@
             "state" : "translated",
             "value" : "Abwärts blättern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Desplazar hacia abajo"
+          }
         }
       }
     },
@@ -2725,6 +4145,12 @@
             "state" : "translated",
             "value" : "Aufwärts blättern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Desplazar hacia arriba"
+          }
         }
       }
     },
@@ -2735,6 +4161,12 @@
             "state" : "translated",
             "value" : "Alter auswählen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Seleccione edad"
+          }
         }
       }
     },
@@ -2746,6 +4178,12 @@
             "state" : "translated",
             "value" : "Jetzt %@ senden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Enviar %@ ahora"
+          }
         }
       }
     },
@@ -2757,6 +4195,12 @@
             "state" : "translated",
             "value" : "Geld senden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Enviar Dinero"
+          }
         }
       }
     },
@@ -2775,6 +4219,12 @@
             "state" : "new",
             "value" : "Send\tMoney"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Enviar\tDinero"
+          }
         }
       }
     },
@@ -2793,6 +4243,12 @@
             "state" : "new",
             "value" : "Send"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Enviar"
+          }
         }
       }
     },
@@ -2804,6 +4260,12 @@
             "state" : "translated",
             "value" : "Geld gesendet"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Enviar Dinero"
+          }
         }
       }
     },
@@ -2814,6 +4276,12 @@
             "state" : "translated",
             "value" : "Interner Serialisierungsfehler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Error de serialización"
+          }
         }
       }
     },
@@ -2824,27 +4292,46 @@
             "state" : "translated",
             "value" : "Teilen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Compartir"
+          }
         }
       }
     },
     "Share the PayTo URL" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Die PayTo URL teilen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Compartir la URL PayTo"
+          }
         }
       }
     },
     "Sheet.ID." : {
-      "comment" : "AccessibilityLabel",
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Blatt.ID."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Sheet.ID."
+          }
         }
       }
     },
@@ -2856,6 +4343,12 @@
             "state" : "translated",
             "value" : "Kurzwahl"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Atajo"
+          }
         }
       }
     },
@@ -2866,6 +4359,12 @@
             "state" : "translated",
             "value" : "Logs anzeigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Mostrar logs"
+          }
         }
       }
     },
@@ -2876,6 +4375,12 @@
             "state" : "translated",
             "value" : "Warnungen anzeigen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Mostrar advertencias"
+          }
         }
       }
     },
@@ -2886,6 +4391,12 @@
             "state" : "translated",
             "value" : "Zeigt bei welchen Dialog Sie gerade sind"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Mostrar en que vista estás actualmente."
+          }
         }
       }
     },
@@ -2896,6 +4407,12 @@
             "state" : "translated",
             "value" : "Da die Demobank Taler direkt unterstützt, können Sie 
eine Abhebung starten von der"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Como el bank de demo esta integrado con Taler, puede 
comenzar una extracción directamente en el"
+          }
         }
       }
     },
@@ -2906,6 +4423,12 @@
             "state" : "translated",
             "value" : "Ausstehende Umsätze"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Algunas transacciones pendientes"
+          }
         }
       }
     },
@@ -2916,6 +4439,12 @@
             "state" : "translated",
             "value" : "Standard iOS Zeichensatz"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Fuente iOS Standard"
+          }
         }
       }
     },
@@ -2926,6 +4455,12 @@
             "state" : "translated",
             "value" : "Status: %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Estado: %@"
+          }
         }
       }
     },
@@ -2936,6 +4471,12 @@
             "state" : "translated",
             "value" : "Verwendungszweck"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Asunto"
+          }
         }
       }
     },
@@ -2946,16 +4487,29 @@
             "state" : "translated",
             "value" : "Übersicht"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Resumen"
+          }
         }
       }
     },
     "Taler Logo" : {
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Taler Logo"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Logo Taler"
+          }
         }
       }
     },
@@ -2964,7 +4518,13 @@
         "de" : {
           "stringUnit" : {
             "state" : "translated",
-            "value" : "Drücken Sie den Button um auf die KYC Webseite zu 
gehen."
+            "value" : "Drücken Sie den Button um auf die KYC Webseite zu 
gehen."
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Toque el boton para ir la sitio web KYC."
           }
         }
       }
@@ -2976,6 +4536,12 @@
             "state" : "translated",
             "value" : "Nutzungsbedingungen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Términos de servicio"
+          }
         }
       }
     },
@@ -2986,6 +4552,12 @@
             "state" : "translated",
             "value" : "Die Bank wartet auf Ihre Freigabe."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "El banco esta esperando su confirmación."
+          }
         }
       }
     },
@@ -2996,6 +4568,12 @@
             "state" : "translated",
             "value" : "Der Zahlungsdienst wartet auf Ihre Überweisung."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "El Proveedor de Servicio de Pago esta esperando por su 
transferencia bancaria."
+          }
         }
       }
     },
@@ -3006,6 +4584,12 @@
             "state" : "translated",
             "value" : "Es gibt noch keine Zahlungsdienste."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "No hay Servicios de Pago todavía."
+          }
         }
       }
     },
@@ -3016,6 +4600,12 @@
             "state" : "translated",
             "value" : "Es gibt noch keine Umsätze for %@."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "No hay transacciones para %@."
+          }
         }
       }
     },
@@ -3026,6 +4616,12 @@
             "state" : "translated",
             "value" : "Es gibt noch kein digitales Geld in Ihrem Wallet."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "No hay dinero digital en tu billetera."
+          }
         }
       }
     },
@@ -3036,6 +4632,12 @@
             "state" : "translated",
             "value" : "Dies ist obligatorisch, da Ihr Geld sonst nicht in 
dieser Wallet ankommt."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Esto es obligatorio, sino su dinero no llegará a la 
billetera."
+          }
         }
       }
     },
@@ -3046,6 +4648,12 @@
             "state" : "translated",
             "value" : "Das kann nicht rückgängig gemacht werden"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Esta operación no puede ser deshecha"
+          }
         }
       }
     },
@@ -3056,6 +4664,12 @@
             "state" : "translated",
             "value" : "Diese Transaktion ist noch nicht bereit..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Esta transacción no esta lista..."
+          }
         }
       }
     },
@@ -3073,6 +4687,12 @@
             "state" : "new",
             "value" : "Balances"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Saldos"
+          }
         }
       }
     },
@@ -3090,11 +4710,17 @@
             "state" : "new",
             "value" : "Payment Services"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Servicios de Pago"
+          }
         }
       }
     },
     "TitleIncoming_Full" : {
-      "comment" : "`Pending incoming´ in Balances - set exactly 1 \t for line 
break",
+      "comment" : "`Pending incoming´ in Balances - set exactly 1 \\t for line 
break",
       "extractionState" : "extracted_with_value",
       "localizations" : {
         "de" : {
@@ -3108,6 +4734,12 @@
             "state" : "new",
             "value" : "Pending\tincoming"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pendiente\tentrante"
+          }
         }
       }
     },
@@ -3126,11 +4758,17 @@
             "state" : "new",
             "value" : "Incoming"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Entrante"
+          }
         }
       }
     },
     "TitleOutgoing_Full" : {
-      "comment" : "`Pending outgoing´ in Balances - set exactly 1 \t for line 
break",
+      "comment" : "`Pending outgoing´ in Balances - set exactly 1 \\t for line 
break",
       "extractionState" : "extracted_with_value",
       "localizations" : {
         "de" : {
@@ -3144,6 +4782,12 @@
             "state" : "new",
             "value" : "Pending\toutgoing"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Pendiente\tsaliente"
+          }
         }
       }
     },
@@ -3162,6 +4806,12 @@
             "state" : "new",
             "value" : "Outgoing"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Saliente"
+          }
         }
       }
     },
@@ -3179,6 +4829,12 @@
             "state" : "new",
             "value" : "Settings"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Configuración"
+          }
         }
       }
     },
@@ -3189,6 +4845,12 @@
             "state" : "translated",
             "value" : "Umsatzliste"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Lista de transacciones"
+          }
         }
       }
     },
@@ -3200,6 +4862,12 @@
             "state" : "translated",
             "value" : "Umsätze"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Transacciones"
+          }
         }
       }
     },
@@ -3210,6 +4878,12 @@
             "state" : "translated",
             "value" : "Überweisen Sie %@ an den Zahlungsdienst."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Transferir %@ al Servicio de Pagos."
+          }
         }
       }
     },
@@ -3228,6 +4902,12 @@
             "state" : "new",
             "value" : "Abort"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Detener"
+          }
         }
       }
     },
@@ -3246,6 +4926,12 @@
             "state" : "new",
             "value" : "Delete from history"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Borrar del historial"
+          }
         }
       }
     },
@@ -3264,6 +4950,12 @@
             "state" : "new",
             "value" : "Fail"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Fallar"
+          }
         }
       }
     },
@@ -3282,6 +4974,12 @@
             "state" : "new",
             "value" : "Retry now"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Reintentar ahora"
+          }
         }
       }
     },
@@ -3300,6 +4998,12 @@
             "state" : "new",
             "value" : "Abort pending..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Detención pendiente..."
+          }
         }
       }
     },
@@ -3318,6 +5022,12 @@
             "state" : "new",
             "value" : "Deleted from list"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Borrado de la lista"
+          }
         }
       }
     },
@@ -3336,6 +5046,12 @@
             "state" : "new",
             "value" : "Failing..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Fallando..."
+          }
         }
       }
     },
@@ -3354,6 +5070,12 @@
             "state" : "new",
             "value" : "Retrying..."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Reintentando..."
+          }
         }
       }
     },
@@ -3364,6 +5086,12 @@
             "state" : "translated",
             "value" : "Unbekanntes Kommando"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "comando desconocido"
+          }
         }
       }
     },
@@ -3374,6 +5102,12 @@
             "state" : "translated",
             "value" : "Unbekannter Fehler"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Error desconocido"
+          }
         }
       }
     },
@@ -3384,6 +5118,12 @@
             "state" : "translated",
             "value" : "Unbekannte Zahlungsmethode"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "método de pago desconocido"
+          }
         }
       }
     },
@@ -3395,6 +5135,12 @@
             "state" : "translated",
             "value" : "Unbekannter Zahlungsdienst"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Proveedor de Pagos desconocido"
+          }
         }
       }
     },
@@ -3405,6 +5151,12 @@
             "state" : "translated",
             "value" : "Unbekannter Zahlungsdienst"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Servicio de Pago Desconocido"
+          }
         }
       }
     },
@@ -3415,6 +5167,12 @@
             "state" : "translated",
             "value" : "unbeschränkt"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "sin restricción"
+          }
         }
       }
     },
@@ -3425,6 +5183,12 @@
             "state" : "translated",
             "value" : "Fügen Sie einen Zahlungsdienst hinzu."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Use el botón Agregar para agregar un servicio."
+          }
         }
       }
     },
@@ -3435,6 +5199,12 @@
             "state" : "translated",
             "value" : "Scannen Sie einen QR-Code um eine Abhebung zu starten 
wenn Ihre Bank schon Taler-Abhebungen unterstützt."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Use el botón de escaneo de código QR para comenzar una 
extracción si su banco ya soporta sistema Taler."
+          }
         }
       }
     },
@@ -3445,6 +5215,12 @@
             "state" : "translated",
             "value" : "Mit dem Zahlungsdienst:"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Usando proveedor de servicio de pago:"
+          }
         }
       }
     },
@@ -3455,6 +5231,12 @@
             "state" : "translated",
             "value" : "via %@"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "vía %@"
+          }
         }
       }
     },
@@ -3465,17 +5247,29 @@
             "state" : "translated",
             "value" : "Vibrations-Feedback"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Vibración"
+          }
         }
       }
     },
     "View.ID." : {
-      "comment" : "AccessibilityLabel",
+      "comment" : "VoiceOver",
       "localizations" : {
         "de" : {
           "stringUnit" : {
             "state" : "translated",
             "value" : "Seiten-ID."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "View.ID."
+          }
         }
       }
     },
@@ -3486,6 +5280,12 @@
             "state" : "translated",
             "value" : "Gehe zur taler.net Webseite"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Visite el sitio web taler.net"
+          }
         }
       }
     },
@@ -3496,6 +5296,12 @@
             "state" : "translated",
             "value" : "WalletCore-Version"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Versión WalletCore"
+          }
         }
       }
     },
@@ -3506,6 +5312,12 @@
             "state" : "translated",
             "value" : "Wenn eine Transaktion abgeschlossen ist"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Cuando una trasacción termina"
+          }
         }
       }
     },
@@ -3516,6 +5328,12 @@
             "state" : "translated",
             "value" : "Geht zur Bank-Webseite um diese Abhebung zu bestätigen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá al sitio web del bank para confirmar esta 
extracción."
+          }
         }
       }
     },
@@ -3526,6 +5344,12 @@
             "state" : "translated",
             "value" : "Geht zur Detail-Ansicht."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá a ver el detalle."
+          }
         }
       }
     },
@@ -3536,6 +5360,12 @@
             "state" : "translated",
             "value" : "Geht zur KYC-Webseite um diese Abhebung zu bestätigen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá al sitio web KYC para confirmar esta extracción"
+          }
         }
       }
     },
@@ -3546,6 +5376,12 @@
             "state" : "translated",
             "value" : "Geht zur Transaktions-Liste"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá a la lista de transacciones principal."
+          }
         }
       }
     },
@@ -3556,6 +5392,12 @@
             "state" : "translated",
             "value" : "Geht zu den ausstehenden Transaktionen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá a transacciones pendientes."
+          }
         }
       }
     },
@@ -3566,6 +5408,12 @@
             "state" : "translated",
             "value" : "Geht zur Demo Shop-Webseite"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá al sitio web del comerciante demo."
+          }
         }
       }
     },
@@ -3576,6 +5424,12 @@
             "state" : "translated",
             "value" : "Geht zur Händler-Webseite"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Irá al sitio web del comerciante."
+          }
         }
       }
     },
@@ -3586,6 +5440,12 @@
             "state" : "translated",
             "value" : "Überweisung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Transferencia bancaria"
+          }
         }
       }
     },
@@ -3597,6 +5457,12 @@
             "state" : "translated",
             "value" : "%@\tabheben"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Extraer\t%@"
+          }
         }
       }
     },
@@ -3608,6 +5474,12 @@
             "state" : "translated",
             "value" : "Abhebung"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Extracción"
+          }
         }
       }
     },
@@ -3626,11 +5498,14 @@
             "state" : "new",
             "value" : "Withdraw"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Extraer"
+          }
         }
       }
-    },
-    "xloc.generic.ok" : {
-
     },
     "Yesterday" : {
       "localizations" : {
@@ -3639,6 +5514,12 @@
             "state" : "translated",
             "value" : "Gestern"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Ayer"
+          }
         }
       }
     },
@@ -3649,6 +5530,12 @@
             "state" : "translated",
             "value" : "Sie können in den Einstellungen manuell einen 
Zahlungsdienst hinzufügen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "También puede agregar un servicio de pago manualmente 
en Configuración."
+          }
         }
       }
     },
@@ -3659,6 +5546,12 @@
             "state" : "translated",
             "value" : "Sie können auch im Salden-Tab einen Abheben-QR-Code 
scannen um automatisch einen Zahlungsdienst hinzuzufügen."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "También puede escanear el código QR de extracción desde 
to banco on la solapa de Banaces para agregar un servicio de pago 
automaticamente."
+          }
         }
       }
     },
@@ -3669,6 +5562,12 @@
             "state" : "translated",
             "value" : "Sie können diese %@ im Demo-Shop ausgeben, oder an eine 
andere Wallet senden."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Puede gastar estos %@ en el comercio Demo, o enviarlos 
a otra billetera."
+          }
         }
       }
     },
@@ -3679,6 +5578,12 @@
             "state" : "translated",
             "value" : "Sie haben nicht genug %@."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "No tiene suficiente %@."
+          }
         }
       }
     },
@@ -3690,6 +5595,12 @@
             "state" : "translated",
             "value" : "Sie müssen zuerst die Nutzungsbedingungen des 
Zahlungsdienstes akzeptieren, bevor sie Elektronic-Cash in ihr Wallet bekommen 
können."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Debe aceptar los Términos de Servicio del Proveedor de 
Servicio de Pago antes de recibir dinero electrónico en su billetera."
+          }
         }
       }
     },
@@ -3700,6 +5611,12 @@
             "state" : "translated",
             "value" : "Sie müssen zuerst die Nutzungsbedingungen des 
Zahlungsdienstes akzeptieren, bevor sie Electronic-Cash in Ihr Wallet abheben 
können."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Debe aceptar los Términos de Servicio del Proveedor de 
Servicio de Pago antes de que pueda usarlo para extraer dinero electrónico en 
su billetera."
+          }
         }
       }
     },
@@ -3710,6 +5627,12 @@
             "state" : "translated",
             "value" : "Sie müssen zuerst die Nutzungsbedingungen des 
Zahlungsdienstes akzeptieren."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Debe aceptar los Términos de Servicio del Servicio de 
PAgo antes."
+          }
         }
       }
     },
@@ -3720,6 +5643,12 @@
             "state" : "translated",
             "value" : "Sie müssen ein KYC-Verfahren durchlaufen"
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Necesita pasar un un procedimiento de KYC"
+          }
         }
       }
     },
@@ -3736,6 +5665,12 @@
             "state" : "new",
             "value" : "You need to transfer %1$@ from your regular bank 
account to the Payment Service Provider to receive %2$@ as electronic cash in 
this wallet."
           }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Necesita transferir %1$@ desde una cuenta bancaria 
regular al Proveedor de Servicio de Pago para recibir %2$@ en forma de dinero 
electrónico."
+          }
         }
       }
     }
diff --git a/TalerWallet1/Model/Model+Exchange.swift 
b/TalerWallet1/Model/Model+Exchange.swift
index 717b971..b22432b 100644
--- a/TalerWallet1/Model/Model+Exchange.swift
+++ b/TalerWallet1/Model/Model+Exchange.swift
@@ -146,7 +146,9 @@ fileprivate struct AddExchange: 
WalletBackendFormattedRequest {
 
 /// A request to get info about a currency
 fileprivate struct GetCurrencySpecification: WalletBackendFormattedRequest {
-    typealias Response = CurrencySpecification2
+    struct Response: Codable, Sendable {
+        let currencySpecification: CurrencySpecification
+    }
     func operation() -> String { "getCurrencySpecification" }
     func args() -> Args { Args(scope: scope) }
 
diff --git a/TalerWallet1/Model/Model+Payment.swift 
b/TalerWallet1/Model/Model+Payment.swift
index 3abb316..019b56c 100644
--- a/TalerWallet1/Model/Model+Payment.swift
+++ b/TalerWallet1/Model/Model+Payment.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import Foundation
 import taler_swift
 import AnyCodable
@@ -163,7 +166,7 @@ struct TemplateParams: Codable {
     let summary: String?                    // Human-readable short summary of 
the contract
 }
 /// A request to get an exchange's payment contract terms.
-fileprivate struct PreparePayForTemplate: WalletBackendFormattedRequest {
+fileprivate struct PreparePayForTemplateRequest: WalletBackendFormattedRequest 
{
     typealias Response = PreparePayResult
     func operation() -> String { "preparePayForTemplate" }
     func args() -> Args { Args(talerPayTemplateUri: talerPayTemplateUri, 
templateParams: templateParams) }
@@ -176,6 +179,52 @@ fileprivate struct PreparePayForTemplate: 
WalletBackendFormattedRequest {
     }
 }
 // MARK: -
+struct TemplateContractDetails: Codable {
+    let summary: String?                    // Human-readable short summary of 
the contract. Fixed if this field exists, editable if nil
+    let currency: String?
+    let amount: Amount?                     // Total amount payable. Fixed if 
this field exists, editable if nil
+    let minimumAge: Int
+    let payDuration: RelativeTime
+
+    enum CodingKeys: String, CodingKey {
+        case summary, currency, amount
+        case minimumAge = "minimum_age"
+        case payDuration = "pay_duration"
+    }
+}
+struct TemplateContractDetailsDefaults: Codable {
+    let summary: String?                    // Default 'Human-readable short 
summary' if editable, or empty if nil.
+    let currency: String?
+    let amount: Amount?                     // Default amount if editable, or 
unspecified if nil.
+}
+struct TalerMerchantTemplateDetails: Codable {
+    let templateContract: TemplateContractDetails
+    let editableDefaults: TemplateContractDetailsDefaults?
+//    let requiredCurrency: String?
+    enum CodingKeys: String, CodingKey {
+        case templateContract = "template_contract"
+        case editableDefaults = "editable_defaults"
+//        case requiredCurrency = "required_currency"
+    }
+}
+
+/// The result from checkPayForTemplate
+struct WalletTemplateDetails: Codable {
+    let templateDetails: TalerMerchantTemplateDetails
+    let supportedCurrencies: [String]
+}
+/// A request to get an exchange's payment contract terms.
+fileprivate struct CheckPayForTemplate: WalletBackendFormattedRequest {
+    typealias Response = WalletTemplateDetails
+    func operation() -> String { "checkPayForTemplate" }
+    func args() -> Args { Args(talerPayTemplateUri: talerPayTemplateUri) }
+
+    var talerPayTemplateUri: String
+    struct Args: Encodable {
+        var talerPayTemplateUri: String
+    }
+}
+// MARK: -
 /// The result from confirmPayForUri
 struct ConfirmPayResult: Decodable {
     var type: String
@@ -204,18 +253,25 @@ extension WalletModel {
           return response
     }
     @MainActor
+    func checkPayForTemplateM(_ talerPayTemplateUri: String, viewHandles: Bool 
= false)       // M for MainActor
+      async throws -> WalletTemplateDetails {
+        let request = CheckPayForTemplate(talerPayTemplateUri: 
talerPayTemplateUri)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+        return response
+    }
+    @MainActor
     func preparePayForTemplateM(_ talerPayTemplateUri: String, amount: 
Amount?, summary: String?, viewHandles: Bool = false)       // M for MainActor
-    async throws -> PreparePayResult {
+      async throws -> PreparePayResult {
         let templateParams = TemplateParams(amount: amount, summary: summary)
-        let request = PreparePayForTemplate(talerPayTemplateUri: 
talerPayTemplateUri, templateParams: templateParams)
+        let request = PreparePayForTemplateRequest(talerPayTemplateUri: 
talerPayTemplateUri, templateParams: templateParams)
         let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
         return response
     }
     @MainActor
     func confirmPayM(_ transactionId: String, viewHandles: Bool = false)       
       // M for MainActor
       async throws -> ConfirmPayResult {
-          let request = confirmPayForUri(transactionId: transactionId)
-          let response = try await sendRequest(request, ASYNCDELAY, 
viewHandles: viewHandles)
-          return response
+        let request = confirmPayForUri(transactionId: transactionId)
+        let response = try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+        return response
     }
 }
diff --git a/TalerWallet1/Model/Model+Withdraw.swift 
b/TalerWallet1/Model/Model+Withdraw.swift
index 7aa2488..95caaa9 100644
--- a/TalerWallet1/Model/Model+Withdraw.swift
+++ b/TalerWallet1/Model/Model+Withdraw.swift
@@ -46,8 +46,9 @@ struct WithdrawalExchangeAccountDetails: Decodable {
 // MARK: -
 /// The result from getWithdrawalDetailsForUri
 struct WithdrawUriInfoResponse: Decodable {
-    var amount: Amount
-    var defaultExchangeBaseUrl: String?
+    var amount: Amount?                     // if nil then ask User
+    var currency: String                    // use this if amount=nil
+    var defaultExchangeBaseUrl: String?     // if nil then use 
possibleExchanges
     var possibleExchanges: [Exchange]       // TODO: query these for fees?
 }
 /// A request to get an exchange's withdrawal details.
diff --git a/TalerWallet1/Model/Transaction.swift 
b/TalerWallet1/Model/Transaction.swift
index 956d7ed..ddc1a4d 100644
--- a/TalerWallet1/Model/Transaction.swift
+++ b/TalerWallet1/Model/Transaction.swift
@@ -22,9 +22,9 @@ enum TransactionMinorState: String, Codable {
     // Placeholder until D37 is fully implemented
     case unknown
     case deposit
-    case kyc        // KycRequired
     case aml        // AmlRequired
-    case mergeKycRequired = "merge-kyc"
+    case kyc        // KycRequired
+    case mergeKycRequired = "merge-kyc"     // same as KYC but for P2P
     case track
     case submitPayment = "submit-payment"
     case rebindSession = "rebind-session"
@@ -59,9 +59,8 @@ enum TransactionMinorState: String, Codable {
         switch self {
             case .unknown:                  return self.rawValue
             case .deposit:                  return self.rawValue
-            case .kyc:                      return String(localized: 
"MinorState.kyc", defaultValue: "KYC required", comment: "TxMinorState heading")
+            case .kyc, .mergeKycRequired:   return String(localized: 
"MinorState.kyc", defaultValue: "KYC required", comment: "TxMinorState heading")
             case .aml:                      return String(localized: 
"MinorState.aml", defaultValue: "AML required", comment: "TxMinorState heading")
-            case .mergeKycRequired:         return String(localized: 
"MinorState.mergekyc", defaultValue: "KYC required", comment: "TxMinorState 
heading")
             case .track:                    return self.rawValue
             case .submitPayment:            return self.rawValue
             case .rebindSession:            return self.rawValue
diff --git a/TalerWallet1/Model/WalletModel.swift 
b/TalerWallet1/Model/WalletModel.swift
index 9e48f44..8aaa1e2 100644
--- a/TalerWallet1/Model/WalletModel.swift
+++ b/TalerWallet1/Model/WalletModel.swift
@@ -67,17 +67,26 @@ class WalletModel: ObservableObject {
             throw error
         }
     }
+}
+// MARK: -
+/// A request to tell wallet-core about the network.
+fileprivate struct NetworkAvailabilityRequest: WalletBackendFormattedRequest {
+    struct Response: Decodable {}
+    func operation() -> String { "hintNetworkAvailability" }
+    func args() -> Args { Args(isNetworkAvailable: isNetworkAvailable) }
 
-    func getTransactionByIdT(_ transactionId: String, viewHandles: Bool = 
false)
-      async throws -> Transaction {              // T for any Thread
-        // might be called from a background thread itself
-        let request = GetTransactionById(transactionId: transactionId)
-        return try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+    var isNetworkAvailable: Bool
+
+    struct Args: Encodable {
+        var isNetworkAvailable: Bool
     }
-    /// get the specified transaction from Wallet-Core. No networking involved
-    @MainActor func getTransactionByIdM(_ transactionId: String, viewHandles: 
Bool = false)
-    async throws -> Transaction {                // M for MainActor
-        return try await getTransactionByIdT(transactionId, viewHandles: 
viewHandles)      // call GetTransactionById on main thread
+}
+
+extension WalletModel {
+    func hintNetworkAvailabilityT(_ isNetworkAvailable: Bool = false) async {
+        // T for any Thread
+        let request = NetworkAvailabilityRequest(isNetworkAvailable: 
isNetworkAvailable)
+        _ = try? await sendRequest(request, 0)
     }
 }
 // MARK: -
@@ -93,6 +102,20 @@ fileprivate struct GetTransactionById: 
WalletBackendFormattedRequest {
         var transactionId: String
     }
 }
+
+extension WalletModel {
+    func getTransactionByIdT(_ transactionId: String, viewHandles: Bool = 
false)
+      async throws -> Transaction {              // T for any Thread
+        // might be called from a background thread itself
+        let request = GetTransactionById(transactionId: transactionId)
+        return try await sendRequest(request, ASYNCDELAY, viewHandles: 
viewHandles)
+    }
+    /// get the specified transaction from Wallet-Core. No networking involved
+    @MainActor func getTransactionByIdM(_ transactionId: String, viewHandles: 
Bool = false)
+    async throws -> Transaction {                // M for MainActor
+        return try await getTransactionByIdT(transactionId, viewHandles: 
viewHandles)      // call GetTransactionById on main thread
+    }
+}
 // MARK: -
 /// The info returned from Wallet-core init
 struct VersionInfo: Decodable {
@@ -257,7 +280,6 @@ extension WalletModel {
     }
 }
 // MARK: -
-
 fileprivate struct DevExperimentRequest: WalletBackendFormattedRequest {
     func operation() -> String { "applyDevExperiment" }
     func args() -> Args { Args(devExperimentUri: talerUri) }
@@ -278,4 +300,3 @@ extension WalletModel {
         _ = try await sendRequest(request, 0, viewHandles: viewHandles)
     }
 }
-
diff --git a/TalerWallet1/Quickjs/QuickDataTask.swift 
b/TalerWallet1/Quickjs/QuickDataTask.swift
index 4334e88..15036f4 100644
--- a/TalerWallet1/Quickjs/QuickDataTask.swift
+++ b/TalerWallet1/Quickjs/QuickDataTask.swift
@@ -134,19 +134,23 @@ class QuickDataTask: NSObject {
                         let responseInfoPtr = 
UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
                         responseCb(responseCbCls, responseInfoPtr)
                     }
-                } else {    // TODO: pass error to walletCore
-                    var errmsg = "No http response from \(url)"
-                    logger.error("⁉️ \(self.requestID, privacy: .public)  
\(method, privacy: .public) \(errmsg, privacy: .public)")
-                    var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: 
errmsg.cString(using: .utf8))
-                    var responseInfo = JSHttpResponseInfo(request_id: 
self.requestID,
-                                                              status: 0,
-                                                              errmsg: 
errmsg_p0,
-                                                    response_headers: nil,
-                                                num_response_headers: 0,
-                                                                body: nil,
-                                                            body_len: 0)
-                    let responseInfoPtr = 
UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
-                    responseCb(responseCbCls, responseInfoPtr)
+                } else {    // pass error to walletCore
+                    Task.detached {
+                        var errmsg = "No http response from \(url)"
+                        logger.error("⁉️ \(self.requestID, privacy: .public)  
\(method, privacy: .public) \(error, privacy: .public) \(errmsg, privacy: 
.public)")
+                        await 
WalletModel.shared.hintNetworkAvailabilityT(false)
+                        Controller.shared.checkInternetConnection()    // 
TODO: pass error to walletCore
+                        var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: 
errmsg.cString(using: .utf8))
+                        var responseInfo = JSHttpResponseInfo(request_id: 
self.requestID,
+                                                                  status: 0,
+                                                                  errmsg: 
errmsg_p0,
+                                                        response_headers: nil,
+                                                    num_response_headers: 0,
+                                                                    body: nil,
+                                                                body_len: 0)
+                        let responseInfoPtr = 
UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
+                        responseCb(responseCbCls, responseInfoPtr)
+                    }
                 }
                 requests[requestID] = nil
             }
diff --git a/TalerWallet1/Quickjs/quickjs.swift 
b/TalerWallet1/Quickjs/quickjs.swift
index 19fe572..a62b63e 100644
--- a/TalerWallet1/Quickjs/quickjs.swift
+++ b/TalerWallet1/Quickjs/quickjs.swift
@@ -52,8 +52,8 @@ public class Quickjs {      // acts as singleton, since only 
one instance ever e
     public weak var messageHandler: QuickjsMessageHandler?
     var logger: Logger
 
-    @Atomic(default: 0)
-    private var lastRequestID: Int32
+    @Atomic(default: 0)                                                        
 // boilerPlate around NSLock…
+    private var lastRequestID: Int32                                           
 // …to safeguard increments
     private var requests: [Int32: QuickDataTask] = [:]
 
     private lazy var urlSession: URLSession = {
@@ -84,7 +84,7 @@ public class Quickjs {      // acts as singleton, since only 
one instance ever e
     func reqCreate(_ request: URLRequest,
                 _ responseCb: JSHttpResponseCb?,
              _ responseCbCls: Optional<UnsafeMutableRawPointer>) -> Int32 {
-        let requestID = $lastRequestID.atomicIncrement()
+        let requestID = $lastRequestID.atomicIncrement()                       
 // ensure requestID is unique
         let quickDataTask = QuickDataTask(urlSession: urlSession,
                                              request: request,
                                            requestID: requestID,
diff --git a/TalerWallet1/Taler_Wallet InfoPlist.xcstrings 
b/TalerWallet1/Taler_Wallet InfoPlist.xcstrings
new file mode 100644
index 0000000..25d82e2
--- /dev/null
+++ b/TalerWallet1/Taler_Wallet InfoPlist.xcstrings     
@@ -0,0 +1,78 @@
+{
+  "sourceLanguage" : "en",
+  "strings" : {
+    "CFBundleDisplayName" : {
+      "comment" : "Bundle display name",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Taler Wallet"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Taler Wallet"
+          }
+        }
+      }
+    },
+    "CFBundleName" : {
+      "comment" : "Bundle name",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Taler_Wallet"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Taler_Wallet"
+          }
+        }
+      }
+    },
+    "NSCameraUsageDescription" : {
+      "comment" : "Privacy - Camera Usage Description",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "Scan QR Codes"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "Escanear códigos QR"
+          }
+        }
+      }
+    },
+    "NSHumanReadableCopyright" : {
+      "comment" : "Copyright (human-readable)",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "© Taler-Systems.com"
+          }
+        },
+        "es" : {
+          "stringUnit" : {
+            "state" : "translated",
+            "value" : "© Taler-Systems.com"
+          }
+        }
+      }
+    }
+  },
+  "version" : "1.0"
+}
\ No newline at end of file
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift 
b/TalerWallet1/Views/Balances/BalancesListView.swift
index 804edf2..2f3e40a 100644
--- a/TalerWallet1/Views/Balances/BalancesListView.swift
+++ b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -13,6 +13,7 @@ struct BalancesListView: View {
     let stack: CallStack
     let navTitle: String
     @Binding var balances: [Balance]
+//    @Binding var shouldReloadPending: Int
     @Binding var shouldReloadBalances: Int
 
     @EnvironmentObject private var model: WalletModel
@@ -100,6 +101,7 @@ struct BalancesListView: View {
 #endif
         Content(symLog: symLog, stack: stack.push(), balances: $balances,
                 amountToTransfer: $amountToTransfer, summary: $summary,
+//                shouldReloadPending: $shouldReloadPending,
                 shouldReloadBalances: $shouldReloadBalances,
                 reloadBalances: reloadBalances)
             .navigationTitle(navTitle)
@@ -110,7 +112,7 @@ struct BalancesListView: View {
                                     dismissAlertButton },
                        message: {   Text("Please allow camera access in 
settings.") })
             .sheet(isPresented: $showQRScanner, onDismiss: dismissingSheet) {
-                let sheet = AnyView(QRSheet(stack: stack.push()))
+                let sheet = AnyView(QRSheet(stack: stack.push(".sheet")))
                 Sheet(sheetView: sheet)
             } // sheet
             .task(id: shouldReloadBalances) {
@@ -130,6 +132,7 @@ extension BalancesListView {
         @Binding var balances: [Balance]
         @Binding var amountToTransfer: Amount
         @Binding var summary: String
+//        @Binding var shouldReloadPending: Int
         @Binding var shouldReloadBalances: Int
         var reloadBalances: (_ stack: CallStack, _ invalidateCache: Bool) 
async -> Int?
 
diff --git a/TalerWallet1/Views/Balances/PendingRowView.swift 
b/TalerWallet1/Views/Balances/PendingRowView.swift
index f8411ed..3601e12 100644
--- a/TalerWallet1/Views/Balances/PendingRowView.swift
+++ b/TalerWallet1/Views/Balances/PendingRowView.swift
@@ -18,12 +18,12 @@ struct PendingRowView: View {
     let inTitle0 = String(localized: "TitleIncoming_Short", defaultValue: 
"Incoming",
                           comment: "Abbreviation of `Pending incoming´ in 
Balances")
     let inTitle1 = String(localized: "TitleIncoming_Full", defaultValue: 
"Pending\tincoming",
-                          comment: "`Pending incoming´ in Balances - set 
exactly 1 \t for line break")
+                          comment: "`Pending incoming´ in Balances - set 
exactly 1 \\t for line break")
 
     let outTitle0 = String(localized: "TitleOutgoing_Short", defaultValue: 
"Outgoing",
                            comment: "Abbreviation of `Pending outgoing´ in 
Balances")
     let outTitle1 = String(localized: "TitleOutgoing_Full", defaultValue: 
"Pending\toutgoing",
-                           comment: "`Pending outgoing´ in Balances - set 
exactly 1 \t for line break")
+                           comment: "`Pending outgoing´ in Balances - set 
exactly 1 \\t for line break")
 
     var body: some View {
         let pendingColor = WalletColors().pendingColor(incoming)
diff --git a/TalerWallet1/Views/Banking/DepositAmountV.swift 
b/TalerWallet1/Views/Banking/DepositAmountV.swift
index 0884295..8f36830 100644
--- a/TalerWallet1/Views/Banking/DepositAmountV.swift
+++ b/TalerWallet1/Views/Banking/DepositAmountV.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -44,8 +47,6 @@ struct DepositAmountV: View {
         feeAmount = nil
         return feeAmount
     }
-    
-    var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) 
fee") : EMPTYSTRING }
 
     private func feeIsNotZero() -> Bool? {
         if let hasNoFees = exchange?.noFees {
@@ -109,9 +110,9 @@ struct DepositAmountV: View {
                                    title: minimalistic ? String(localized: 
"Amount:")
                                                        : String(localized: 
"Amount to deposit:"),
                           shortcutAction: nil)
-//                .padding(.top)
+
                 Text(insufficient ? insufficientLabel
-                                  : feeLabel)
+                                  : feeLabel(feeStr))
                     .talerFont(.body)
                     .foregroundColor(insufficient ? .red
                                                   : (feeAmount?.isZero ?? 
true) ? WalletColors().secondary(colorScheme, colorSchemeContrast)
@@ -157,7 +158,7 @@ struct DepositAmountV: View {
                     }
 
                     if insufficient {
-                        announce(this: "\(amountVoiceOver), 
\(insufficientLabel2)")
+                        announce("\(amountVoiceOver), \(insufficientLabel2)")
                         feeStr = EMPTYSTRING
                     }
                 }
@@ -167,11 +168,15 @@ struct DepositAmountV: View {
                         prepareDepositResult = nil
                     } else if let paytoUri {
                         if let ppCheck = try? await 
model.prepareDepositM(paytoUri, amount: amountToTransfer) {
-                            prepareDepositResult = ppCheck
-                            if let feeAmount = fee(ppCheck: 
prepareDepositResult) {
+                            if let feeAmount = fee(ppCheck: ppCheck) {
                                 feeStr = feeAmount.string(currencyInfo)
-                            } else { feeStr = EMPTYSTRING }
-                            announce(this: "\(amountVoiceOver), \(feeLabel)")
+                                let feeLabel = feeLabel(feeStr)
+                                announce("\(amountVoiceOver), \(feeLabel)")
+                            } else {
+                                feeStr = EMPTYSTRING
+                                announce(amountVoiceOver)
+                            }
+                            prepareDepositResult = ppCheck
                         } else {
                             prepareDepositResult = nil
                         }
diff --git a/TalerWallet1/Views/Banking/ExchangeListView.swift 
b/TalerWallet1/Views/Banking/ExchangeListView.swift
index 6191730..e6c6e45 100644
--- a/TalerWallet1/Views/Banking/ExchangeListView.swift
+++ b/TalerWallet1/Views/Banking/ExchangeListView.swift
@@ -26,14 +26,14 @@ struct ExchangeListView: View {
             symLog.log("adding: \(exchange)")
             if let _ = try? await model.addExchange(url: exchange) {
                 symLog.log("added: \(exchange)")
-                announce(this: "added: \(exchange)")
+                announce("added: \(exchange)")
             }
         }
     }
 
     var body: some View {
-        let accessibilityLabelStr = String(localized: "Add Payment Service", 
comment: "accessibilityLabel for the + button")
-        let plusButton = PlusButton(accessibilityLabelStr: 
accessibilityLabelStr) {
+        let a11yLabelStr = String(localized: "Add Payment Service", comment: 
"VoiceOver for the + button")
+        let plusButton = PlusButton(accessibilityLabelStr: a11yLabelStr) {
             showAlert = true
         }
         let addTitleStr = String(localized: "Add Payment Service", comment: 
"title of the addExchange alert")
diff --git a/TalerWallet1/Views/HelperViews/AmountInputV.swift 
b/TalerWallet1/Views/HelperViews/AmountInputV.swift
index 4ebb3e8..cc6ae64 100644
--- a/TalerWallet1/Views/HelperViews/AmountInputV.swift
+++ b/TalerWallet1/Views/HelperViews/AmountInputV.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -14,10 +17,10 @@ struct AmountInputV: View {
     let amountAvailable: Amount?                    // TODO: 
GetMaxPeerPushAmount
     @Binding var amountToTransfer: Amount
     let amountLabel: String
-    let wantsSummary: Bool                      // if true we call 
SubjectInputV next
+    let summaryIsEditable: Bool                      // if true we call 
SubjectInputV next
     @Binding var summary: String
-    @Binding var insufficient: Bool
-    @Binding var feeAmount: Amount?
+//    @Binding var insufficient: Bool
+//    @Binding var feeAmount: Amount?
     let shortcutAction: ((_ amount: Amount) -> Void)?
     let buttonAction: () -> Void
 
@@ -29,24 +32,22 @@ struct AmountInputV: View {
     @State private var preparePayResult: PreparePayResult? = nil
     @State private var feeStr: String = EMPTYSTRING
 
-    var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) 
fee") : EMPTYSTRING }
+    struct Flags {
+        let insufficient: Bool
+        let disabled: Bool
+    }
 
-    func preparePayForTemplate() async {
-        if let url {
-            if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary) {
-                let amount = ppCheck.amountRaw
-                let currency = amount.currencyStr
-                let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
-                insufficient = ppCheck.status == .insufficientBalance
-                feeAmount = templateFee(ppCheck: ppCheck)
-                if let feeAmount {
-                    feeStr = feeAmount.string(currencyInfo)
-                } else { feeStr = EMPTYSTRING }
-                let amountVoiceOver = amount.string(currencyInfo)
-                announce(this: "\(amountVoiceOver), \(feeLabel)")
-                preparePayResult = ppCheck
+    func checkAvailable(amount: Amount) -> Flags {
+        if let amountAvailable {
+            do {
+                let insufficient = try amountAvailable > amount
+                let disabled = insufficient || amount.isZero
+                return Flags(insufficient: insufficient, disabled: disabled)
+            } catch {
+                // TODO: error Amounts don't match
             }
         }
+        return Flags(insufficient: false, disabled: amount.isZero)
     }
 
     var body: some View {
@@ -54,58 +55,60 @@ struct AmountInputV: View {
         let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
         let insufficientLabel = String(localized: "You don't have enough 
\(currency).")
         let available = amountAvailable?.string(currencyInfo) ?? nil
-        let disabled = insufficient || amountToTransfer.isZero
-        VStack(alignment: .trailing) {
+        let flags = checkAvailable(amount: amountToTransfer)
+        ScrollView { VStack(alignment: .trailing) {
             if let available {
                 Text("Available:\t\(available)")
                     .talerFont(.title3)
                     .padding(.bottom, 2)
-                //                .accessibility(sortPriority: 3)
+//                  .accessibility(sortPriority: 3)
             }
             CurrencyInputView(amount: $amountToTransfer,
                            available: amountAvailable,
                                title: amountLabel,
                       shortcutAction: shortcutAction)
 //                .accessibility(sortPriority: 2)
-            Text(insufficient ? insufficientLabel
-                              : feeLabel)
-                .talerFont(.body)
-                .foregroundColor(insufficient ? .red
-                : (feeAmount?.isZero ?? true) ? 
WalletColors().secondary(colorScheme, colorSchemeContrast)
-                                              : .red)
-//                .accessibility(sortPriority: 1)
-                .padding(4)
-            Group {
-                if let url {
-                    let destination = LazyView {
-                        PaymentView(stack: stack.push(),
-                                      url: url,
-                                 template: true,
-                         amountToTransfer: $amountToTransfer,
-                                  summary: $summary)
-                    }
-                    NavigationLink(destination: destination) {
-                        Text("Next")
-                    }
-                    .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
-                    .disabled(disabled)
-                } else {
-                    Button("Next") {
-                        buttonAction()
-                    }
-                        .buttonStyle(TalerButtonStyle(type: .prominent, 
disabled: disabled))
-                        .disabled(disabled)
-                }
-            }
-//                .accessibility(sortPriority: 0)
-                .task(id: amountToTransfer.value) {
-                    symLog.log(".task")
-                    await preparePayForTemplate()
-                }
-        }.padding(.horizontal)
-            .onAppear() {
-                //            symLog.log("onAppear")
-                DebugViewC.shared.setSheetID(SHEET_PAY_TEMPL_AMOUNT)
+            if flags.insufficient {
+                Text(insufficientLabel)
+                    .talerFont(.body)
+                    .foregroundColor(.red)
+                    .padding(4)
+//            } else {
+//                Text(feeLabel(feeStr))
+//                    .talerFont(.body)
+//                    .foregroundColor((feeAmount?.isZero ?? true) ? 
WalletColors().secondary(colorScheme, colorSchemeContrast)
+//                                     : .red)
+//                    .padding(4)
+//                  .accessibility(sortPriority: 1)
             }
+            Button("Next") { buttonAction() }
+                .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
flags.disabled))
+                .disabled(flags.disabled)
+        }.padding(.horizontal) } // ScrollVStack
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+        .onAppear() {
+//          symLog.log("onAppear")
+            DebugViewC.shared.setSheetID(SHEET_PAY_TEMPL_AMOUNT)
+        }
+//        .task(id: amountToTransfer.value) {
+//            if let url {
+//                symLog.log(".task preparePayForTemplate")
+//                if let result = await preparePayForTemplate(model: model,
+//                                                              url: url,
+//                                                           amount: 
amountToTransfer,
+//                                                          summary: 
summaryIsEditable ? summary ?? " "
+//                                                                             
        : nil,
+//                                                         announce: announce)
+//                {   symLog.log("preparePayForTemplate finished")
+//                    insufficient = result.insufficient
+//                    feeAmount = result.feeAmount
+//                    feeStr = result.feeStr
+//                    preparePayResult = result.ppCheck
+//                } else {
+//                    symLog.log("preparePayForTemplateM failed")
+//                }
+//            }
+//        }
     }
 }
diff --git a/TalerWallet1/Views/HelperViews/AmountV.swift 
b/TalerWallet1/Views/HelperViews/AmountV.swift
index f2bd5be..1e29c64 100644
--- a/TalerWallet1/Views/HelperViews/AmountV.swift
+++ b/TalerWallet1/Views/HelperViews/AmountV.swift
@@ -11,15 +11,8 @@ struct AmountV: View {
 
     @EnvironmentObject private var controller: Controller
 
-    var amountStr: String {
-        if let currencyInfo = controller.info(for: amount.currencyStr) {
-            return amount.string(currencyInfo)
-        }
-        return amount.readableDescription
-    }
-
     var body: some View {
-        Text(amountStr)
+        Text(amount.string())
             .multilineTextAlignment(.center)
             .talerFont(large ? .title : .title2)
 //            .fontWeight(large ? .medium : .regular)       // @available(iOS 
16.0, *)
diff --git a/TalerWallet1/Views/HelperViews/LaunchAnimationView.swift 
b/TalerWallet1/Views/HelperViews/LaunchAnimationView.swift
index 5a4cf84..4210aaa 100644
--- a/TalerWallet1/Views/HelperViews/LaunchAnimationView.swift
+++ b/TalerWallet1/Views/HelperViews/LaunchAnimationView.swift
@@ -38,7 +38,8 @@ struct RotatingTaler: View {
             .scaledToFit()
             .frame(width: size, height: size)
             .rotationEffect(rotationDirection ? Angle(degrees: 0) : 
Angle(degrees: 900))
-            .accessibilityLabel(progress ? "In progress" : "Taler Logo")       
// decorative logo - with button function
+            .accessibilityLabel(progress ? Text("In progress", comment: 
"VoiceOver")
+                                         : Text("Taler Logo", comment: 
"VoiceOver"))       // decorative logo - with button function
             .onReceive(animationTimer) { timerValue in
                 if rotationEnabled {
                     withAnimation(.easeInOut(duration: 1.9)) {
diff --git a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift 
b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
index 44796f2..fa46b83 100644
--- a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
+++ b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
@@ -15,13 +15,6 @@ struct QRCodeDetailView: View {
     @EnvironmentObject private var controller: Controller
     @AppStorage("minimalistic") var minimalistic: Bool = false
 
-    var amountStr: String {
-        if let currencyInfo = controller.info(for: amount.currencyStr) {
-            return amount.string(currencyInfo)
-        }
-        return amount.readableDescription
-    }
-
     var body: some View {
         if talerURI.count > 10 {
             Section {
@@ -58,6 +51,7 @@ struct QRCodeDetailView: View {
                     .accessibilityLabel("QR Code")
                     .listRowSeparator(.hidden)
 
+                let amountStr = amount.string()
                 let scanMini = incoming ? String(localized: "Either (payer) 
Mini 3",
                                               defaultValue: "to pay 
\(amountStr).", comment: "e.g. '5,3 €'")
                                         : String(localized: "Either (payee) 
Mini 3",
diff --git a/TalerWallet1/Views/HelperViews/SubjectInputV.swift 
b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
index df64493..5357ba2 100644
--- a/TalerWallet1/Views/HelperViews/SubjectInputV.swift
+++ b/TalerWallet1/Views/HelperViews/SubjectInputV.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -15,8 +18,8 @@ struct SubjectInputV<TargetView: View>: View {
     @Binding var amountToTransfer: Amount
     let amountLabel: String
     @Binding var summary: String
-    @Binding var insufficient: Bool
-    @Binding var feeAmount: Amount?
+//    @Binding var insufficient: Bool
+//    @Binding var feeAmount: Amount?
     let feeIsNotZero: Bool?             // nil = no fees at all, false = no 
fee for this tx
     let currencyInfo: CurrencyInfo
 
@@ -30,37 +33,21 @@ struct SubjectInputV<TargetView: View>: View {
     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     @AppStorage("minimalistic") var minimalistic: Bool = false
 
+    let navTitle = String(localized: "Custom Summary", comment:"pay merchant")
+
     @State private var preparePayResult: PreparePayResult? = nil
-    @State private var feeStr: String = EMPTYSTRING
     @FocusState private var isFocused: Bool
 
-    var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) 
fee") : EMPTYSTRING }
-
-    func preparePayForTemplate() async {
-        if let url {
-            if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary) {
-                let amount = ppCheck.amountRaw
-                let currency = amount.currencyStr
-                let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
-                insufficient = ppCheck.status == .insufficientBalance
-                feeAmount = templateFee(ppCheck: ppCheck)
-                if let feeAmount {
-                    feeStr = feeAmount.string(currencyInfo)
-                } else { feeStr = EMPTYSTRING }
-                let amountVoiceOver = amount.string(currencyInfo)
-                announce(this: "\(amountVoiceOver), \(feeLabel)")
-                preparePayResult = ppCheck
-            }
-        }
-    }
-
     var body: some View {
         let currency = amountToTransfer.currencyStr
-        let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
-        let insufficientLabel = String(localized: "You don't have enough 
\(currency).")
+//        let feeStr = feeAmount?.string(currencyInfo) ?? EMPTYSTRING
+//        let insufficientLabel = String(localized: "You don't have enough 
\(currency).")
+//        let feeLabel = insufficient ? insufficientLabel
+//                                    : feeLabel(feeStr)
         let available = amountAvailable?.string(currencyInfo) ?? nil
-        let disabled = insufficient || summary.count == 0
-        VStack(alignment: .leading) {
+//        let disabled = insufficient || summary.count == 0
+        let disabled = summary.count == 0
+        ScrollView { VStack(alignment: .leading) {
             if let available {
                 Text("Available:\t\(available)")
                     .talerFont(.title3)
@@ -97,22 +84,31 @@ struct SubjectInputV<TargetView: View>: View {
                     }
                 }
             }
-
-            let subLabel = insufficient ? insufficientLabel
-                                        : feeLabel
-            Text(subLabel)
+            HStack {
+                Text(amountToTransfer.string(currencyInfo))
+                // TODO: hasFees?
+//                Text(feeLabel)
+            }
                 .talerFont(.body)
-                .foregroundColor(insufficient ? .red
-                                 : (feeAmount?.isZero ?? true) ? 
WalletColors().secondary(colorScheme, colorSchemeContrast)
-                                 : .red)
-            //                    .accessibility(sortPriority: 1)
+//                .foregroundColor(insufficient ? .red : 
WalletColors().secondary(colorScheme, colorSchemeContrast))
+                .foregroundColor(WalletColors().secondary(colorScheme, 
colorSchemeContrast))
+//                .accessibility(sortPriority: 1)
                 .padding(4)
+//            if insufficient {
+//                Text(insufficientLabel)
+//                    .talerFont(.body)
+//                    .foregroundColor(.red)
+//                    .padding(4)
+//            }
 
             NavigationLink("Next", destination: targetView)
                 .buttonStyle(TalerButtonStyle(type: .prominent, disabled: 
disabled))
                 .disabled(disabled)
 //                .accessibility(sortPriority: 0)
-        }.padding(.horizontal)
+        }.padding(.horizontal) } // ScrollVStack
+        .navigationTitle(navTitle)
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
         .onAppear() {
 //            symLog.log("onAppear")
             DebugViewC.shared.setSheetID(SHEET_PAY_TEMPL_SUBJECT)
diff --git a/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift 
b/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift
index baecf5a..cbfd0c5 100644
--- a/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift
+++ b/TalerWallet1/Views/HelperViews/View+fitsSideBySide.swift
@@ -7,7 +7,7 @@ import UIKit
 
 extension View {
     @MainActor
-    public func announce(this: String) {
+    public func announce(_ this: String) {
         if UIAccessibility.isVoiceOverRunning {
             UIAccessibility.post(notification: .announcement, argument: this)
         }
diff --git a/TalerWallet1/Views/Main/MainView.swift 
b/TalerWallet1/Views/Main/MainView.swift
index 3c3bced..d70b292 100644
--- a/TalerWallet1/Views/Main/MainView.swift
+++ b/TalerWallet1/Views/Main/MainView.swift
@@ -121,6 +121,7 @@ extension MainView {
         @EnvironmentObject private var viewState2: ViewState2     // 
popToRootView()
 
         @State private var shouldReloadBalances = 0
+        @State private var shouldReloadPending = 0
         @State private var balances: [Balance] = []
         @State private var selectedTab: Tab = .balances
         @State private var showKycAlert: Bool = false
@@ -193,6 +194,7 @@ extension MainView {
                     BalancesListView(stack: stack.push(balancesTitle),
                                   navTitle: balancesTitle,
                                   balances: $balances,
+//                       shouldReloadPending: $shouldReloadPending,
                       shouldReloadBalances: $shouldReloadBalances)
                 }.id(viewState.rootViewId)                // any change to 
rootViewId triggers popToRootView behaviour
                 .navigationViewStyle(.stack)
@@ -255,14 +257,19 @@ extension MainView {
                 shouldReloadBalances += 1
             }
             .onNotification(.TransactionExpired) { notification in
-                // reload balances on receiving BalanceChange notification ...
+                // reload balances on receiving TransactionExpired 
notification ...
                 logger.info(".onNotification(.TransactionExpired) ==> reload")
                 shouldReloadBalances += 1
+                shouldReloadPending += 1
             }
             .onNotification(.TransactionDone) {
                 shouldReloadBalances += 1
+                shouldReloadPending += 1
                 selectedTab = .balances
             }
+            .onNotification(.TransactionError) { notification in
+                shouldReloadPending += 1
+            }
             .onNotification(.Error) { notification in
                 if let error = notification.userInfo?[NOTIFICATIONERROR] as? 
Error {
                     model.setError(error)
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift 
b/TalerWallet1/Views/Peer2peer/SendAmount.swift
index ab2dac3..af727a0 100644
--- a/TalerWallet1/Views/Peer2peer/SendAmount.swift
+++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -43,8 +46,6 @@ struct SendAmount: View {
         feeAmount = nil
         return feeAmount
     }
-    
-    var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) 
fee") : EMPTYSTRING }
 
     private func shortcutAction(_ shortcut: Amount) {
         amountShortcut = shortcut
@@ -82,11 +83,11 @@ struct SendAmount: View {
 
         let inputDestination = LazyView {
             P2PSubjectV(stack: stack.push(),
-                     feeLabel: feeLabel,
+                     feeLabel: feeLabel(feeStr),
                  feeIsNotZero: feeIsNotZero(),
                  currencyInfo: currencyInfo,
                  amountToSend: true,
-             amountToTransfer: $amountToTransfer,
+             amountToTransfer: $amountToTransfer,       // from the textedit
                       summary: $summary,
                    expireDays: $expireDays)
         }
@@ -96,20 +97,22 @@ struct SendAmount: View {
                  feeIsNotZero: feeIsNotZero(),
                  currencyInfo: currencyInfo,
                  amountToSend: true,
-             amountToTransfer: $amountShortcut,
+             amountToTransfer: $amountShortcut,         // from the tapped 
shortcut button
                       summary: $summary,
                    expireDays: $expireDays)
         }
         ScrollView {
+            let amountLabel = minimalistic ? String(localized: "Amount:")
+                                           : String(localized: "Amount to 
send:")
+
             AmountInputV(stack: stack.push(), url: nil,
                amountAvailable: amountAvailable,
               amountToTransfer: $amountToTransfer,
-                   amountLabel: minimalistic ? String(localized: "Amount:")
-                                             : String(localized: "Amount to 
send:"),
-                  wantsSummary: true,
+                   amountLabel: amountLabel,
+             summaryIsEditable: true,
                        summary: $summary,
-                  insufficient: $insufficient,
-                     feeAmount: $feeAmount,
+//                  insufficient: $insufficient,
+//                     feeAmount: $feeAmount,
                 shortcutAction: shortcutAction,
                   buttonAction: buttonAction)
             .background(NavigationLink(destination: shortcutDestination, 
isActive: $shortcutSelected)
@@ -144,18 +147,22 @@ struct SendAmount: View {
             }
 
             if insufficient {
-                announce(this: "\(amountVoiceOver), \(insufficientLabel2)")
+                announce("\(amountVoiceOver), \(insufficientLabel2)")
             } else if amountToTransfer.isZero {
                 feeStr = EMPTYSTRING
             } else {
                 if let ppCheck = try? await 
model.checkPeerPushDebitM(amountToTransfer) {
-                    peerPushCheck = ppCheck
                 // TODO: set from exchange
 //                agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
-                    if let feeAmount = fee(ppCheck: peerPushCheck) {
+                    if let feeAmount = fee(ppCheck: ppCheck) {
                         feeStr = feeAmount.string(currencyInfo)
-                    } else { feeStr = EMPTYSTRING }
-                    announce(this: "\(amountVoiceOver), \(feeLabel)")
+                        let feeLabel = feeLabel(feeStr)
+                        announce("\(amountVoiceOver), \(feeLabel)")
+                    } else {
+                        feeStr = EMPTYSTRING
+                        announce(amountVoiceOver)
+                    }
+                    peerPushCheck = ppCheck
                 } else {
                     peerPushCheck = nil
                 }
diff --git a/TalerWallet1/Views/Settings/AboutView.swift 
b/TalerWallet1/Views/Settings/AboutView.swift
index 2a4965a..a01271e 100644
--- a/TalerWallet1/Views/Settings/AboutView.swift
+++ b/TalerWallet1/Views/Settings/AboutView.swift
@@ -35,10 +35,9 @@ struct AboutView: View {
         let walletCore = WalletCore.shared
         Group {
             List {
-                RotatingTaler(size: 100, progress: false,
-                              rotationEnabled: $rotationEnabled)
-                .frame(maxWidth: .infinity, alignment: .center)
-//                    .accessibilityLabel("Progress indicator")
+                RotatingTaler(size: 100, progress: false, rotationEnabled: 
$rotationEnabled)
+                    // has its own accessibilityLabel
+                    .frame(maxWidth: .infinity, alignment: .center)
                     .onTapGesture(count: 1) { rotationEnabled.toggle() }
                 SettingsItem(name: String(localized: "Visit the taler.net 
website"), id1: "web",
                              description: minimalistic ? nil : 
String(localized: "More info about GNU Taler in general...")) { }
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift 
b/TalerWallet1/Views/Settings/SettingsView.swift
index 3c456a2..86ad303 100644
--- a/TalerWallet1/Views/Settings/SettingsView.swift
+++ b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -154,12 +154,12 @@ struct SettingsView: View {
                     }
 #if DEBUG
                     if showDevelopItems {
-                        Text(verbatim: "https://bank.taler.grothoff.org/";)
-                        Text(verbatim: "https://bank.regio-taler.fdold.eu/";)
-                        Text(verbatim: "https://bank.head.taler.net/";)
-                        Text(verbatim: "https://bank.test.taler.net/";)
-                        Text(verbatim: "https://bank.demo.taler.net/";)
-                        Text(verbatim: "https://bank.taler.ar/";)
+                        let banks = ["taler.grothoff.org", 
"regio-taler.fdold.eu", "taler.ar",
+                                     "head.taler.net", "demo.taler.net", 
"test.taler.net"]
+                        ForEach(banks, id: \.self) { bank in
+                            let urlStr = "https://bank."; + bank
+                            Link(bank, destination: URL(string: urlStr)!)
+                        }
                     }
 #endif
                     if showDevelopItems {  // show or hide the following items
diff --git a/TalerWallet1/Views/Sheets/ErrorSheet.swift 
b/TalerWallet1/Views/Sheets/ErrorSheet.swift
index 2953d2d..49ecdff 100644
--- a/TalerWallet1/Views/Sheets/ErrorSheet.swift
+++ b/TalerWallet1/Views/Sheets/ErrorSheet.swift
@@ -9,8 +9,8 @@ import SwiftUI
 import taler_swift
 
 enum ErrorData {
-case message(title: String, message: String)
-case error(Error)
+    case message(title: String, message: String)
+    case error(Error)
 }
 
 struct ErrorSheet: View {
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
index fed0b41..9d945e1 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pPayURIView.swift
@@ -11,7 +11,7 @@ import SymLog
 struct P2pPayURIView: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
-    let navTitle = String(localized: "Pay P2P")
+    let navTitle = String(localized: "Pay P2P", comment: "Nav Title")
 
     // the scanned URL
     let url: URL
@@ -41,16 +41,16 @@ struct P2pPayURIView: View {
                                      large: false, pending: false, incoming: 
false,
                                    baseURL: nil,
                                     noFees: nil,        // TODO: check baseURL 
for fees
-                                    status: nil,
+                                txStateLcl: nil,
                                    summary: 
peerPullDebitResponse.contractTerms.summary,
                                   merchant: nil)
                     let expiration = 
peerPullDebitResponse.contractTerms.purse_expiration
                     let (dateString, date) = TalerDater.dateString(from: 
expiration)
-                    let accessibilityDate = TalerDater.accessibilityDate(date) 
?? dateString
-                    let accessibilityLabel = String(localized: "Expires: 
\(accessibilityDate)")
+                    let a11yDate = TalerDater.accessibilityDate(date) ?? 
dateString
+                    let a11yLabel = String(localized: "Expires: \(a11yDate)", 
comment: "VoiceOver")
                     Text("Expires: \(dateString)")
                         .talerFont(.body)
-                        .accessibilityLabel(accessibilityLabel)
+                        .accessibilityLabel(a11yLabel)
                         .foregroundColor(WalletColors().secondary(colorScheme, 
colorSchemeContrast))
                 }
                 .listStyle(myListStyle.style).anyView
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift 
b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
index 09235bc..d28413b 100644
--- a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
+++ b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
@@ -50,16 +50,16 @@ struct P2pReceiveURIView: View {
                                      large: false, pending: false, incoming: 
true,
                                    baseURL: nil,
                                     noFees: nil,        // TODO: check baseURL 
for fees
-                                    status: nil,
+                                txStateLcl: nil,
                                    summary: 
peerPushCreditResponse.contractTerms.summary,
                                   merchant: nil)
                     let expiration = 
peerPushCreditResponse.contractTerms.purse_expiration
                     let (dateString, date) = TalerDater.dateString(from: 
expiration)
-                    let accessibilityDate = TalerDater.accessibilityDate(date) 
?? dateString
-                    let accessibilityLabel = String(localized: "Expires: 
\(accessibilityDate)")
+                    let a11yDate = TalerDater.accessibilityDate(date) ?? 
dateString
+                    let a11yLabel = String(localized: "Expires: \(a11yDate)", 
comment: "VoiceOver")
                     Text("Expires: \(dateString)")
                         .talerFont(.body)
-                        .accessibilityLabel(accessibilityLabel)
+                        .accessibilityLabel(a11yLabel)
                         .foregroundColor(WalletColors().secondary(colorScheme, 
colorSchemeContrast))
                 }
                 .listStyle(myListStyle.style).anyView
diff --git a/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift 
b/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
index 6eaa555..ec5a3b2 100644
--- a/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PayTemplateV.swift
@@ -1,26 +1,17 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
 
-func templateFee(ppCheck: PreparePayResult?) -> Amount? {
-    do {
-        if let ppCheck {
-            // Outgoing: fee = effective - raw
-            if let effective = ppCheck.amountEffective {
-                let fee = try effective - ppCheck.amountRaw
-                return fee
-            }
-        }
-    } catch {}
-    return nil
-}
-
-// Will be called either by the user scanning a QR code or tapping the 
provided link,
-// both from the shop's website. We show the payment details
+// Will be called either by the user scanning a pay-template QR code or 
tapping the provided link,
+// both from the shop's website or even from a printed QR code.
+// We check whether amount and/or summary is editable, and finally go to 
PaymentView
 struct PayTemplateV: View {
     private let symLog = SymLogV(0)
     let stack: CallStack
@@ -33,22 +24,23 @@ struct PayTemplateV: View {
     @AppStorage("minimalistic") var minimalistic: Bool = false
     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
 
-    let navTitle = String(localized: "Customize Order", comment:"pay merchant")
+    let navTitle = String(localized: "Custom Amount", comment:"pay merchant")
 
-    @State private var insufficient = false
-    @State private var preparePayResult: PreparePayResult? = nil
-    @State private var currencyName: String? = nil
+//    @State private var insufficient = false
+//    @State private var preparePayResult: PreparePayResult? = nil
+    @State private var templateContract: TemplateContractDetails? = nil
+    @State private var currencyName = EMPTYSTRING
+    @State private var amountIsEditable = false
     @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING)   
 // Update currency when used
     @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING)     
 // Update currency when used
     @State private var shortcutSelected = false
     @State private var buttonSelected1 = false
     @State private var buttonSelected2 = false
+    @State private var summaryIsEditable = false
     @State private var summary: String = EMPTYSTRING       // templateParam
-    @State private var wantsSummary: Bool = false
-    @State private var feeAmount: Amount? = nil
-    @State private var feeStr: String = EMPTYSTRING
 
-    var feeLabel: String { feeStr.count > 0 ? String(localized: "+ \(feeStr) 
fee") : EMPTYSTRING }
+//    @State private var feeAmount: Amount? = nil
+//    @State private var feeStr: String = EMPTYSTRING
 
     private func shortcutAction(_ shortcut: Amount) {
         amountShortcut = shortcut
@@ -73,46 +65,13 @@ struct PayTemplateV: View {
         }
     }
 
-    func queryURL() -> Bool {
-        if let queryParameters = url.queryParameters {
-            if let amountStr = queryParameters["amount"] {
-                currencyName = amountStr
-                amountToTransfer = Amount.zero(currency: amountStr)
-            }
-            if let summaryStr = queryParameters["summary"] {
-                summary = summaryStr
-                wantsSummary = true
-            }
-            return true
-        }
-        return false
-    }
-
-    func preparePayForTemplate() async {
-        if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amountToTransfer, 
summary: summary) {
-            let amount = ppCheck.amountRaw
-            let currency = amount.currencyStr
-            let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
-            insufficient = ppCheck.status == .insufficientBalance
-            feeAmount = templateFee(ppCheck: ppCheck)
-            if let feeAmount {
-                feeStr = feeAmount.string(currencyInfo)
-            } else { feeStr = EMPTYSTRING }
-            let amountVoiceOver = amount.string(currencyInfo)
-            announce(this: "\(amountVoiceOver), \(feeLabel)")
-            preparePayResult = ppCheck
-        } else {
-            symLog.log("preparePayForTemplateM failed")
-        }
-    }
-
     var body: some View {
-        if let preparePayResult {
-            let effective = preparePayResult.amountEffective
-            let baseURL = preparePayResult.contractTerms.exchanges.first?.url
-            let raw = preparePayResult.amountRaw
-            let currency = raw.currencyStr
-            let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
+        if let templateContract {       // preparePayResult
+//            let effective = preparePayResult.amountEffective
+//            let baseURL = preparePayResult.contractTerms.exchanges.first?.url
+//            let raw = preparePayResult.amountRaw
+//            let currency = raw.currencyStr
+            let currencyInfo = controller.info(for: currencyName, 
controller.currencyTicker)
             let amountLabel = minimalistic ? String(localized: "Amount:")
                                            : String(localized: "Amount to 
pay:")
             let finalDestinationI = LazyView {
@@ -120,53 +79,58 @@ struct PayTemplateV: View {
                               url: url,
                          template: true,
                  amountToTransfer: $amountToTransfer,
-                          summary: $summary)
-            }
+                          summary: $summary,
+                 amountIsEditable: amountIsEditable,
+                summaryIsEditable: summaryIsEditable)
+            }  // final destination with amountToTransfer, after user input of 
amount
+            let finalDestinationS = LazyView {
+                PaymentView(stack: stack.push(),
+                              url: url,
+                         template: true,
+                 amountToTransfer: $amountShortcut,
+                          summary: $summary,
+                 amountIsEditable: amountIsEditable,
+                summaryIsEditable: summaryIsEditable)
+            }  // final destination with amountShortcut, when user tapped a 
shortcut
+
             let inputDestination = LazyView {
                 SubjectInputV(stack: stack.push(), url: url,
                     amountAvailable: nil,
                    amountToTransfer: $amountToTransfer,
                         amountLabel: amountLabel,
                             summary: $summary,
-                       insufficient: $insufficient,
-                          feeAmount: $feeAmount,
-                       feeIsNotZero: true, // feeIsNotZero(),
+//                       insufficient: $insufficient,
+//                          feeAmount: $feeAmount,
+                       feeIsNotZero: true, // TODO: feeIsNotZero()
                        currencyInfo: currencyInfo,
                          targetView: finalDestinationI)
-            }
-            let finalDestinationS = LazyView {
-                PaymentView(stack: stack.push(),
-                              url: url,
-                         template: true,
-                 amountToTransfer: $amountShortcut,
-                          summary: $summary)
-            }
+            }   // destination to subject input
             let shortcutDestination = LazyView {
                 SubjectInputV(stack: stack.push(), url: url,
                     amountAvailable: nil,
                    amountToTransfer: $amountShortcut,
                         amountLabel: amountLabel,
                             summary: $summary,
-                       insufficient: $insufficient,
-                          feeAmount: $feeAmount,
-                       feeIsNotZero: true, // feeIsNotZero(),
+//                       insufficient: $insufficient,
+//                          feeAmount: $feeAmount,
+                       feeIsNotZero: true, // TODO: feeIsNotZero()
                        currencyInfo: currencyInfo,
                          targetView: finalDestinationS)
-            }
+            }// destination to subject input, when user tapped an amount 
shortcut
           Group {
-            if let currencyName {    // template included a currency name => 
let the user input an amount
+            if amountIsEditable {           // template contract amount is not 
fixed => let the user input an amount first
                 let amountInput = AmountInputV(stack: stack.push(), url: url,
                                      amountAvailable: nil,
                                     amountToTransfer: $amountToTransfer,
                                          amountLabel: amountLabel,
-                                        wantsSummary: wantsSummary,
+                                   summaryIsEditable: summaryIsEditable,
                                              summary: $summary,
-                                        insufficient: $insufficient,
-                                           feeAmount: $feeAmount,
+//                                        insufficient: $insufficient,
+//                                           feeAmount: $feeAmount,
                                       shortcutAction: shortcutAction,
                                         buttonAction: buttonAction1)
                 ScrollView {
-                    if wantsSummary {
+                    if summaryIsEditable {  // after amount input,
                         amountInput
                             .background(NavigationLink(destination: 
shortcutDestination, isActive: $shortcutSelected)
                                         { EmptyView() }.frame(width: 
0).opacity(0).hidden())
@@ -180,32 +144,64 @@ struct PayTemplateV: View {
                             .background(NavigationLink(destination: 
finalDestinationI, isActive: $buttonSelected1)
                                         { EmptyView() }.frame(width: 
0).opacity(0).hidden())
                     }
-                } // ScrollVStack
-            } else if wantsSummary { // check summary
+                }
+            } else if summaryIsEditable {   // template contract summary is 
not fixed => let the user input a summary
                 ScrollView {
                     inputDestination
                         .background(NavigationLink(destination: 
finalDestinationI, isActive: $buttonSelected2)
                                     { EmptyView() }.frame(width: 
0).opacity(0).hidden()
                         )
                 }
-            } else {
+            } else {                        // both template contract amount 
and summary are fixed => directly show the payment
                 PaymentView(stack: stack.push(),
                               url: url,
                          template: true,
                  amountToTransfer: $amountToTransfer,
-                          summary: $summary)
+                          summary: $summary,
+                 amountIsEditable: amountIsEditable,
+                summaryIsEditable: summaryIsEditable)
             }
-          }.onAppear() {
+          }.navigationTitle(navTitle)
+          .frame(maxWidth: .infinity, alignment: .leading)
+          
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+          .onAppear() {
               symLog.log("onAppear")
               DebugViewC.shared.setSheetID(SHEET_PAY_TEMPLATE)
-          }.navigationTitle(navTitle)
+          }
         } else {
             LoadingView(scopeInfo: nil, message: url.host)
                 .task {
-                    symLog.log("LoadingView.task preparePayForTemplate")
-                    let hasParams = queryURL()
-                    await preparePayForTemplate()
-                    symLog.log("preparePayForTemplate finished")
+                    if let details = try? await 
model.checkPayForTemplateM(url.absoluteString) {
+                        let supportedCurrency0 = details.supportedCurrencies[0]
+                        let contract = 
details.templateDetails.templateContract     // specifies fixed amount/summary
+                        amountIsEditable = contract.amount == nil
+                        summaryIsEditable = contract.summary == nil
+                        let defaults = 
details.templateDetails.editableDefaults     // might be nil, or its fields 
might be nil
+                        let prepCurrency = contract.currency ?? 
defaults?.currency ?? supportedCurrency0
+                        let zeroAmount = Amount(currency: prepCurrency, cent: 
0)
+                        let prepAmount = contract.amount ?? defaults?.amount   
     // might be nil
+                        let prepSummary = contract.summary ?? 
defaults?.summary     // might be nil
+//                        symLog.log("LoadingView.task preparePayForTemplate")
+//                        if let result = await preparePayForTemplate(model: 
model,
+//                                                                      url: 
url,
+//                                                                   amount: 
amountIsEditable ? prepAmount ?? zeroAmount
+//                                                                             
               : nil,
+//                                                                  summary: 
summaryIsEditable ? prepSummary ?? " "
+//                                                                             
                : nil,
+//                                                                 announce: 
announce)
+//                        {   symLog.log("preparePayForTemplate finished")
+                            amountToTransfer = prepAmount ?? zeroAmount
+                            currencyName = prepCurrency
+                            summary = prepSummary ?? EMPTYSTRING
+                        templateContract = contract
+//                            insufficient = result.insufficient
+//                            feeAmount = result.feeAmount
+//                            feeStr = result.feeStr
+//                            preparePayResult = result.ppCheck
+//                        } else {
+//                            symLog.log("preparePayForTemplateM failed")
+//                        }
+                    }
                 }
         }
     }
diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift 
b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
index dd3581f..8dfcc08 100644
--- a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
+++ b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
@@ -1,11 +1,69 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
 
+typealias Announce = (_ this: String) -> ()
+
+func feeLabel(_ feeString: String) -> String {
+    feeString.count > 0 ? String(localized: "+ \(feeString) fee")
+    : EMPTYSTRING
+}
+
+func templateFee(ppCheck: PreparePayResult?) -> Amount? {
+    do {
+        if let ppCheck {
+            // Outgoing: fee = effective - raw
+            if let effective = ppCheck.amountEffective {
+                let fee = try effective - ppCheck.amountRaw
+                return fee
+            }
+        }
+    } catch {}
+    return nil
+}
+
+struct PayForTemplateResult {
+    let ppCheck: PreparePayResult
+    let insufficient: Bool
+    let feeAmount: Amount?
+    let feeStr: String
+}
+
+func preparePayForTemplate(model: WalletModel,
+                             url: URL,
+                          amount: Amount?,
+                         summary: String?,
+                        announce: Announce)
+  async -> PayForTemplateResult? {
+    if let ppCheck = try? await 
model.preparePayForTemplateM(url.absoluteString, amount: amount, summary: 
summary) {
+        let controller = Controller.shared
+        let amountRaw = ppCheck.amountRaw
+        let currency = amountRaw.currencyStr
+        let currencyInfo = controller.info(for: currency, 
controller.currencyTicker)
+        let amountVoiceOver = amountRaw.string(currencyInfo)
+        let insufficient = ppCheck.status == .insufficientBalance
+        if let feeAmount = templateFee(ppCheck: ppCheck) {
+            let feeStr = feeAmount.string(currencyInfo)
+            let feeLabel = feeLabel(feeStr)
+            announce("\(amountVoiceOver), \(feeLabel)")
+            return PayForTemplateResult(ppCheck: ppCheck, insufficient: 
insufficient,
+                                        feeAmount: feeAmount, feeStr: feeStr)
+        }
+        announce(amountVoiceOver)
+        return PayForTemplateResult(ppCheck: ppCheck, insufficient: 
insufficient,
+                                    feeAmount: nil, feeStr: EMPTYSTRING)
+    }
+    return nil
+}
+
+// MARK: -
 // Will be called either by the user scanning a QR code or tapping the 
provided link,
 // both from the shop's website. We show the payment details in a sheet.
 struct PaymentView: View {
@@ -18,6 +76,8 @@ struct PaymentView: View {
     let template: Bool
     @Binding var amountToTransfer: Amount
     @Binding var summary: String
+    let amountIsEditable: Bool                      //
+    let summaryIsEditable: Bool                      //
 
     @EnvironmentObject private var model: WalletModel
     @EnvironmentObject private var controller: Controller
@@ -34,6 +94,7 @@ struct PaymentView: View {
                 // TODO: show balanceDetails.balanceAvailable
                 let baseURL = 
preparePayResult.contractTerms.exchanges.first?.url
                 let raw = preparePayResult.amountRaw
+                let status = preparePayResult.status
                 let currency = raw.currencyStr
                 let topTitle = String(localized: "Amount to pay:")
                 let topAbbrev = String(localized: "Pay:", comment: "mini")
@@ -50,7 +111,7 @@ struct PaymentView: View {
                                      large: false, pending: false, incoming: 
false,
                                    baseURL: baseURL,
                                     noFees: nil,        // TODO: check baseURL 
for fees
-                                    status: nil,
+                                txStateLcl: nil,
                                    summary: terms.summary,
                                   merchant: terms.merchant.name)
                     // TODO: payment: popup with all possible exchanges, check 
fees
@@ -67,7 +128,7 @@ struct PaymentView: View {
                                      large: false, pending: false, incoming: 
false,
                                    baseURL: baseURL,
                                     noFees: nil,        // TODO: check baseURL 
for fees
-                                    status: nil,
+                                txStateLcl: nil,
                                    summary: terms.summary,
                                   merchant: terms.merchant.name)
                 } else {
@@ -102,8 +163,8 @@ struct PaymentView: View {
                 symLog.log(".task")
                 if template {
                     if let result = try? await 
model.preparePayForTemplateM(url.absoluteString,
-                                                                 amount: 
amountToTransfer,
-                                                                            
summary: summary) {
+                                                                     amount: 
amountIsEditable ? amountToTransfer : nil,
+                                                                    summary: 
summaryIsEditable ? summary : nil) {
                         preparePayResult = result
                     }
                 } else {
@@ -170,9 +231,10 @@ struct PaymentURIView_Previews: PreviewProvider {
 //        @State private var amount: Amount? = nil        // templateParam
 //        @State private var summary: String? = nil       // templateParam
 
-        PaymentView(stack: CallStack("Preview"),
-                    url: url, template: false, amountToTransfer: nil, summary: 
nil,
-                    preparePayResult: details)
+        PaymentView(stack: CallStack("Preview"), url: url,
+                 template: false, amountToTransfer: nil, summary: nil,
+         amountIsEditable: false, summaryIsEditable: false,
+         preparePayResult: details)
     }
 }
 #endif
diff --git a/TalerWallet1/Views/Sheets/QRSheet.swift 
b/TalerWallet1/Views/Sheets/QRSheet.swift
index d775deb..8445157 100644
--- a/TalerWallet1/Views/Sheets/QRSheet.swift
+++ b/TalerWallet1/Views/Sheets/QRSheet.swift
@@ -12,12 +12,24 @@ struct QRSheet: View {
     let stack: CallStack
     @State private var scannedCode: String?
 
+    func codeToURL(_ code: String) -> URL? {
+        if let scannedURL = URL(string: code) {
+            return scannedURL
+        }
+        if let encodedScan = code.addingPercentEncoding(withAllowedCharacters: 
.urlQueryAllowed) {
+            if let encodedURL = URL(string: encodedScan) {
+                return encodedURL
+            }
+        }
+        return nil
+    }
+
     var body: some View {
         Group {
             if scannedCode != nil {
 //                let _ = print(scannedCode as Any)       // TODO: logging
 
-                if let scannedURL = URL(string: scannedCode!) {
+                if let scannedURL = codeToURL(scannedCode!) {
                     let scheme = scannedURL.scheme
                     if scheme?.lowercased() == "taler" {
                         URLSheet(stack: stack.push(), urlToOpen: scannedURL)
diff --git a/TalerWallet1/Views/Sheets/Sheet.swift 
b/TalerWallet1/Views/Sheets/Sheet.swift
index 6aa796e..7b3cae5 100644
--- a/TalerWallet1/Views/Sheets/Sheet.swift
+++ b/TalerWallet1/Views/Sheets/Sheet.swift
@@ -58,7 +58,7 @@ struct Sheet: View {
                 .monospacedDigit()
                 .edgesIgnoringSafeArea(.top)
                 .id("sheetID")
-                .accessibilityLabel(Text("Sheet.ID.", comment: 
"AccessibilityLabel"))
+                .accessibilityLabel(Text("Sheet.ID.", comment: "VoiceOver"))
                 .accessibilityValue(idString)
         }
         .onDisappear {
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift 
b/TalerWallet1/Views/Sheets/URLSheet.swift
index ea0e809..9cb70f1 100644
--- a/TalerWallet1/Views/Sheets/URLSheet.swift
+++ b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -1,7 +1,10 @@
 /*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-24 Taler Systems S.A.
  * See LICENSE.md
  */
+/**
+ * @author Marc Stibane
+ */
 import SwiftUI
 import taler_swift
 import SymLog
@@ -32,7 +35,8 @@ struct URLSheet: View {
                     WithdrawExchangeV(stack: stack.push(), url: urlToOpen)     
 // TODO: just check the ToS
                 case .pay:
                     PaymentView(stack: stack.push(), url: urlToOpen,
-                             template: false, amountToTransfer: 
$amountToTransfer, summary: $summary)
+                             template: false, amountToTransfer: 
$amountToTransfer, summary: $summary,
+                     amountIsEditable: false, summaryIsEditable: false)
                 case .payPull:
                     P2pPayURIView(stack: stack.push(), url: urlToOpen)
                 case .payPush:
diff --git 
a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift 
b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
index 35840d1..4d3bd51 100644
--- a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
+++ b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -56,7 +56,7 @@ struct WithdrawURIView: View {
                                      large: false, pending: false, incoming: 
true,
                                    baseURL: exchange.exchangeBaseUrl,
                                     noFees: exchange.noFees,
-                                    status: nil,        // 
common.txState.major.localizedState
+                                txStateLcl: nil,        // 
common.txState.major.localizedState
                                    summary: nil,
                                   merchant: nil)
                     let someCoins = SomeCoins(details: withdrawalAmountDetails)
@@ -95,18 +95,21 @@ struct WithdrawURIView: View {
         .task {
             symLog.log(".task")
             if let withdrawUriInfo = try? await 
model.getWithdrawalDetailsForUriM(url.absoluteString) {
-                let amount = withdrawUriInfo.amount
-                let baseUrl = withdrawUriInfo.defaultExchangeBaseUrl
-                           ?? 
withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl
-                if let baseUrl {
-                    exchange = try? await model.getExchangeByUrl(url: baseUrl)
-                    if let details = try? await 
model.getWithdrawalDetailsForAmountM(baseUrl, amount: amount) {
-                        withdrawalAmountDetails = details
+                if let amount = withdrawUriInfo.amount {
+                    let baseUrl = withdrawUriInfo.defaultExchangeBaseUrl
+                    ?? withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl
+                    if let baseUrl {
+                        exchange = try? await model.getExchangeByUrl(url: 
baseUrl)
+                        if let details = try? await 
model.getWithdrawalDetailsForAmountM(baseUrl, amount: amount) {
+                            withdrawalAmountDetails = details
+                        }
+                        //                    agePicker.setAges(ages: 
details?.ageRestrictionOptions)
+                    } else {    // TODO: error
+                        symLog.log("no exchangeBaseUrl or no exchange")
+                        withdrawalAmountDetails = nil
                     }
-                    //                    agePicker.setAges(ages: 
details?.ageRestrictionOptions)
-                } else {    // TODO: error
-                    symLog.log("no exchangeBaseUrl or no exchange")
-                    withdrawalAmountDetails = nil
+                } else {
+                    // TODO: amount = nil ==> show amount input
                 }
             }
         }
diff --git a/TalerWallet1/Views/Transactions/ManualDetailsV.swift 
b/TalerWallet1/Views/Transactions/ManualDetailsV.swift
index ea25e11..f007ec2 100644
--- a/TalerWallet1/Views/Transactions/ManualDetailsV.swift
+++ b/TalerWallet1/Views/Transactions/ManualDetailsV.swift
@@ -159,14 +159,26 @@ struct ManualDetailsV: View {
         }
     }
 
+    func amountString(_ amount: Amount, specs: CurrencySpecification?, scope: 
ScopeInfo? = nil) -> String {
+        if let specs {
+            let myScope = scope ?? ScopeInfo(type: .madeUp, currency: 
amount.currencyStr)
+            let currencyInfo = CurrencyInfo(scope: myScope, specs: specs,
+                                        formatter: 
CurrencyFormatter.formatter(scope: myScope,
+                                                                               
specs: specs))
+            return amount.string(currencyInfo)
+        }
+        return amount.string()
+    }
+
     var body: some View {
         if let accountDetails = details.exchangeCreditAccountDetails {
             let validDetails = validDetails(accountDetails)
             if validDetails.count > 0 {
                 let account = validDetails[accountID]
                 if let amount = account.transferAmount {
-                    let amountStr = amount.readableDescription        // TODO: 
formatter?
-                    let obtainStr = common.amountRaw.readableDescription
+                    let specs = account.currencySpecification
+                    let amountStr = amountString(amount, specs: specs)
+                    let obtainStr = common.amountRaw.string()
                     if !minimalistic {
                         Text("The Payment Service Provider is waiting for your 
wire-transfer.")
                             .bold()
@@ -183,7 +195,6 @@ struct ManualDetailsV: View {
                                 .listRowSeparator(.hidden)
                         }
                     } else if let amount = account.transferAmount {
-                        let amountStr = amount.readableDescription
                         if let bankName = account.bankLabel {
                             Text(bankName + ":   " + amountStr)
                         } else {
@@ -193,10 +204,10 @@ struct ManualDetailsV: View {
                     let payto = account.paytoUri
                     let payURL = URL(string: payto)
                     if let queryParameters = payURL?.queryParameters {
-                        let amountStr = queryParameters["amount"] ?? 
EMPTYSTRING
-                        let receiverStr = queryParameters["receiver-name"] ?? 
EMPTYSTRING
-                        let senderStr = queryParameters["sender-name"] ?? 
EMPTYSTRING
-                        let messageStr = queryParameters["message"] ?? 
EMPTYSTRING
+                        let receiverStr = (queryParameters["receiver-name"] ?? 
EMPTYSTRING).replacingOccurrences(of: "+", with: " ")
+//                        let amountStr = queryParameters["amount"] ?? 
EMPTYSTRING
+//                        let messageStr = queryParameters["message"] ?? 
EMPTYSTRING
+//                        let senderStr = queryParameters["sender-name"] ?? 
EMPTYSTRING
 
                         let iban = payURL?.iban
                         let xTaler = payURL?.xTaler ??
@@ -205,10 +216,10 @@ struct ManualDetailsV: View {
                         let cryptocode = HStack {
                             Text(details.reservePub)
                                 .monospacedDigit()
-                                .accessibilityLabel("Cryptocode")
+                                .accessibilityLabel(Text("Cryptocode", 
comment: "VoiceOver"))
                                 .frame(maxWidth: .infinity, alignment: 
.leading)
                             CopyButton(textToCopy: details.reservePub, 
vertical: true)
-                                .accessibilityLabel("Copy the cryptocode")
+                                .accessibilityLabel(Text("Copy the 
cryptocode", comment: "VoiceOver"))
                                 .disabled(false)
                         }   .padding(.leading)
                         let payeeCode = HStack {
@@ -220,9 +231,9 @@ struct ManualDetailsV: View {
                                     .padding(.leading)
                             }   .frame(maxWidth: .infinity, alignment: 
.leading)
                                 .accessibilityElement(children: .combine)
-                                .accessibilityLabel("Payee")
+                                .accessibilityLabel(Text("Payee", comment: 
"VoiceOver"))
                             CopyButton(textToCopy: receiverStr, vertical: true)
-                                .accessibilityLabel("Copy the payee")
+                                .accessibilityLabel(Text("Copy the payee", 
comment: "VoiceOver"))
                                 .disabled(false)
                         }   .padding(.top, -8)
                         let ibanCode = HStack {
@@ -234,9 +245,9 @@ struct ManualDetailsV: View {
                                     .padding(.leading)
                             }   .frame(maxWidth: .infinity, alignment: 
.leading)
                                 .accessibilityElement(children: .combine)
-                                .accessibilityLabel("IBAN of the payee")
+                                .accessibilityLabel(Text("IBAN of the payee", 
comment: "VoiceOver"))
                             CopyButton(textToCopy: iban ?? EMPTYSTRING, 
vertical: true)
-                                .accessibilityLabel("Copy the IBAN")
+                                .accessibilityLabel(Text("Copy the IBAN", 
comment: "VoiceOver"))
                                 .disabled(false)
                         }   .padding(.top, -8)
                         let xTalerCode = HStack {
@@ -248,9 +259,9 @@ struct ManualDetailsV: View {
                                     .padding(.leading)
                             }   .frame(maxWidth: .infinity, alignment: 
.leading)
                                 .accessibilityElement(children: .combine)
-                                .accessibilityLabel("account of the payee")
+                                .accessibilityLabel(Text("account of the 
payee", comment: "VoiceOver"))
                             CopyButton(textToCopy: xTaler, vertical: true)
-                                .accessibilityLabel("Copy the account")
+                                .accessibilityLabel(Text("Copy the account", 
comment: "VoiceOver"))
                                 .disabled(false)
                         }   .padding(.top, -8)
 
@@ -305,7 +316,7 @@ struct ManualDetailsV: View {
 //                              Spacer()
                                 ShareButton(textToShare: payto)
                                     .frame(maxWidth: .infinity, alignment: 
.center)
-                                    .accessibilityLabel("Share the PayTo URL")
+                                    .accessibilityLabel(Text("Share the PayTo 
URL", comment: "VoiceOver"))
                                     .disabled(false)
 //                              Spacer()
                             }   .listRowSeparator(.automatic)
diff --git a/TalerWallet1/Views/Transactions/ThreeAmountsV.swift 
b/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
index c5945df..8951a92 100644
--- a/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
+++ b/TalerWallet1/Views/Transactions/ThreeAmountsV.swift
@@ -18,6 +18,12 @@ struct ThreeAmountsSheet: View {
     let summary: String?
     let merchant: String?
 
+#if DEBUG
+    @AppStorage("developerMode") var developerMode: Bool = true
+#else
+    @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+
     var body: some View {
         let raw = common.amountRaw
         let effective = common.amountEffective
@@ -35,6 +41,9 @@ struct ThreeAmountsSheet: View {
                                                       : String(localized: 
"Obtained:", comment: "mini") )
                                            : (pending ? String(localized: 
"Pay:", comment: "mini")
                                                       : String(localized: 
"Paid:", comment: "mini") )
+        let majorLcl = common.txState.major.localizedState
+        let txStateLcl = developerMode && pending ? 
(common.txState.minor?.localizedState ?? majorLcl)
+                                                  : majorLcl
         ThreeAmountsV(stack: stack.push(),
                    topTitle: topTitle, topAbbrev: topAbbrev,
                   topAmount: raw, fee: fee,
@@ -44,7 +53,7 @@ struct ThreeAmountsSheet: View {
                       large: large, pending: pending, incoming: incoming,
                     baseURL: baseURL,
                      noFees: noFees,
-                     status: common.txState.major.localizedState,
+                 txStateLcl: txStateLcl,
                     summary: summary,
                    merchant: merchant)
     }
@@ -64,7 +73,7 @@ struct ThreeAmountsV: View {
     let incoming: Bool
     let baseURL: String?
     let noFees: Bool?                       // true if exchange charges no 
fees at all
-    let status: String?
+    let txStateLcl: String?                 // localizedState
     let summary: String?
     let merchant: String?
 
diff --git a/TalerWallet1/Views/Transactions/TransactionRowView.swift 
b/TalerWallet1/Views/Transactions/TransactionRowView.swift
index 61e878d..df40764 100644
--- a/TalerWallet1/Views/Transactions/TransactionRowView.swift
+++ b/TalerWallet1/Views/Transactions/TransactionRowView.swift
@@ -90,7 +90,7 @@ struct TransactionRowView: View {
             .talerFont(.headline)
 //            .fontWeight(.medium)      iOS 16
             .padding(.bottom, -2.0)
-            .accessibilityLabel(doneOrPending ? topString : topString +  
String(localized: ", canceled"))
+            .accessibilityLabel(doneOrPending ? topString : topString +  
String(localized: ", canceled", comment: "VoiceOver"))
         let centerBottom = Text(dateString)
             .foregroundColor(textColor)
             .talerFont(.callout)
diff --git a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift 
b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
index 12f6314..f9a6bd7 100644
--- a/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
+++ b/TalerWallet1/Views/Transactions/TransactionSummaryV.swift
@@ -100,7 +100,7 @@ struct TransactionSummaryV: View {
         let pending = transaction.isPending
         let locale = TalerDater.shared.locale
         let (dateString, date) = TalerDater.dateString(from: common.timestamp)
-        let accessibilityDate = TalerDater.accessibilityDate(date) ?? 
dateString
+        let a11yDate = TalerDater.accessibilityDate(date) ?? dateString
         let navTitle2 = transaction.isDone ? transaction.localizedTypePast
                                            : transaction.localizedType
         VStack {
@@ -117,7 +117,7 @@ struct TransactionSummaryV: View {
                 } // Suspend + Resume buttons
                 Text(dateString)
                     .talerFont(.body)
-                    .accessibilityLabel(accessibilityDate)
+                    .accessibilityLabel(a11yDate)
                     .foregroundColor(WalletColors().secondary(colorScheme, 
colorSchemeContrast))
                     .listRowSeparator(.hidden)
                 VStack(alignment: .trailing) {
@@ -296,10 +296,9 @@ struct TransactionSummaryV: View {
                         let title = EMPTYSTRING
                         Text(title)
                             .talerFont(.body)
-                        RotatingTaler(size: 100, progress: true,
-                                      rotationEnabled: $rotationEnabled)
+                        RotatingTaler(size: 100, progress: true, 
rotationEnabled: $rotationEnabled)
                             .frame(maxWidth: .infinity, alignment: .center)
-//                            .accessibilityLabel("Progress indicator")
+                            // has its own accessibilityLabel
                     case .withdrawal(let withdrawalTransaction): Group {
                         let details = withdrawalTransaction.details
                         if pending {
@@ -373,7 +372,7 @@ struct TransactionSummaryV: View {
                                     let stackStr = error.stack ?? EMPTYSTRING
                                     let errorStr = error.hint + "\n" + stackStr
                                     CopyButton(textToCopy: errorStr, vertical: 
true)
-                                        .accessibilityLabel("Copy the error")
+                                        .accessibilityLabel(Text("Copy the 
error", comment: "VoiceOver"))
                                         .disabled(false)
                                 }
                             }
@@ -384,11 +383,11 @@ struct TransactionSummaryV: View {
                         if !transaction.isDone {
                             let expiration = details.info.expiration
                             let (dateString, date) = 
TalerDater.dateString(from: expiration)
-                            let accessibilityDate = 
TalerDater.accessibilityDate(date) ?? dateString
-                            let accessibilityLabel = String(localized: 
"Expires: \(accessibilityDate)")
+                            let a11yDate = TalerDater.accessibilityDate(date) 
?? dateString
+                            let a11yLabel = String(localized: "Expires: 
\(a11yDate)", comment: "VoiceOver")
                             Text("Expires: \(dateString)")
                                 .talerFont(.body)
-                                .accessibilityLabel(accessibilityLabel)
+                                .accessibilityLabel(a11yLabel)
                                 
.foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
                         }
                         // TODO: isSendCoins should show QR only while not yet 
expired  - either set timer or wallet-core should do so and send a 
state-changed notification
diff --git a/TestFlight/WhatToTest.en-US.txt b/TestFlight/WhatToTest.en-US.txt
index 15e49fd..8ae3c01 100644
--- a/TestFlight/WhatToTest.en-US.txt
+++ b/TestFlight/WhatToTest.en-US.txt
@@ -1,3 +1,15 @@
+Version 0.11.0 (wallet-core 0.11.1)
+
+• Spanish localization
+- Pay-Templates overhauled
+
+
+Version 0.10.3 (wallet-core 0.10.8)
+
+- Error handling
+- Bugfixes
+
+
 Version 0.10.2 (wallet-core 0.10.7)
 
 - Improve German localization
diff --git a/taler-swift/Sources/taler-swift/Amount.swift 
b/taler-swift/Sources/taler-swift/Amount.swift
index 9866301..768fd36 100644
--- a/taler-swift/Sources/taler-swift/Amount.swift
+++ b/taler-swift/Sources/taler-swift/Amount.swift
@@ -182,9 +182,10 @@ public final class Amount: Codable, Hashable, @unchecked 
Sendable, CustomStringC
         "\(currency):\(valueStr)"
     }
     
-    /// The string representation of the amount, formatted as 
"`integer`.`fraction` `currency`" (with space).
+    /// The string representation of the amount, formatted as 
"`integer`.`fraction` `currency`" (with non-breaking space).
     public var readableDescription: String {
-        "\(valueStr) \(currency)"
+        let NONBREAKING = "\u{00A0}"
+        return valueStr + NONBREAKING + currency
     }
     
     /// Whether the value is valid. An amount is valid if and only if the 
currency is not empty and the value is less than the maximum allowed value.
diff --git a/taler-swift/Sources/taler-swift/Time.swift 
b/taler-swift/Sources/taler-swift/Time.swift
index 98f0e27..084ad09 100644
--- a/taler-swift/Sources/taler-swift/Time.swift
+++ b/taler-swift/Sources/taler-swift/Time.swift
@@ -16,6 +16,51 @@ enum TimestampError: Error {
     case invalidUInt64Value
 }
 
+public enum RelativeTime: Codable, Hashable, Sendable {
+    // Duration in microseconds or "forever"
+    // to represent an infinite duration. Numeric
+    // values are capped at 2^53 - 1 inclusive.
+    case microseconds(UInt64)
+    case forever
+
+    enum CodingKeys: String, CodingKey {
+        case d_us = "d_us"
+    }
+
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        do {
+            self = RelativeTime.microseconds(try container.decode(UInt64.self, 
forKey: .d_us))
+        } catch {       // rethrows or never
+            let stringValue = try container.decode(String.self, forKey: .d_us)
+            if stringValue == "forever" {
+                self = RelativeTime.forever
+            } else {
+                throw TimestampError.invalidStringRepresentation
+            }
+        }
+    }
+    public func hash(into hasher: inout Hasher) {
+        switch self {
+            case .microseconds(let d_us):
+                hasher.combine(d_us)
+            case .forever:
+                hasher.combine("forever")
+        }
+    }
+    public func encode(to encoder: Encoder) throws {
+        var value = encoder.container(keyedBy: CodingKeys.self)
+        switch self {
+            case .microseconds(let d_us):
+                try value.encode(d_us, forKey: .d_us)
+            case .forever:
+                try value.encode("forever", forKey: .d_us)
+        }
+    }
+}
+
+
+
 /// A point in time, represented by milliseconds from January 1, 1970..
 public enum Timestamp: Codable, Hashable, Sendable {
     case milliseconds(UInt64)

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]