aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/WireGuard
diff options
context:
space:
mode:
Diffstat (limited to 'WireGuard/WireGuard')
-rw-r--r--WireGuard/WireGuard/Base.lproj/Localizable.strings210
-rw-r--r--WireGuard/WireGuard/LocalizationHelper.swift12
-rw-r--r--WireGuard/WireGuard/Tunnel/TunnelErrors.swift33
-rw-r--r--WireGuard/WireGuard/UI/TunnelViewModel.swift97
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/QRScanViewController.swift18
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift39
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift34
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift47
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift27
-rw-r--r--WireGuard/WireGuard/ZipArchive/ZipArchive.swift6
-rw-r--r--WireGuard/WireGuard/ZipArchive/ZipExporter.swift2
-rw-r--r--WireGuard/WireGuard/ZipArchive/ZipImporter.swift2
12 files changed, 392 insertions, 135 deletions
diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings
new file mode 100644
index 0000000..524cfdc
--- /dev/null
+++ b/WireGuard/WireGuard/Base.lproj/Localizable.strings
@@ -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
index 0000000..ea4cc64
--- /dev/null
+++ b/WireGuard/WireGuard/LocalizationHelper.swift
@@ -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)
+}
diff --git a/WireGuard/WireGuard/Tunnel/TunnelErrors.swift b/WireGuard/WireGuard/Tunnel/TunnelErrors.swift
index 45c20f6..c3d15b2 100644
--- a/WireGuard/WireGuard/Tunnel/TunnelErrors.swift
+++ b/WireGuard/WireGuard/Tunnel/TunnelErrors.swift
@@ -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 ""
}
diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift
index 71cb18e..f7ebb68 100644
--- a/WireGuard/WireGuard/UI/TunnelViewModel.swift
+++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift
@@ -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")
}
}
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/QRScanViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/QRScanViewController.swift
index e4b6287..1fd6905 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/QRScanViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/QRScanViewController.swift
@@ -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
}
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift
index 65ad2fe..22edcbc 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift
@@ -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)
}
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift
index d3f9c84..50c0e33 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift
@@ -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 {
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
index 4aa1180..3d9724c 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
@@ -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)
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift
index 9dea8b0..fff976f 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelsListTableViewController.swift
@@ -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
diff --git a/WireGuard/WireGuard/ZipArchive/ZipArchive.swift b/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
index 66c99f5..df0ec73 100644
--- a/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
+++ b/WireGuard/WireGuard/ZipArchive/ZipArchive.swift
@@ -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"))
}
}
}
diff --git a/WireGuard/WireGuard/ZipArchive/ZipExporter.swift b/WireGuard/WireGuard/ZipArchive/ZipExporter.swift
index 4c5fde8..33d62fd 100644
--- a/WireGuard/WireGuard/ZipArchive/ZipExporter.swift
+++ b/WireGuard/WireGuard/ZipArchive/ZipExporter.swift
@@ -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"))
}
}
diff --git a/WireGuard/WireGuard/ZipArchive/ZipImporter.swift b/WireGuard/WireGuard/ZipArchive/ZipImporter.swift
index e87633c..0178ca0 100644
--- a/WireGuard/WireGuard/ZipArchive/ZipImporter.swift
+++ b/WireGuard/WireGuard/ZipArchive/ZipImporter.swift
@@ -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"))
}
}