]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
Localize all the things
authorRoopesh Chander <roop@roopc.net>
Tue, 18 Dec 2018 11:00:16 +0000 (16:30 +0530)
committerRoopesh Chander <roop@roopc.net>
Fri, 21 Dec 2018 13:04:09 +0000 (18:34 +0530)
Signed-off-by: Roopesh Chander <roop@roopc.net>
13 files changed:
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/Base.lproj/Localizable.strings [new file with mode: 0644]
WireGuard/WireGuard/LocalizationHelper.swift [new file with mode: 0644]
WireGuard/WireGuard/Tunnel/TunnelErrors.swift
WireGuard/WireGuard/UI/TunnelViewModel.swift
WireGuard/WireGuard/UI/iOS/ViewController/QRScanViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift
WireGuard/WireGuard/ZipArchive/ZipArchive.swift
WireGuard/WireGuard/ZipArchive/ZipExporter.swift
WireGuard/WireGuard/ZipArchive/ZipImporter.swift

index e96b34b6a13e94f4d753c917a29bf01acae1af32..24c59c5c3b5560f7b1a73121eabf09ce8d4d36c6 100644 (file)
@@ -53,6 +53,8 @@
                6FDEF802218646BA00D8FBF6 /* ZipArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF801218646B900D8FBF6 /* ZipArchive.swift */; };
                6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */; };
                6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF8072187442100D8FBF6 /* WgQuickConfigFileWriter.swift */; };
+               6FE1765621C90BBE002690EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6FE1765421C90BBE002690EA /* Localizable.strings */; };
+               6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1765921C90E87002690EA /* LocalizationHelper.swift */; };
                6FE254FB219C10800028284D /* ZipImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FA219C10800028284D /* ZipImporter.swift */; };
                6FE254FF219C60290028284D /* ZipExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FE219C60290028284D /* ZipExporter.swift */; };
                6FF3527021C240160008484E /* ringlogger.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FF3526C21C23F960008484E /* ringlogger.c */; };
                6FDEF801218646B900D8FBF6 /* ZipArchive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZipArchive.swift; sourceTree = "<group>"; };
                6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewController.swift; sourceTree = "<group>"; };
                6FDEF8072187442100D8FBF6 /* WgQuickConfigFileWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WgQuickConfigFileWriter.swift; sourceTree = "<group>"; };
+               6FE1765521C90BBE002690EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = WireGuard/Base.lproj/Localizable.strings; sourceTree = "<group>"; };
+               6FE1765921C90E87002690EA /* LocalizationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationHelper.swift; sourceTree = "<group>"; };
                6FE254FA219C10800028284D /* ZipImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipImporter.swift; sourceTree = "<group>"; };
                6FE254FE219C60290028284D /* ZipExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipExporter.swift; sourceTree = "<group>"; };
                6FF3526B21C23F960008484E /* ringlogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ringlogger.h; sourceTree = "<group>"; };
                6FF4AC0B211EC46F002C96EB = {
                        isa = PBXGroup;
                        children = (
+                               6FE1765421C90BBE002690EA /* Localizable.strings */,
                                6F5D0C432183B4A4000F85AD /* Shared */,
                                6FF4AC16211EC46F002C96EB /* WireGuard */,
                                6F5D0C1B218352EF000F85AD /* WireGuardNetworkExtension */,
                                6FDEF7E72186320E00D8FBF6 /* ZipArchive */,
                                6F61F1E821B932F700483816 /* WireGuardAppError.swift */,
                                6F61F1EA21B937EF00483816 /* WireGuardResult.swift */,
+                               6FE1765921C90E87002690EA /* LocalizationHelper.swift */,
                                6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
                                6FF4AC1E211EC472002C96EB /* Assets.xcassets */,
                                6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */,
                        buildActionMask = 2147483647;
                        files = (
                                6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */,
+                               6FE1765621C90BBE002690EA /* Localizable.strings in Resources */,
                                6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */,
                                6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */,
                                6FF4AC1F211EC472002C96EB /* Assets.xcassets in Resources */,
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
                                6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
                                6FF3527321C2616C0008484E /* Logger.swift in Sources */,
                                6F6899AC218099F00012E523 /* WgQuickConfigFileParser.swift in Sources */,
 /* End PBXTargetDependency section */
 
 /* Begin PBXVariantGroup section */
+               6FE1765421C90BBE002690EA /* Localizable.strings */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               6FE1765521C90BBE002690EA /* Base */,
+                       );
+                       name = Localizable.strings;
+                       sourceTree = "<group>";
+               };
                6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */ = {
                        isa = PBXVariantGroup;
                        children = (
diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings
new file mode 100644 (file)
index 0000000..524cfdc
--- /dev/null
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+// Generic alert action names
+
+"actionOK" = "OK";
+"actionCancel" = "Cancel";
+"actionSave" = "Save";
+
+// Tunnels list UI
+
+"tunnelsListTitle" = "WireGuard";
+"tunnelsListSettingsButtonTitle" = "Settings";
+"tunnelsListCenteredAddTunnelButtonTitle" = "Add a tunnel";
+"tunnelsListSwipeDeleteButtonTitle" = "Delete";
+
+// Tunnels list menu
+
+"addTunnelMenuHeader" = "Add a new WireGuard tunnel";
+"addTunnelMenuImportFile" = "Create from file or archive";
+"addTunnelMenuQRCode" = "Create from QR code";
+"addTunnelMenuFromScratch" = "Create from scratch";
+
+// Tunnels list alerts
+
+"alertImportedFromZipTitle (%d)" = "Created %d tunnels";
+"alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive";
+
+"alertUnableToImportTitle" = "Unable to import tunnel";
+"alertUnableToImportMessage" = "An error occured when importing the tunnel configuration.";
+
+// Tunnel detail and edit UI
+
+"newTunnelViewTitle" = "New configuration";
+"editTunnelViewTitle" = "Edit configuration";
+
+"tunnelSectionTitleStatus" = "Status";
+
+"tunnelStatusInactive" = "Inactive";
+"tunnelStatusActivating" = "Activating";
+"tunnelStatusActive" = "Active";
+"tunnelStatusDeactivating" = "Deactivating";
+"tunnelStatusReasserting" = "Reactivating";
+"tunnelStatusRestarting" = "Restarting";
+"tunnelStatusWaiting" = "Waiting";
+
+"tunnelSectionTitleInterface" = "Interface";
+
+"tunnelInterfaceName" = "Name";
+"tunnelInterfacePrivateKey" = "Private key";
+"tunnelInterfacePublicKey" = "Public key";
+"tunnelInterfaceGenerateKeypair" = "Generate keypair";
+"tunnelInterfaceAddresses" = "Addresses";
+"tunnelInterfaceListenPort" = "Listen port";
+"tunnelInterfaceMTU" = "MTU";
+"tunnelInterfaceDNS" = "DNS servers";
+
+"tunnelSectionTitlePeer" = "Peer";
+
+"tunnelPeerPublicKey" = "Public key";
+"tunnelPeerPreSharedKey" = "Preshared key";
+"tunnelPeerEndpoint" = "Endpoint";
+"tunnelPeerPersistentKeepalive" = "Persistent keepalive";
+"tunnelPeerAllowedIPs" = "Allowed IPs";
+"tunnelPeerExcludePrivateIPs" = "Exclude private IPs";
+
+"tunnelSectionTitleOnDemand" = "On-Demand Activation";
+
+"tunnelOnDemandKey" = "Activate on demand";
+"tunnelOnDemandOptionOff" = "Off";
+"tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular";
+"tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only";
+"tunnelOnDemandOptionCellularOnly" = "Cellular only";
+
+"addPeerButtonTitle" = "Add peer";
+
+"deletePeerButtonTitle" = "Delete peer";
+"deletePeerConfirmationAlertButtonTitle" = "Delete";
+"deletePeerConfirmationAlertMessage" = "Delete this peer?";
+
+"deleteTunnelButtonTitle" = "Delete tunnel";
+"deleteTunnelConfirmationAlertButtonTitle" = "Delete";
+"deleteTunnelConfirmationAlertMessage" = "Delete this tunnel?";
+
+"tunnelEditPlaceholderTextRequired" = "Required";
+"tunnelEditPlaceholderTextOptional" = "Optional";
+"tunnelEditPlaceholderTextAutomatic" = "Automatic";
+"tunnelEditPlaceholderTextOff" = "Off";
+
+// Error alerts while creating / editing a tunnel configuration
+
+/* Alert title for error in the interface data */
+"alertInvalidInterfaceTitle" = "Invalid interface";
+
+/* Any one of the following alert messages can go with the above title */
+"alertInvalidInterfaceMessageNameRequired" = "Interface name is required";
+"alertInvalidInterfaceMessagePrivateKeyRequired" = "Interface's private key is required";
+"alertInvalidInterfaceMessagePrivateKeyInvalid" = "Interface's private key must be a 32-byte key in base64 encoding";
+"alertInvalidInterfaceMessageAddressInvalid" = "Interface addresses must be a list of comma-separated IP addresses, optionally in CIDR notation";
+"alertInvalidInterfaceMessageListenPortInvalid" = "Interface's listen port must be between 0 and 65535, or unspecified";
+"alertInvalidInterfaceMessageMTUInvalid" = "Interface's MTU must be between 576 and 65535, or unspecified";
+"alertInvalidInterfaceMessageDNSInvalid" = "Interface's DNS servers must be a list of comma-separated IP addresses";
+
+/* Alert title for error in the peer data */
+"alertInvalidPeerTitle" = "Invalid peer";
+
+/* Any one of the following alert messages can go with the above title */
+"alertInvalidPeerMessagePublicKeyRequired" = "Peer's public key is required";
+"alertInvalidPeerMessagePublicKeyInvalid" = "Peer's public key must be a 32-byte key in base64 encoding";
+"alertInvalidPeerMessagePreSharedKeyInvalid" = "Peer's preshared key must be a 32-byte key in base64 encoding";
+"alertInvalidPeerMessageAllowedIPsInvalid" = "Peer's allowed IPs must be a list of comma-separated IP addresses, optionally in CIDR notation";
+"alertInvalidPeerMessageEndpointInvalid" = "Peer's endpoint must be of the form 'host:port' or '[host]:port'";
+"alertInvalidPeerMessagePersistentKeepaliveInvalid" = "Peer's persistent keepalive must be between 0 to 65535, or unspecified";
+"alertInvalidPeerMessagePublicKeyDuplicated" = "Two or more peers cannot have the same public key";
+
+// Scanning QR code UI
+
+"scanQRCodeViewTitle" = "Scan QR code";
+"scanQRCodeTipText" = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`";
+
+// Scanning QR code alerts
+
+"alertScanQRCodeCameraUnsupportedTitle" = "Camera Unsupported";
+"alertScanQRCodeCameraUnsupportedMessage" = "This device is not able to scan QR codes";
+
+"alertScanQRCodeInvalidQRCodeTitle" = "Invalid QR Code";
+"alertScanQRCodeInvalidQRCodeMessage" = "The scanned QR code is not a valid WireGuard configuration";
+
+"alertScanQRCodeUnreadableQRCodeTitle" = "Invalid Code";
+"alertScanQRCodeUnreadableQRCodeMessage" = "The scanned code could not be read";
+
+"alertScanQRCodeNamePromptTitle" = "Please name the scanned tunnel";
+
+// Settings UI
+
+"settingsViewTitle" = "Settings";
+
+"settingsSectionTitleAbout" = "About";
+"settingsVersionKeyWireGuardForIOS" = "WireGuard for iOS";
+"settingsVersionKeyWireGuardGoBackend" = "WireGuard Go Backend";
+
+"settingsSectionTitleExportConfigurations" = "Export configurations";
+"settingsExportZipButtonTitle" = "Export zip archive";
+
+"settingsSectionTitleTunnelLog" = "Tunnel log";
+"settingsExportLogFileButtonTitle" = "Export log file";
+
+// Settings alerts
+
+"alertUnableToRemovePreviousLogTitle" = "Log export failed";
+"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
+
+"alertUnableToFindExtensionLogPathTitle" = "Log export failed";
+"alertUnableToFindExtensionLogPathMessage" = "Unable to determine extension log path";
+
+"alertUnableToWriteLogTitle" = "Log export failed";
+"alertUnableToWriteLogMessage" = "Unable to write logs to file";
+
+// Zip import / export error alerts
+
+"alertCantOpenInputZipFileTitle" = "Unable to read zip archive";
+"alertCantOpenInputZipFileMessage" = "The zip archive could not be read.";
+
+"alertCantOpenOutputZipFileForWritingTitle" = "Unable to create zip archive";
+"alertCantOpenOutputZipFileForWritingMessage" = "Could not open zip file for writing.";
+
+"alertBadArchiveTitle" = "Unable to read zip archive";
+"alertBadArchiveMessage" = "Bad or corrupt zip archive.";
+
+"alertNoTunnelsToExportTitle" = "Nothing to export";
+"alertNoTunnelsToExportMessage" = "There are no tunnels to export";
+
+"alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in zip archive";
+"alertNoTunnelsInImportedZipArchiveMessage" = "No .conf tunnel files were found inside the zip archive.";
+
+// Tunnel management error alerts
+
+"alertTunnelActivationFailureTitle" = "Activation failure";
+"alertTunnelActivationFailureMessage" = "The tunnel could not be activated. Please ensure that you are connected to the Internet.";
+
+"alertTunnelNameEmptyTitle" = "No name provided";
+"alertTunnelNameEmptyMessage" = "Cannot create tunnel with an empty name";
+
+"alertTunnelAlreadyExistsWithThatNameTitle" = "Name already exists";
+"alertTunnelAlreadyExistsWithThatNameMessage" = "A tunnel with that name already exists";
+
+"alertTunnelActivationErrorTunnelIsNotInactiveTitle" = "Activation failure";
+"alertTunnelActivationErrorTunnelIsNotInactiveMessage" = "The tunnel is already active or in the process of being activated";
+
+// Tunnel management error alerts on system error
+
+/* The alert message that goes with the following titles would be
+   one of the alertSystemErrorMessage* listed further down */
+"alertSystemErrorOnListingTunnelsTitle" = "Unable to list tunnels";
+"alertSystemErrorOnAddTunnelTitle" = "Unable to create tunnel";
+"alertSystemErrorOnModifyTunnelTitle" = "Unable to modify tunnel";
+"alertSystemErrorOnRemoveTunnelTitle" = "Unable to remove tunnel";
+
+/* The alert message for this alert shall include
+ one of the alertSystemErrorMessage* listed further down */
+"alertTunnelActivationSystemErrorTitle" = "Activation failure";
+"alertTunnelActivationSystemErrorMessage (%@)" = "The tunnel could not be activated. %@";
+
+/* alertSystemErrorMessage* messages */
+"alertSystemErrorMessageTunnelConfigurationInvalid" = "The configuration is invalid.";
+"alertSystemErrorMessageTunnelConfigurationDisabled" = "The configuration is disabled.";
+"alertSystemErrorMessageTunnelConnectionFailed" = "The connection failed.";
+"alertSystemErrorMessageTunnelConfigurationStale" = "The configuration is stale.";
+"alertSystemErrorMessageTunnelConfigurationReadWriteFailed" = "Reading or writing the configuration failed.";
+"alertSystemErrorMessageTunnelConfigurationUnknown" = "Unknown system error.";
diff --git a/WireGuard/WireGuard/LocalizationHelper.swift b/WireGuard/WireGuard/LocalizationHelper.swift
new file mode 100644 (file)
index 0000000..ea4cc64
--- /dev/null
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+func tr(_ key: String) -> String {
+    return NSLocalizedString(key, comment: "")
+}
+
+func tr(format: String, _ arguments: CVarArg...) -> String {
+    return String(format: NSLocalizedString(format, comment: ""), arguments: arguments)
+}
index 45c20f6eea1972c9e1565d5bcb3d74af5cbc8ba8..c3d15b2bacdbc209ae84b40bb97df212c9b3e5d9 100644 (file)
@@ -14,17 +14,17 @@ enum TunnelsManagerError: WireGuardAppError {
     var alertText: AlertText {
         switch self {
         case .tunnelNameEmpty:
-            return ("No name provided", "Cannot create tunnel with an empty name")
+            return (tr("alertTunnelNameEmptyTitle"), tr("alertTunnelNameEmptyMessage"))
         case .tunnelAlreadyExistsWithThatName:
-            return ("Name already exists", "A tunnel with that name already exists")
+            return (tr("alertTunnelAlreadyExistsWithThatNameTitle"), tr("alertTunnelAlreadyExistsWithThatNameMessage"))
         case .systemErrorOnListingTunnels(let systemError):
-            return ("Unable to list tunnels", systemError.UIString)
+            return (tr("alertSystemErrorOnListingTunnelsTitle"), systemError.localizedUIString)
         case .systemErrorOnAddTunnel(let systemError):
-            return ("Unable to create tunnel", systemError.UIString)
+            return (tr("alertSystemErrorOnAddTunnelTitle"), systemError.localizedUIString)
         case .systemErrorOnModifyTunnel(let systemError):
-            return ("Unable to modify tunnel", systemError.UIString)
+            return (tr("alertSystemErrorOnModifyTunnelTitle"), systemError.localizedUIString)
         case .systemErrorOnRemoveTunnel(let systemError):
-            return ("Unable to remove tunnel", systemError.UIString)
+            return (tr("alertSystemErrorOnRemoveTunnelTitle"), systemError.localizedUIString)
         }
     }
 }
@@ -39,12 +39,13 @@ enum TunnelsManagerActivationAttemptError: WireGuardAppError {
     var alertText: AlertText {
         switch self {
         case .tunnelIsNotInactive:
-            return ("Activation failure", "The tunnel is already active or in the process of being activated")
+            return (tr("alertTunnelActivationErrorTunnelIsNotInactiveTitle"), tr("alertTunnelActivationErrorTunnelIsNotInactiveMessage"))
         case .failedWhileStarting(let systemError),
              .failedWhileSaving(let systemError),
              .failedWhileLoading(let systemError),
              .failedBecauseOfTooManyErrors(let systemError):
-            return ("Activation failure", "The tunnel could not be activated. " + systemError.UIString)
+            return (tr("alertTunnelActivationSystemErrorTitle"),
+                    tr(format: "alertTunnelActivationSystemErrorMessage (%@)", systemError.localizedUIString))
         }
     }
 }
@@ -56,7 +57,7 @@ enum TunnelsManagerActivationError: WireGuardAppError {
     var alertText: AlertText {
         switch self {
         case .activationFailed:
-            return ("Activation failure", "The tunnel could not be activated. Please ensure that you are connected to the Internet.")
+            return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage"))
         case .activationFailedWithExtensionError(let title, let message):
             return (title, message)
         }
@@ -64,21 +65,21 @@ enum TunnelsManagerActivationError: WireGuardAppError {
 }
 
 extension Error {
-    var UIString: String {
+    var localizedUIString: String {
         if let systemError = self as? NEVPNError {
             switch systemError {
             case NEVPNError.configurationInvalid:
-                return "The configuration is invalid."
+                return tr("alertSystemErrorMessageTunnelConfigurationInvalid")
             case NEVPNError.configurationDisabled:
-                return "The configuration is disabled."
+                return tr("alertSystemErrorMessageTunnelConfigurationDisabled")
             case NEVPNError.connectionFailed:
-                return "The connection failed."
+                return tr("alertSystemErrorMessageTunnelConnectionFailed")
             case NEVPNError.configurationStale:
-                return "The configuration is stale."
+                return tr("alertSystemErrorMessageTunnelConfigurationStale")
             case NEVPNError.configurationReadWriteFailed:
-                return "Reading or writing the configuration failed."
+                return tr("alertSystemErrorMessageTunnelConfigurationReadWriteFailed")
             case NEVPNError.configurationUnknown:
-                return "Unknown system error."
+                return tr("alertSystemErrorMessageTunnelConfigurationUnknown")
             default:
                 return ""
             }
index 71cb18e287470cd1142fd2777b559b21a79b0ab6..f7ebb686bb839afcd2a5679d565ec5abfa86d31d 100644 (file)
@@ -6,29 +6,54 @@ import UIKit
 //swiftlint:disable:next type_body_length
 class TunnelViewModel {
 
-    enum InterfaceField: String {
-        case name = "Name"
-        case privateKey = "Private key"
-        case publicKey = "Public key"
-        case generateKeyPair = "Generate keypair"
-        case addresses = "Addresses"
-        case listenPort = "Listen port"
-        case mtu = "MTU"
-        case dns = "DNS servers"
+    enum InterfaceField {
+        case name
+        case privateKey
+        case publicKey
+        case generateKeyPair
+        case addresses
+        case listenPort
+        case mtu
+        case dns
+
+        var localizedUIString: String {
+            switch self {
+            case .name: return tr("tunnelInterfaceName")
+            case .privateKey: return tr("tunnelInterfacePrivateKey")
+            case .publicKey: return tr("tunnelInterfacePublicKey")
+            case .generateKeyPair: return tr("tunnelInterfaceGenerateKeypair")
+            case .addresses: return tr("tunnelInterfaceAddresses")
+            case .listenPort: return tr("tunnelInterfaceListenPort")
+            case .mtu: return tr("tunnelInterfaceMTU")
+            case .dns: return tr("tunnelInterfaceDNS")
+            }
+        }
     }
 
     static let interfaceFieldsWithControl: Set<InterfaceField> = [
         .generateKeyPair
     ]
 
-    enum PeerField: String {
-        case publicKey = "Public key"
-        case preSharedKey = "Preshared key"
-        case endpoint = "Endpoint"
-        case persistentKeepAlive = "Persistent keepalive"
-        case allowedIPs = "Allowed IPs"
-        case excludePrivateIPs = "Exclude private IPs"
-        case deletePeer = "Delete peer"
+    enum PeerField {
+        case publicKey
+        case preSharedKey
+        case endpoint
+        case persistentKeepAlive
+        case allowedIPs
+        case excludePrivateIPs
+        case deletePeer
+
+        var localizedUIString: String {
+            switch self {
+            case .publicKey: return tr("tunnelPeerPublicKey")
+            case .preSharedKey: return tr("tunnelPeerPreSharedKey")
+            case .endpoint: return tr("tunnelPeerEndpoint")
+            case .persistentKeepAlive: return tr("tunnelPeerPersistentKeepalive")
+            case .allowedIPs: return tr("tunnelPeerAllowedIPs")
+            case .excludePrivateIPs: return tr("tunnelPeerExcludePrivateIPs")
+            case .deletePeer: return tr("deletePeerButtonTitle")
+            }
+        }
     }
 
     static let peerFieldsWithControl: Set<PeerField> = [
@@ -103,15 +128,15 @@ class TunnelViewModel {
             fieldsWithError.removeAll()
             guard let name = scratchpad[.name]?.trimmingCharacters(in: .whitespacesAndNewlines), (!name.isEmpty) else {
                 fieldsWithError.insert(.name)
-                return .error("Interface name is required")
+                return .error(tr("alertInvalidInterfaceMessageNameRequired"))
             }
             guard let privateKeyString = scratchpad[.privateKey] else {
                 fieldsWithError.insert(.privateKey)
-                return .error("Interface's private key is required")
+                return .error(tr("alertInvalidInterfaceMessagePrivateKeyRequired"))
             }
             guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else {
                 fieldsWithError.insert(.privateKey)
-                return .error("Interface's private key must be a 32-byte key in base64 encoding")
+                return .error(tr("alertInvalidInterfaceMessagePrivateKeyInvalid"))
             }
             var config = InterfaceConfiguration(name: name, privateKey: privateKey)
             var errorMessages = [String]()
@@ -122,7 +147,7 @@ class TunnelViewModel {
                         addresses.append(address)
                     } else {
                         fieldsWithError.insert(.addresses)
-                        errorMessages.append("Interface addresses must be a list of comma-separated IP addresses, optionally in CIDR notation")
+                        errorMessages.append(tr("alertInvalidInterfaceMessageAddressInvalid"))
                     }
                 }
                 config.addresses = addresses
@@ -132,7 +157,7 @@ class TunnelViewModel {
                     config.listenPort = listenPort
                 } else {
                     fieldsWithError.insert(.listenPort)
-                    errorMessages.append("Interface's listen port must be between 0 and 65535, or unspecified")
+                    errorMessages.append(tr("alertInvalidInterfaceMessageListenPortInvalid"))
                 }
             }
             if let mtuString = scratchpad[.mtu] {
@@ -140,7 +165,7 @@ class TunnelViewModel {
                     config.mtu = mtu
                 } else {
                     fieldsWithError.insert(.mtu)
-                    errorMessages.append("Interface's MTU must be between 576 and 65535, or unspecified")
+                    errorMessages.append(tr("alertInvalidInterfaceMessageMTUInvalid"))
                 }
             }
             if let dnsString = scratchpad[.dns] {
@@ -150,7 +175,7 @@ class TunnelViewModel {
                         dnsServers.append(dnsServer)
                     } else {
                         fieldsWithError.insert(.dns)
-                        errorMessages.append("Interface's DNS servers must be a list of comma-separated IP addresses")
+                        errorMessages.append(tr("alertInvalidInterfaceMessageDNSInvalid"))
                     }
                 }
                 config.dns = dnsServers
@@ -243,11 +268,11 @@ class TunnelViewModel {
             fieldsWithError.removeAll()
             guard let publicKeyString = scratchpad[.publicKey] else {
                 fieldsWithError.insert(.publicKey)
-                return .error("Peer's public key is required")
+                return .error(tr("alertInvalidPeerMessagePublicKeyRequired"))
             }
             guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else {
                 fieldsWithError.insert(.publicKey)
-                return .error("Peer's public key must be a 32-byte key in base64 encoding")
+                return .error(tr("alertInvalidPeerMessagePublicKeyInvalid"))
             }
             var config = PeerConfiguration(publicKey: publicKey)
             var errorMessages = [String]()
@@ -256,7 +281,7 @@ class TunnelViewModel {
                     config.preSharedKey = preSharedKey
                 } else {
                     fieldsWithError.insert(.preSharedKey)
-                    errorMessages.append("Peer's preshared key must be a 32-byte key in base64 encoding")
+                    errorMessages.append(tr("alertInvalidPeerMessagePreSharedKeyInvalid"))
                 }
             }
             if let allowedIPsString = scratchpad[.allowedIPs] {
@@ -266,7 +291,7 @@ class TunnelViewModel {
                         allowedIPs.append(allowedIP)
                     } else {
                         fieldsWithError.insert(.allowedIPs)
-                        errorMessages.append("Peer's allowed IPs must be a list of comma-separated IP addresses, optionally in CIDR notation")
+                        errorMessages.append(tr("alertInvalidPeerMessageAllowedIPsInvalid"))
                     }
                 }
                 config.allowedIPs = allowedIPs
@@ -276,7 +301,7 @@ class TunnelViewModel {
                     config.endpoint = endpoint
                 } else {
                     fieldsWithError.insert(.endpoint)
-                    errorMessages.append("Peer's endpoint must be of the form 'host:port' or '[host]:port'")
+                    errorMessages.append(tr("alertInvalidPeerMessageEndpointInvalid"))
                 }
             }
             if let persistentKeepAliveString = scratchpad[.persistentKeepAlive] {
@@ -284,7 +309,7 @@ class TunnelViewModel {
                     config.persistentKeepAlive = persistentKeepAlive
                 } else {
                     fieldsWithError.insert(.persistentKeepAlive)
-                    errorMessages.append("Peer's persistent keepalive must be between 0 to 65535, or unspecified")
+                    errorMessages.append(tr("alertInvalidPeerMessagePersistentKeepaliveInvalid"))
                 }
             }
 
@@ -354,7 +379,7 @@ class TunnelViewModel {
 
     enum SaveResult<Configuration> {
         case saved(Configuration)
-        case error(String) // TODO: Localize error messages
+        case error(String)
     }
 
     var interfaceData: InterfaceData
@@ -425,7 +450,7 @@ class TunnelViewModel {
             let peerPublicKeysArray = peerConfigurations.map { $0.publicKey }
             let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
             if peerPublicKeysArray.count != peerPublicKeysSet.count {
-                return .error("Two or more peers cannot have the same public key")
+                return .error(tr("alertInvalidPeerMessagePublicKeyDuplicated"))
             }
 
             let tunnelConfiguration = TunnelConfiguration(interface: interfaceConfiguration, peers: peerConfigurations)
@@ -440,13 +465,13 @@ extension TunnelViewModel {
     static func activateOnDemandOptionText(for activateOnDemandOption: ActivateOnDemandOption) -> String {
         switch activateOnDemandOption {
         case .none:
-            return "Off"
+            return tr("tunnelOnDemandOptionOff")
         case .useOnDemandOverWiFiOrCellular:
-            return "Wi-Fi or cellular"
+            return tr("tunnelOnDemandOptionWiFiOrCellular")
         case .useOnDemandOverWiFiOnly:
-            return "Wi-Fi only"
+            return tr("tunnelOnDemandOptionWiFiOnly")
         case .useOnDemandOverCellularOnly:
-            return "Cellular only"
+            return tr("tunnelOnDemandOptionCellularOnly")
         }
     }
 
index e4b62879f70e126a94dd00ec67fd3fcaed93dd01..1fd6905bcdf97bfd39fa1cd8e20f538191018669 100644 (file)
@@ -17,11 +17,11 @@ class QRScanViewController: UIViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        title = "Scan QR code"
+        title = tr("scanQRCodeViewTitle")
         navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
 
         let tipLabel = UILabel()
-        tipLabel.text = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`"
+        tipLabel.text = tr("scanQRCodeTipText")
         tipLabel.adjustsFontSizeToFitWidth = true
         tipLabel.textColor = .lightGray
         tipLabel.textAlignment = .center
@@ -39,7 +39,7 @@ class QRScanViewController: UIViewController {
             let captureSession = captureSession,
             captureSession.canAddInput(videoInput),
             captureSession.canAddOutput(metadataOutput) else {
-                scanDidEncounterError(title: "Camera Unsupported", message: "This device is not able to scan QR codes")
+                scanDidEncounterError(title: tr("alertScanQRCodeCameraUnsupportedTitle"), message: tr("alertScanQRCodeCameraUnsupportedMessage"))
                 return
         }
 
@@ -103,16 +103,16 @@ class QRScanViewController: UIViewController {
     func scanDidComplete(withCode code: String) {
         let scannedTunnelConfiguration = try? WgQuickConfigFileParser.parse(code, name: "Scanned")
         guard let tunnelConfiguration = scannedTunnelConfiguration else {
-            scanDidEncounterError(title: "Invalid QR Code", message: "The scanned QR code is not a valid WireGuard configuration")
+            scanDidEncounterError(title: tr("alertScanQRCodeInvalidQRCodeTitle"), message: tr("alertScanQRCodeInvalidQRCodeMessage"))
             return
         }
 
-        let alert = UIAlertController(title: NSLocalizedString("Please name the scanned tunnel", comment: ""), message: nil, preferredStyle: .alert)
+        let alert = UIAlertController(title: tr("alertScanQRCodeNamePromptTitle"), message: nil, preferredStyle: .alert)
         alert.addTextField(configurationHandler: nil)
-        alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { [weak self] _ in
+        alert.addAction(UIAlertAction(title: tr("actionCancel"), style: .cancel) { [weak self] _ in
             self?.dismiss(animated: true, completion: nil)
         })
-        alert.addAction(UIAlertAction(title: NSLocalizedString("Save", comment: ""), style: .default) { [weak self] _ in
+        alert.addAction(UIAlertAction(title: tr("actionSave"), style: .default) { [weak self] _ in
             guard let title = alert.textFields?[0].text?.trimmingCharacters(in: .whitespacesAndNewlines), !title.isEmpty else { return }
             tunnelConfiguration.interface.name = title
             if let self = self {
@@ -126,7 +126,7 @@ class QRScanViewController: UIViewController {
 
     func scanDidEncounterError(title: String, message: String) {
         let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
-        alertController.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in
+        alertController.addAction(UIAlertAction(title: tr("actionOK"), style: .default) { [weak self] _ in
             self?.dismiss(animated: true, completion: nil)
         })
         present(alertController, animated: true)
@@ -145,7 +145,7 @@ extension QRScanViewController: AVCaptureMetadataOutputObjectsDelegate {
         guard let metadataObject = metadataObjects.first,
             let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
             let stringValue = readableObject.stringValue else {
-                scanDidEncounterError(title: "Invalid Code", message: "The scanned code could not be read")
+                scanDidEncounterError(title: tr("alertScanQRCodeUnreadableQRCodeTitle"), message: tr("alertScanQRCodeUnreadableQRCodeMessage"))
                 return
         }
 
index 65ad2fe565c7e073396986ef1a8c701068cf3ad6..22edcbc64a44e14e5000c9249a6a1f46566024ad 100644 (file)
@@ -6,11 +6,20 @@ import os.log
 
 class SettingsTableViewController: UITableViewController {
 
-    enum SettingsFields: String {
-        case iosAppVersion = "WireGuard for iOS"
-        case goBackendVersion = "WireGuard Go Backend"
-        case exportZipArchive = "Export zip archive"
-        case exportLogFile = "Export log file"
+    enum SettingsFields {
+        case iosAppVersion
+        case goBackendVersion
+        case exportZipArchive
+        case exportLogFile
+
+        var localizedUIString: String {
+            switch self {
+            case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
+            case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
+            case .exportZipArchive: return tr("settingsExportZipButtonTitle")
+            case .exportLogFile: return tr("settingsExportLogFileButtonTitle")
+            }
+        }
     }
 
     let settingsFieldsBySection: [[SettingsFields]] = [
@@ -33,7 +42,7 @@ class SettingsTableViewController: UITableViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
-        title = "Settings"
+        title = tr("settingsViewTitle")
         navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped))
 
         tableView.estimatedRowHeight = 44
@@ -109,19 +118,19 @@ class SettingsTableViewController: UITableViewController {
             if FileManager.default.fileExists(atPath: destinationURL.path) {
                 let isDeleted = FileManager.deleteFile(at: destinationURL)
                 if !isDeleted {
-                    ErrorPresenter.showErrorAlert(title: "Log export failed", message: "The pre-existing log could not be cleared", from: self)
+                    ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
                     return
                 }
             }
 
             guard let networkExtensionLogFilePath = FileManager.networkExtensionLogFileURL?.path else {
-                ErrorPresenter.showErrorAlert(title: "Log export failed", message: "Unable to determine extension log path", from: self)
+                ErrorPresenter.showErrorAlert(title: tr("alertUnableToFindExtensionLogPathTitle"), message: tr("alertUnableToFindExtensionLogPathMessage"), from: self)
                 return
             }
 
             let isWritten = Logger.global?.writeLog(called: "APP", mergedWith: networkExtensionLogFilePath, called: "NET", to: destinationURL.path) ?? false
             guard isWritten else {
-                ErrorPresenter.showErrorAlert(title: "Log export failed", message: "Unable to write logs to file", from: self)
+                ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
                 return
             }
 
@@ -153,11 +162,11 @@ extension SettingsTableViewController {
     override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
         switch section {
         case 0:
-            return "About"
+            return tr("settingsSectionTitleAbout")
         case 1:
-            return "Export configurations"
+            return tr("settingsSectionTitleExportConfigurations")
         case 2:
-            return "Tunnel log"
+            return tr("settingsSectionTitleTunnelLog")
         default:
             return nil
         }
@@ -168,7 +177,7 @@ extension SettingsTableViewController {
         if field == .iosAppVersion || field == .goBackendVersion {
             let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
             cell.copyableGesture = false
-            cell.key = field.rawValue
+            cell.key = field.localizedUIString
             if field == .iosAppVersion {
                 var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
                 if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
@@ -181,7 +190,7 @@ extension SettingsTableViewController {
             return cell
         } else if field == .exportZipArchive {
             let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
-            cell.buttonText = field.rawValue
+            cell.buttonText = field.localizedUIString
             cell.onTapped = { [weak self] in
                 self?.exportConfigurationsAsZipFile(sourceView: cell.button)
             }
@@ -189,7 +198,7 @@ extension SettingsTableViewController {
         } else {
             assert(field == .exportLogFile)
             let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
-            cell.buttonText = field.rawValue
+            cell.buttonText = field.localizedUIString
             cell.onTapped = { [weak self] in
                 self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
             }
index d3f9c8433e33f5c2195582a2b0db0724eab91292..50c0e33218b717192c4e005d07a3a56a3f13a73b 100644 (file)
@@ -85,7 +85,7 @@ class TunnelDetailTableViewController: UITableViewController {
         let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
             onConfirmed()
         }
-        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
+        let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
         let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
         alert.addAction(destroyAction)
         alert.addAction(cancelAction)
@@ -137,13 +137,13 @@ extension TunnelDetailTableViewController {
     override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
         switch sections[section] {
         case .status:
-            return "Status"
+            return tr("tunnelSectionTitleStatus")
         case .interface:
-            return "Interface"
+            return tr("tunnelSectionTitleInterface")
         case .peer:
-            return "Peer"
+            return tr("tunnelSectionTitlePeer")
         case .onDemand:
-            return "On-Demand Activation"
+            return tr("tunnelSectionTitleOnDemand")
         case .delete:
             return nil
         }
@@ -171,19 +171,19 @@ extension TunnelDetailTableViewController {
             let text: String
             switch status {
             case .inactive:
-                text = "Inactive"
+                text = tr("tunnelStatusInactive")
             case .activating:
-                text = "Activating"
+                text = tr("tunnelStatusActivating")
             case .active:
-                text = "Active"
+                text = tr("tunnelStatusActive")
             case .deactivating:
-                text = "Deactivating"
+                text = tr("tunnelStatusDeactivating")
             case .reasserting:
-                text = "Reactivating"
+                text = tr("tunnelStatusReasserting")
             case .restarting:
-                text = "Restarting"
+                text = tr("tunnelStatusRestarting")
             case .waiting:
-                text = "Waiting"
+                text = tr("tunnelStatusWaiting")
             }
             cell.textLabel?.text = text
             DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak cell] in
@@ -213,7 +213,7 @@ extension TunnelDetailTableViewController {
     private func interfaceCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         let field = tunnelViewModel.interfaceData.filterFieldsWithValueOrControl(interfaceFields: interfaceFields)[indexPath.row]
         let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.key = field.rawValue
+        cell.key = field.localizedUIString
         cell.value = tunnelViewModel.interfaceData[field]
         return cell
     }
@@ -221,14 +221,14 @@ extension TunnelDetailTableViewController {
     private func peerCell(for tableView: UITableView, at indexPath: IndexPath, with peerData: TunnelViewModel.PeerData) -> UITableViewCell {
         let field = peerData.filterFieldsWithValueOrControl(peerFields: peerFields)[indexPath.row]
         let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.key = field.rawValue
+        cell.key = field.localizedUIString
         cell.value = peerData[field]
         return cell
     }
 
     private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.key = "Activate on demand"
+        cell.key = tr("tunnelOnDemandKey")
         cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting())
         onDemandStatusObservationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in
             cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting())
@@ -238,11 +238,11 @@ extension TunnelDetailTableViewController {
 
     private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.buttonText = "Delete tunnel"
+        cell.buttonText = tr("deleteTunnelButtonTitle")
         cell.hasDestructiveAction = true
         cell.onTapped = { [weak self] in
             guard let self = self else { return }
-            self.showConfirmationAlert(message: "Delete this tunnel?", buttonTitle: "Delete", from: cell) { [weak self] in
+            self.showConfirmationAlert(message: tr("deleteTunnelConfirmationAlertMessage"), buttonTitle: tr("deleteTunnelConfirmationAlertButtonTitle"), from: cell) { [weak self] in
                 guard let self = self else { return }
                 self.tunnelsManager.remove(tunnel: self.tunnel) { error in
                     if error != nil {
index 4aa1180c08c7b3ec6591f6b2a12cf3e2f1c0fc9f..3d9724c19db955d9b93eb75d71c486b75473e468 100644 (file)
@@ -71,7 +71,7 @@ class TunnelEditTableViewController: UITableViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
-        title = tunnel == nil ? "New configuration" : "Edit configuration"
+        title = tunnel == nil ? tr("newTunnelViewTitle") : tr("editTunnelViewTitle")
         navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped))
         navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
 
@@ -98,8 +98,9 @@ class TunnelEditTableViewController: UITableViewController {
         let tunnelSaveResult = tunnelViewModel.save()
         switch tunnelSaveResult {
         case .error(let errorMessage):
-            let erroringConfiguration = (tunnelViewModel.interfaceData.validatedConfiguration == nil) ? "Interface" : "Peer"
-            ErrorPresenter.showErrorAlert(title: "Invalid \(erroringConfiguration)", message: errorMessage, from: self)
+            let alertTitle = (tunnelViewModel.interfaceData.validatedConfiguration == nil) ?
+                tr("alertInvalidInterfaceTitle") : tr("alertInvalidPeerTitle")
+            ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self)
             tableView.reloadData() // Highlight erroring fields
         case .saved(let tunnelConfiguration):
             if let tunnel = tunnel {
@@ -164,13 +165,13 @@ extension TunnelEditTableViewController {
     override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
         switch sections[section] {
         case .interface:
-            return section == 0 ? "Interface" : nil
+            return section == 0 ? tr("tunnelSectionTitleInterface") : nil
         case .peer:
-            return "Peer"
+            return tr("tunnelSectionTitlePeer")
         case .addPeer:
             return nil
         case .onDemand:
-            return "On-Demand Activation"
+            return tr("tunnelSectionTitleOnDemand")
         }
     }
 
@@ -201,7 +202,7 @@ extension TunnelEditTableViewController {
 
     private func generateKeyPairCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
         let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.buttonText = field.rawValue
+        cell.buttonText = field.localizedUIString
         cell.onTapped = { [weak self] in
             guard let self = self else { return }
 
@@ -218,24 +219,24 @@ extension TunnelEditTableViewController {
 
     private func publicKeyCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
         let cell: TunnelEditKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.key = field.rawValue
+        cell.key = field.localizedUIString
         cell.value = tunnelViewModel.interfaceData[field]
         return cell
     }
 
     private func interfaceFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, with field: TunnelViewModel.InterfaceField) -> UITableViewCell {
         let cell: TunnelEditEditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.key = field.rawValue
+        cell.key = field.localizedUIString
 
         switch field {
         case .name, .privateKey:
-            cell.placeholderText = "Required"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextRequired")
             cell.keyboardType = .default
         case .addresses, .dns:
-            cell.placeholderText = "Optional"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextOptional")
             cell.keyboardType = .numbersAndPunctuation
         case .listenPort, .mtu:
-            cell.placeholderText = "Automatic"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextAutomatic")
             cell.keyboardType = .numberPad
         case .publicKey, .generateKeyPair:
             cell.keyboardType = .default
@@ -283,12 +284,12 @@ extension TunnelEditTableViewController {
 
     private func deletePeerCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
         let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.buttonText = field.rawValue
+        cell.buttonText = field.localizedUIString
         cell.hasDestructiveAction = true
         cell.onTapped = { [weak self, weak peerData] in
             guard let peerData = peerData else { return }
             guard let self = self else { return }
-            self.showConfirmationAlert(message: "Delete this peer?", buttonTitle: "Delete", from: cell) { [weak self] in
+            self.showConfirmationAlert(message: tr("deletePeerConfirmationAlertMessage"), buttonTitle: tr("deletePeerConfirmationAlertButtonTitle"), from: cell) { [weak self] in
                 guard let self = self else { return }
                 let removedSectionIndices = self.deletePeer(peer: peerData)
                 let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
@@ -309,7 +310,7 @@ extension TunnelEditTableViewController {
 
     private func excludePrivateIPsCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
         let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.message = field.rawValue
+        cell.message = field.localizedUIString
         cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl
         cell.isOn = peerData.excludePrivateIPsValue
         cell.onSwitchToggled = { [weak self] isOn in
@@ -324,20 +325,20 @@ extension TunnelEditTableViewController {
 
     private func peerFieldKeyValueCell(for tableView: UITableView, at indexPath: IndexPath, peerData: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) -> UITableViewCell {
         let cell: TunnelEditEditableKeyValueCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.key = field.rawValue
+        cell.key = field.localizedUIString
 
         switch field {
         case .publicKey:
-            cell.placeholderText = "Required"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextRequired")
             cell.keyboardType = .default
         case .preSharedKey, .endpoint:
-            cell.placeholderText = "Optional"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextOptional")
             cell.keyboardType = .default
         case .allowedIPs:
-            cell.placeholderText = "Optional"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextOptional")
             cell.keyboardType = .numbersAndPunctuation
         case .persistentKeepAlive:
-            cell.placeholderText = "Off"
+            cell.placeholderText = tr("tunnelEditPlaceholderTextOff")
             cell.keyboardType = .numberPad
         case .excludePrivateIPs, .deletePeer:
             cell.keyboardType = .default
@@ -373,7 +374,7 @@ extension TunnelEditTableViewController {
 
     private func addPeerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
-        cell.buttonText = "Add peer"
+        cell.buttonText = tr("addPeerButtonTitle")
         cell.onTapped = { [weak self] in
             guard let self = self else { return }
             let shouldHideExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
@@ -394,7 +395,7 @@ extension TunnelEditTableViewController {
     private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         if indexPath.row == 0 {
             let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
-            cell.message = "Activate on demand"
+            cell.message = tr("tunnelOnDemandKey")
             cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
             cell.onSwitchToggled = { [weak self] isOn in
                 guard let self = self else { return }
@@ -443,7 +444,7 @@ extension TunnelEditTableViewController {
         let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
             onConfirmed()
         }
-        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
+        let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
         let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
         alert.addAction(destroyAction)
         alert.addAction(cancelAction)
index 9dea8b0d4cc1cad607d54fe0df9e0f1de7e82824..fff976f949939fe189f0c6dd9ae91e585a730c36 100644 (file)
@@ -20,7 +20,7 @@ class TunnelsListTableViewController: UIViewController {
     
     let centeredAddButton: BorderedTextButton = {
         let button = BorderedTextButton()
-        button.title = "Add a tunnel"
+        button.title = tr("tunnelsListCenteredAddTunnelButtonTitle")
         button.isHidden = true
         return button
     }()
@@ -72,9 +72,9 @@ class TunnelsListTableViewController: UIViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        title = "WireGuard"
+        title = tr("tunnelsListTitle")
         navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
-        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Settings", style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
+        navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSettingsButtonTitle"), style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
 
         restorationIdentifier = "TunnelsListVC"
     }
@@ -97,25 +97,25 @@ class TunnelsListTableViewController: UIViewController {
     @objc func addButtonTapped(sender: AnyObject) {
         guard tunnelsManager != nil else { return }
         
-        let alert = UIAlertController(title: "", message: "Add a new WireGuard tunnel", preferredStyle: .actionSheet)
-        let importFileAction = UIAlertAction(title: "Create from file or archive", style: .default) { [weak self] _ in
+        let alert = UIAlertController(title: "", message: tr("addTunnelMenuHeader"), preferredStyle: .actionSheet)
+        let importFileAction = UIAlertAction(title: tr("addTunnelMenuImportFile"), style: .default) { [weak self] _ in
             self?.presentViewControllerForFileImport()
         }
         alert.addAction(importFileAction)
 
-        let scanQRCodeAction = UIAlertAction(title: "Create from QR code", style: .default) { [weak self] _ in
+        let scanQRCodeAction = UIAlertAction(title: tr("addTunnelMenuQRCode"), style: .default) { [weak self] _ in
             self?.presentViewControllerForScanningQRCode()
         }
         alert.addAction(scanQRCodeAction)
 
-        let createFromScratchAction = UIAlertAction(title: "Create from scratch", style: .default) { [weak self] _ in
+        let createFromScratchAction = UIAlertAction(title: tr("addTunnelMenuFromScratch"), style: .default) { [weak self] _ in
             if let self = self, let tunnelsManager = self.tunnelsManager {
                 self.presentViewControllerForTunnelCreation(tunnelsManager: tunnelsManager)
             }
         }
         alert.addAction(createFromScratchAction)
 
-        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
+        let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
         alert.addAction(cancelAction)
 
         if let sender = sender as? UIBarButtonItem {
@@ -172,9 +172,9 @@ class TunnelsListTableViewController: UIViewController {
                         completionHandler?()
                         return
                     }
-                    ErrorPresenter.showErrorAlert(title: "Created \(numberSuccessful) tunnels",
-                        message: "Created \(numberSuccessful) of \(configs.count) tunnels from zip archive",
-                        from: self, onPresented: completionHandler)
+                    let title = tr(format: "alertImportedFromZipTitle (%d)", numberSuccessful)
+                    let message = tr(format: "alertImportedFromZipMessage (%1$d of %2$d)", numberSuccessful, configs.count)
+                    ErrorPresenter.showErrorAlert(title: title, message: message, from: self, onPresented: completionHandler)
                 }
             }
         } else /* if (url.pathExtension == "conf") -- we assume everything else is a conf */ {
@@ -189,8 +189,7 @@ class TunnelsListTableViewController: UIViewController {
                     }
                 }
             } else {
-                ErrorPresenter.showErrorAlert(title: "Unable to import tunnel",
-                                              message: "An error occured when importing the tunnel configuration.",
+                ErrorPresenter.showErrorAlert(title: tr("alertUnableToImportTitle"), message: tr("alertUnableToImportMessage"),
                                               from: self, onPresented: completionHandler)
             }
         }
@@ -266,7 +265,7 @@ extension TunnelsListTableViewController: UITableViewDelegate {
 
     func tableView(_ tableView: UITableView,
                    trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
-        let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] _, _, completionHandler in
+        let deleteAction = UIContextualAction(style: .destructive, title: tr("tunnelsListSwipeDeleteButtonTitle")) { [weak self] _, _, completionHandler in
             guard let tunnelsManager = self?.tunnelsManager else { return }
             let tunnel = tunnelsManager.tunnel(at: indexPath.row)
             tunnelsManager.remove(tunnel: tunnel) { error in
index 66c99f53e04c2b505b296cd695f71a09b776eb4d..df0ec7369d29e58f5c4da5504d7a10accfc06089 100644 (file)
@@ -11,11 +11,11 @@ enum ZipArchiveError: WireGuardAppError {
     var alertText: AlertText {
         switch self {
         case .cantOpenInputZipFile:
-            return ("Unable to read zip archive", "The zip archive could not be read.")
+            return (tr("alertCantOpenInputZipFileTitle"), tr("alertCantOpenInputZipFileMessage"))
         case .cantOpenOutputZipFileForWriting:
-            return ("Unable to create zip archive", "Could not open zip file for writing.")
+            return (tr("alertCantOpenOutputZipFileForWritingTitle"), tr("alertCantOpenOutputZipFileForWritingMessage"))
         case .badArchive:
-            return ("Unable to read zip archive", "Bad or corrupt zip archive.")
+            return (tr("alertBadArchiveTitle"), tr("alertBadArchiveMessage"))
         }
     }
 }
index 4c5fde865206de593e362d2402692b8fe9f10777..33d62fd94005e041a2f51662b41f49e2f18b3ede 100644 (file)
@@ -7,7 +7,7 @@ enum ZipExporterError: WireGuardAppError {
     case noTunnelsToExport
 
     var alertText: AlertText {
-        return ("Nothing to export", "There are no tunnels to export")
+        return (tr("alertNoTunnelsToExportTitle"), tr("alertNoTunnelsToExportMessage"))
     }
 }
 
index e87633c2624ac62522658a5c6a176e1d766aeabc..0178ca0746c258159d0d6a1469c197a6a4cc80d2 100644 (file)
@@ -7,7 +7,7 @@ enum ZipImporterError: WireGuardAppError {
     case noTunnelsInZipArchive
 
     var alertText: AlertText {
-        return ("No tunnels in zip archive", "No .conf tunnel files were found inside the zip archive.")
+        return (tr("alertNoTunnelsInImportedZipArchiveTitle"), tr("alertNoTunnelsInImportedZipArchiveMessage"))
     }
 }