Share common protocols across localized strings (#324)
This commit is contained in:
parent
bf70c7c59a
commit
98e5e4cdde
|
@ -29,7 +29,6 @@
|
||||||
0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */; };
|
0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */; };
|
||||||
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; };
|
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6329C84B5A0022E884 /* LockableView.swift */; };
|
||||||
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
|
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
|
||||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; };
|
|
||||||
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */; };
|
0E1AD5CC2A2682DA002AE6E6 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */; };
|
||||||
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; };
|
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; };
|
||||||
0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; };
|
0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; };
|
||||||
|
@ -317,7 +316,6 @@
|
||||||
0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SceneDelegate+Shortcuts.swift"; sourceTree = "<group>"; };
|
0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SceneDelegate+Shortcuts.swift"; sourceTree = "<group>"; };
|
||||||
0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = "<group>"; };
|
0E0F4C6329C84B5A0022E884 /* LockableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockableView.swift; sourceTree = "<group>"; };
|
||||||
0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
|
0E0F4C6529C84CF60022E884 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
|
||||||
0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; };
|
|
||||||
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
|
0E1AD5CB2A2682DA002AE6E6 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
|
||||||
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = "<group>"; };
|
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = "<group>"; };
|
||||||
0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = "<group>"; };
|
0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -612,7 +610,6 @@
|
||||||
0E2C172A27CB63F9007E8488 /* Reviewer.swift */,
|
0E2C172A27CB63F9007E8488 /* Reviewer.swift */,
|
||||||
0ED89C1427DE0A0C008B36D6 /* Shortcut.swift */,
|
0ED89C1427DE0A0C008B36D6 /* Shortcut.swift */,
|
||||||
0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */,
|
0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */,
|
||||||
0E12BC8E27F62C8500B2F912 /* Validators.swift */,
|
|
||||||
);
|
);
|
||||||
path = Reusable;
|
path = Reusable;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1509,7 +1506,6 @@
|
||||||
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */,
|
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */,
|
||||||
0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */,
|
0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */,
|
||||||
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
|
||||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */,
|
|
||||||
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
|
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
|
||||||
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
|
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
|
||||||
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
|
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
|
||||||
|
|
|
@ -48,7 +48,7 @@ final class IntentDispatcher {
|
||||||
intent.profileId = header.id.uuidString
|
intent.profileId = header.id.uuidString
|
||||||
intent.providerFullName = providerFullName
|
intent.providerFullName = providerFullName
|
||||||
intent.serverId = server.id
|
intent.serverId = server.id
|
||||||
intent.serverName = server.localizedLongDescription(withCategory: false)
|
intent.serverName = server.localizedDescription(style: .longWithCategory(withCategory: false))
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ extension ProviderLocation: Comparable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func < (lhs: Self, rhs: Self) -> Bool {
|
public static func < (lhs: Self, rhs: Self) -> Bool {
|
||||||
lhs.localizedCountry < rhs.localizedCountry
|
lhs.localizedDescription(style: .country) < rhs.localizedDescription(style: .country)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,19 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
|
|
||||||
extension ObservableVPNState {
|
extension ObservableVPNState: StyledLocalizableEntity {
|
||||||
func localizedStatusDescription(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool) -> String {
|
public enum Style {
|
||||||
|
case status(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(style: Style) -> String {
|
||||||
|
switch style {
|
||||||
|
case .status(let isActiveProfile, let withErrors, let dataCountIfAvailable):
|
||||||
|
return statusDescription(isActiveProfile: isActiveProfile, withErrors: withErrors, dataCountIfAvailable: dataCountIfAvailable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func statusDescription(isActiveProfile: Bool, withErrors: Bool, dataCountIfAvailable: Bool) -> String {
|
||||||
guard isActiveProfile && isEnabled else {
|
guard isActiveProfile && isEnabled else {
|
||||||
return L10n.Tunnelkit.Vpn.disabled
|
return L10n.Tunnelkit.Vpn.disabled
|
||||||
}
|
}
|
||||||
|
@ -65,8 +76,8 @@ extension Profile.WireGuardSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Network.Choice {
|
extension Network.Choice: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .automatic:
|
case .automatic:
|
||||||
return L10n.Global.Strings.automatic
|
return L10n.Global.Strings.automatic
|
||||||
|
@ -77,8 +88,8 @@ extension Network.Choice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Network.DNSSettings.ConfigurationType {
|
extension Network.DNSSettings.ConfigurationType: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .plain:
|
case .plain:
|
||||||
return Unlocalized.DNS.plain
|
return Unlocalized.DNS.plain
|
||||||
|
@ -95,8 +106,8 @@ extension Network.DNSSettings.ConfigurationType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Network.ProxySettings.ConfigurationType {
|
extension Network.ProxySettings.ConfigurationType: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .manual:
|
case .manual:
|
||||||
return L10n.Global.Strings.manual
|
return L10n.Global.Strings.manual
|
||||||
|
@ -109,3 +120,38 @@ extension Network.ProxySettings.ConfigurationType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Profile.Account.AuthenticationMethod: LocalizableEntity {
|
||||||
|
public var localizedDescription: String {
|
||||||
|
switch self {
|
||||||
|
case .persistent:
|
||||||
|
return L10n.Account.Items.AuthenticationMethod.persistent
|
||||||
|
|
||||||
|
case .interactive:
|
||||||
|
return L10n.Account.Items.AuthenticationMethod.interactive
|
||||||
|
|
||||||
|
case .totp:
|
||||||
|
return Unlocalized.Other.totp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int: StyledLocalizableEntity {
|
||||||
|
public enum Style {
|
||||||
|
case mtu
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(style: Style) -> String {
|
||||||
|
switch style {
|
||||||
|
case .mtu:
|
||||||
|
return mtuDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mtuDescription: String {
|
||||||
|
guard self != 0 else {
|
||||||
|
return L10n.Global.Strings.default
|
||||||
|
}
|
||||||
|
return description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,20 +27,20 @@ import Foundation
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
import TunnelKitOpenVPN
|
import TunnelKitOpenVPN
|
||||||
|
|
||||||
extension OpenVPN.Cipher {
|
extension OpenVPN.Cipher: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OpenVPN.Digest {
|
extension OpenVPN.Digest: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OpenVPN.CompressionFraming {
|
extension OpenVPN.CompressionFraming: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .disabled:
|
case .disabled:
|
||||||
return L10n.Global.Strings.disabled
|
return L10n.Global.Strings.disabled
|
||||||
|
@ -54,8 +54,8 @@ extension OpenVPN.CompressionFraming {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OpenVPN.CompressionAlgorithm {
|
extension OpenVPN.CompressionAlgorithm: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
||||||
switch self {
|
switch self {
|
||||||
case .disabled:
|
case .disabled:
|
||||||
|
@ -70,24 +70,24 @@ extension OpenVPN.CompressionAlgorithm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Optional where Wrapped == OpenVPN.TLSWrap {
|
extension OpenVPN.XORMethod: StyledLocalizableEntity {
|
||||||
var localizedDescription: String {
|
public enum Style {
|
||||||
guard let strategy = self?.strategy else {
|
case short
|
||||||
return L10n.Global.Strings.disabled
|
|
||||||
}
|
|
||||||
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
|
||||||
switch strategy {
|
|
||||||
case .auth:
|
|
||||||
return V.TlsWrapping.Value.auth
|
|
||||||
|
|
||||||
case .crypt:
|
case long
|
||||||
return V.TlsWrapping.Value.crypt
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension OpenVPN.XORMethod {
|
public func localizedDescription(style: Style) -> String {
|
||||||
var localizedDescription: String {
|
switch style {
|
||||||
|
case .short:
|
||||||
|
return shortDescription
|
||||||
|
|
||||||
|
case .long:
|
||||||
|
return longDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var shortDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .xormask:
|
case .xormask:
|
||||||
return Unlocalized.OpenVPN.XOR.xormask.rawValue
|
return Unlocalized.OpenVPN.XOR.xormask.rawValue
|
||||||
|
@ -103,52 +103,22 @@ extension OpenVPN.XORMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedLongDescription: String {
|
private var longDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .xormask(let mask):
|
case .xormask(let mask):
|
||||||
return "\(localizedDescription) \(mask.toHex())"
|
return "\(shortDescription) \(mask.toHex())"
|
||||||
|
|
||||||
case .obfuscate(let mask):
|
case .obfuscate(let mask):
|
||||||
return "\(localizedDescription) \(mask.toHex())"
|
return "\(shortDescription) \(mask.toHex())"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return localizedDescription
|
return shortDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Optional where Wrapped == Bool {
|
extension OpenVPN.PullMask: LocalizableEntity {
|
||||||
var localizedDescriptionAsEKU: String {
|
public var localizedDescription: String {
|
||||||
let V = L10n.Global.Strings.self
|
|
||||||
return (self ?? false) ? V.enabled : V.disabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TimeInterval {
|
|
||||||
var localizedDescriptionAsRenegotiatesAfter: String {
|
|
||||||
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
|
||||||
if self > 0 {
|
|
||||||
return V.RenegotiationSeconds.Value.after(TimeInterval(self).localizedDescription)
|
|
||||||
} else {
|
|
||||||
return L10n.Global.Strings.disabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Bool {
|
|
||||||
var localizedDescriptionAsRandomizeEndpoint: String {
|
|
||||||
let V = L10n.Global.Strings.self
|
|
||||||
return self ? V.enabled : V.disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
var localizedDescriptionAsRandomizeHostnames: String {
|
|
||||||
let V = L10n.Global.Strings.self
|
|
||||||
return self ? V.enabled : V.disabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension OpenVPN.PullMask {
|
|
||||||
var localizedDescription: String {
|
|
||||||
switch self {
|
switch self {
|
||||||
case .routes:
|
case .routes:
|
||||||
return L10n.Endpoint.Advanced.Openvpn.Items.Route.caption
|
return L10n.Endpoint.Advanced.Openvpn.Items.Route.caption
|
||||||
|
@ -162,6 +132,111 @@ extension OpenVPN.PullMask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension OpenVPN.ConfigurationBuilder: StyledLocalizableEntity {
|
||||||
|
public enum Style {
|
||||||
|
case tlsWrap
|
||||||
|
|
||||||
|
case eku
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(style: Style) -> String {
|
||||||
|
switch style {
|
||||||
|
case .tlsWrap:
|
||||||
|
return tlsWrap.tlsWrapDescription
|
||||||
|
|
||||||
|
case .eku:
|
||||||
|
return checksEKU.ekuDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OpenVPN.Configuration: StyledOptionalLocalizableEntity {
|
||||||
|
public enum OptionalStyle {
|
||||||
|
case keepAlive
|
||||||
|
|
||||||
|
case renegotiatesAfter
|
||||||
|
|
||||||
|
case randomizeEndpoint
|
||||||
|
|
||||||
|
case randomizeHostnames
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(optionalStyle: OptionalStyle) -> String? {
|
||||||
|
switch optionalStyle {
|
||||||
|
case .keepAlive:
|
||||||
|
return keepAliveInterval?.keepAliveDescription
|
||||||
|
|
||||||
|
case .renegotiatesAfter:
|
||||||
|
return renegotiatesAfter?.renegotiatesAfterDescription
|
||||||
|
|
||||||
|
case .randomizeEndpoint:
|
||||||
|
return randomizeEndpoint?.randomizeEndpointDescription
|
||||||
|
|
||||||
|
case .randomizeHostnames:
|
||||||
|
return randomizeHostnames?.randomizeHostnamesDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Optional where Wrapped == OpenVPN.TLSWrap {
|
||||||
|
var tlsWrapDescription: String {
|
||||||
|
guard let strategy = self?.strategy else {
|
||||||
|
return L10n.Global.Strings.disabled
|
||||||
|
}
|
||||||
|
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
||||||
|
switch strategy {
|
||||||
|
case .auth:
|
||||||
|
return V.TlsWrapping.Value.auth
|
||||||
|
|
||||||
|
case .crypt:
|
||||||
|
return V.TlsWrapping.Value.crypt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TimeInterval {
|
||||||
|
var keepAliveDescription: String {
|
||||||
|
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
||||||
|
if self > 0 {
|
||||||
|
return V.KeepAlive.Value.seconds(Int(self))
|
||||||
|
} else {
|
||||||
|
return L10n.Global.Strings.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Optional where Wrapped == Bool {
|
||||||
|
var ekuDescription: String {
|
||||||
|
let V = L10n.Global.Strings.self
|
||||||
|
return (self ?? false) ? V.enabled : V.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TimeInterval {
|
||||||
|
var renegotiatesAfterDescription: String {
|
||||||
|
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
||||||
|
if self > 0 {
|
||||||
|
return V.RenegotiationSeconds.Value.after(TimeInterval(self).localizedDescription)
|
||||||
|
} else {
|
||||||
|
return L10n.Global.Strings.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Bool {
|
||||||
|
var randomizeEndpointDescription: String {
|
||||||
|
let V = L10n.Global.Strings.self
|
||||||
|
return self ? V.enabled : V.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var randomizeHostnamesDescription: String {
|
||||||
|
let V = L10n.Global.Strings.self
|
||||||
|
return self ? V.enabled : V.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Errors
|
||||||
|
|
||||||
extension TunnelKitOpenVPNError: LocalizedError {
|
extension TunnelKitOpenVPNError: LocalizedError {
|
||||||
public var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
let V = L10n.Tunnelkit.Errors.Vpn.self
|
let V = L10n.Tunnelkit.Errors.Vpn.self
|
||||||
|
|
|
@ -26,15 +26,31 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
|
|
||||||
extension ProviderManager {
|
extension ProviderManager: StyledOptionalLocalizableEntity {
|
||||||
func localizedPreset(forProfile profile: Profile) -> String? {
|
public enum OptionalStyle {
|
||||||
|
case preset(profile: Profile)
|
||||||
|
|
||||||
|
case infrastructureUpdate(profile: Profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(optionalStyle: OptionalStyle) -> String? {
|
||||||
|
switch optionalStyle {
|
||||||
|
case .preset(let profile):
|
||||||
|
return presetDescription(forProfile: profile)
|
||||||
|
|
||||||
|
case .infrastructureUpdate(let profile):
|
||||||
|
return infrastructureUpdateDescription(forProfile: profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presetDescription(forProfile profile: Profile) -> String? {
|
||||||
guard let server = profile.providerServer(self) else {
|
guard let server = profile.providerServer(self) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return profile.providerPreset(server)?.localizedDescription
|
return profile.providerPreset(server)?.localizedDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
func localizedInfrastructureUpdate(forProfile profile: Profile) -> String? {
|
private func infrastructureUpdateDescription(forProfile profile: Profile) -> String? {
|
||||||
guard let providerName = profile.header.providerName else {
|
guard let providerName = profile.header.providerName else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -42,8 +58,19 @@ extension ProviderManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProviderMetadata {
|
extension ProviderMetadata: StyledOptionalLocalizableEntity {
|
||||||
var localizedGuidanceString: String? {
|
public enum OptionalStyle {
|
||||||
|
case guidance
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(optionalStyle: OptionalStyle) -> String? {
|
||||||
|
switch optionalStyle {
|
||||||
|
case .guidance:
|
||||||
|
return guidanceString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var guidanceString: String? {
|
||||||
let prefix = "account.sections.guidance.footer.infrastructure"
|
let prefix = "account.sections.guidance.footer.infrastructure"
|
||||||
let key = "\(prefix).\(name)"
|
let key = "\(prefix).\(name)"
|
||||||
var format = NSLocalizedString(key, bundle: .main, comment: "")
|
var format = NSLocalizedString(key, bundle: .main, comment: "")
|
||||||
|
@ -59,26 +86,92 @@ extension ProviderMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProviderLocation {
|
extension ProviderLocation: StyledLocalizableEntity {
|
||||||
var localizedCountry: String {
|
public enum Style {
|
||||||
|
case country
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(style: Style) -> String {
|
||||||
|
switch style {
|
||||||
|
case .country:
|
||||||
|
return countryDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var countryDescription: String {
|
||||||
countryCode.localizedAsCountryCode
|
countryCode.localizedAsCountryCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProviderServer {
|
extension ProviderServer: StyledLocalizableEntity {
|
||||||
var localizedCountry: String {
|
public enum Style {
|
||||||
|
case country
|
||||||
|
|
||||||
|
case countryWithCategory(withCategory: Bool)
|
||||||
|
|
||||||
|
case shortWithDefault
|
||||||
|
|
||||||
|
case longWithCategory(withCategory: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(style: Style) -> String {
|
||||||
|
switch style {
|
||||||
|
case .country:
|
||||||
|
return countryDescription
|
||||||
|
|
||||||
|
case .countryWithCategory(let withCategory):
|
||||||
|
return countryDescription(withCategory: withCategory)
|
||||||
|
|
||||||
|
case .shortWithDefault:
|
||||||
|
return shortDescriptionWithDefault
|
||||||
|
|
||||||
|
case .longWithCategory(let withCategory):
|
||||||
|
return longDescription(withCategory: withCategory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var countryDescription: String {
|
||||||
countryCode.localizedAsCountryCode
|
countryCode.localizedAsCountryCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func localizedCountry(withCategory: Bool) -> String {
|
private func countryDescription(withCategory: Bool) -> String {
|
||||||
let desc = localizedCountry
|
let desc = countryDescription
|
||||||
if withCategory, !categoryName.isEmpty {
|
if withCategory, !categoryName.isEmpty {
|
||||||
return "\(categoryName.uppercased()): \(desc)"
|
return "\(categoryName.uppercased()): \(desc)"
|
||||||
}
|
}
|
||||||
return desc
|
return desc
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedShortDescription: String? {
|
private var shortDescriptionWithDefault: String {
|
||||||
|
shortDescription ?? "\(L10n.Global.Strings.default) [\(apiId)]"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func longDescription(withCategory: Bool) -> String {
|
||||||
|
var comps: [String] = [countryDescription]
|
||||||
|
shortDescription.map {
|
||||||
|
comps.append($0)
|
||||||
|
}
|
||||||
|
let desc = comps.joined(separator: ", ")
|
||||||
|
if withCategory, !categoryName.isEmpty {
|
||||||
|
return "\(categoryName.uppercased()): \(desc)"
|
||||||
|
}
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ProviderServer: StyledOptionalLocalizableEntity {
|
||||||
|
public enum OptionalStyle {
|
||||||
|
case short
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(optionalStyle: OptionalStyle) -> String? {
|
||||||
|
switch optionalStyle {
|
||||||
|
case .short:
|
||||||
|
return shortDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var shortDescription: String? {
|
||||||
var comps = localizedName.map { [$0] } ?? []
|
var comps = localizedName.map { [$0] } ?? []
|
||||||
if let serverIndex = serverIndex {
|
if let serverIndex = serverIndex {
|
||||||
comps.append("#\(serverIndex)")
|
comps.append("#\(serverIndex)")
|
||||||
|
@ -96,26 +189,10 @@ extension ProviderServer {
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedShortDescriptionWithDefault: String {
|
|
||||||
localizedShortDescription ?? "\(L10n.Global.Strings.default) [\(apiId)]"
|
|
||||||
}
|
|
||||||
|
|
||||||
func localizedLongDescription(withCategory: Bool) -> String {
|
|
||||||
var comps: [String] = [localizedCountry]
|
|
||||||
localizedShortDescription.map {
|
|
||||||
comps.append($0)
|
|
||||||
}
|
|
||||||
let desc = comps.joined(separator: ", ")
|
|
||||||
if withCategory, !categoryName.isEmpty {
|
|
||||||
return "\(categoryName.uppercased()): \(desc)"
|
|
||||||
}
|
|
||||||
return desc
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProviderServer.Preset {
|
extension ProviderServer.Preset: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ import TunnelKitManager
|
||||||
import TunnelKitOpenVPN
|
import TunnelKitOpenVPN
|
||||||
import TunnelKitWireGuard
|
import TunnelKitWireGuard
|
||||||
|
|
||||||
extension VPNStatus {
|
extension VPNStatus: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .connecting:
|
case .connecting:
|
||||||
return L10n.Tunnelkit.Vpn.connecting
|
return L10n.Tunnelkit.Vpn.connecting
|
||||||
|
@ -48,62 +48,74 @@ extension VPNStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DataCount {
|
extension DataCount: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
let down = received.descriptionAsDataUnit
|
let down = received.descriptionAsDataUnit
|
||||||
let up = sent.descriptionAsDataUnit
|
let up = sent.descriptionAsDataUnit
|
||||||
return "↓\(down) ↑\(up)"
|
return "↓\(down) ↑\(up)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Int {
|
extension IPv4Settings: StyledLocalizableEntity {
|
||||||
var localizedDescriptionAsMTU: String {
|
public enum Style {
|
||||||
guard self != 0 else {
|
case address
|
||||||
return L10n.Global.Strings.default
|
|
||||||
}
|
|
||||||
return description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TimeInterval {
|
case defaultGateway
|
||||||
var localizedDescriptionAsKeepAlive: String {
|
|
||||||
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
|
||||||
if self > 0 {
|
|
||||||
return V.KeepAlive.Value.seconds(Int(self))
|
|
||||||
} else {
|
|
||||||
return L10n.Global.Strings.disabled
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension IPv4Settings {
|
public func localizedDescription(style: Style) -> String {
|
||||||
var localizedAddress: String {
|
switch style {
|
||||||
|
case .address:
|
||||||
|
return addressDescription
|
||||||
|
|
||||||
|
case .defaultGateway:
|
||||||
|
return defaultGatewayDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var addressDescription: String {
|
||||||
"\(address)/\(addressMask)"
|
"\(address)/\(addressMask)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedDefaultGateway: String {
|
private var defaultGatewayDescription: String {
|
||||||
defaultGateway
|
defaultGateway
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IPv6Settings {
|
extension IPv6Settings: StyledLocalizableEntity {
|
||||||
var localizedAddress: String {
|
public enum Style {
|
||||||
|
case address
|
||||||
|
|
||||||
|
case defaultGateway
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(style: Style) -> String {
|
||||||
|
switch style {
|
||||||
|
case .address:
|
||||||
|
return addressDescription
|
||||||
|
|
||||||
|
case .defaultGateway:
|
||||||
|
return defaultGatewayDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var addressDescription: String {
|
||||||
"\(address)/\(addressPrefixLength)"
|
"\(address)/\(addressPrefixLength)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedDefaultGateway: String {
|
private var defaultGatewayDescription: String {
|
||||||
defaultGateway
|
defaultGateway
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IPv4Settings.Route {
|
extension IPv4Settings.Route: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
"\(destination)/\(mask) -> \(gateway ?? "*")"
|
"\(destination)/\(mask) -> \(gateway ?? "*")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IPv6Settings.Route {
|
extension IPv6Settings.Route: LocalizableEntity {
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
"\(destination)/\(prefixLength) -> \(gateway ?? "*")"
|
"\(destination)/\(prefixLength) -> \(gateway ?? "*")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,36 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import PassepartoutLibrary
|
||||||
import TunnelKitWireGuard
|
import TunnelKitWireGuard
|
||||||
|
|
||||||
|
extension WireGuard.ConfigurationBuilder: StyledOptionalLocalizableEntity {
|
||||||
|
public enum OptionalStyle {
|
||||||
|
case keepAlive(peerIndex: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func localizedDescription(optionalStyle: OptionalStyle) -> String? {
|
||||||
|
switch optionalStyle {
|
||||||
|
case .keepAlive(let peerIndex):
|
||||||
|
return keepAlive(ofPeer: peerIndex)?.keepAliveDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UInt16 {
|
||||||
|
var keepAliveDescription: String {
|
||||||
|
// FIXME: l10n, move from OpenVPN to shared
|
||||||
|
let V = L10n.Endpoint.Advanced.Openvpn.Items.self
|
||||||
|
if self > 0 {
|
||||||
|
return V.KeepAlive.Value.seconds(Int(self))
|
||||||
|
} else {
|
||||||
|
return L10n.Global.Strings.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Errors
|
||||||
|
|
||||||
extension TunnelKitWireGuardError: LocalizedError {
|
extension TunnelKitWireGuardError: LocalizedError {
|
||||||
public var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
let V = L10n.Tunnelkit.Errors.Vpn.self
|
let V = L10n.Tunnelkit.Errors.Vpn.self
|
||||||
|
|
|
@ -50,7 +50,7 @@ final class DefaultLightProviderLocation: LightProviderLocation {
|
||||||
let servers: [LightProviderServer]
|
let servers: [LightProviderServer]
|
||||||
|
|
||||||
init(_ location: ProviderLocation) {
|
init(_ location: ProviderLocation) {
|
||||||
description = location.localizedCountry
|
description = location.localizedDescription(style: .country)
|
||||||
id = location.id
|
id = location.id
|
||||||
countryCode = location.countryCode
|
countryCode = location.countryCode
|
||||||
servers = location.servers?
|
servers = location.servers?
|
||||||
|
@ -71,8 +71,8 @@ final class DefaultLightProviderServer: LightProviderServer {
|
||||||
let serverId: String
|
let serverId: String
|
||||||
|
|
||||||
init(_ server: ProviderServer) {
|
init(_ server: ProviderServer) {
|
||||||
description = server.localizedShortDescriptionWithDefault
|
description = server.localizedDescription(style: .shortWithDefault)
|
||||||
longDescription = server.localizedLongDescription(withCategory: false)
|
longDescription = server.localizedDescription(style: .longWithCategory(withCategory: false))
|
||||||
categoryName = server.categoryName
|
categoryName = server.categoryName
|
||||||
locationId = server.locationId
|
locationId = server.locationId
|
||||||
serverId = server.id
|
serverId = server.id
|
||||||
|
|
|
@ -89,7 +89,7 @@ struct AccountView: View {
|
||||||
.withLeadingText(L10n.Account.Items.Seed.caption)
|
.withLeadingText(L10n.Account.Items.Seed.caption)
|
||||||
}
|
}
|
||||||
} footer: {
|
} footer: {
|
||||||
metadata?.localizedGuidanceString.map {
|
metadata?.localizedDescription(optionalStyle: .guidance).map {
|
||||||
Text($0)
|
Text($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,21 +133,6 @@ private extension AccountView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension Profile.Account.AuthenticationMethod {
|
|
||||||
var localizedDescription: String {
|
|
||||||
switch self {
|
|
||||||
case .persistent:
|
|
||||||
return L10n.Account.Items.AuthenticationMethod.persistent
|
|
||||||
|
|
||||||
case .interactive:
|
|
||||||
return L10n.Account.Items.AuthenticationMethod.interactive
|
|
||||||
|
|
||||||
case .totp:
|
|
||||||
return Unlocalized.Other.totp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
private extension AccountView {
|
private extension AccountView {
|
||||||
|
|
|
@ -87,11 +87,11 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
if let settings = builder.ipv4 {
|
if let settings = builder.ipv4 {
|
||||||
themeLongContentLinkDefault(
|
themeLongContentLinkDefault(
|
||||||
L10n.Global.Strings.address,
|
L10n.Global.Strings.address,
|
||||||
content: .constant(settings.localizedAddress)
|
content: .constant(settings.localizedDescription(style: .address))
|
||||||
)
|
)
|
||||||
themeLongContentLinkDefault(
|
themeLongContentLinkDefault(
|
||||||
L10n.NetworkSettings.Gateway.title,
|
L10n.NetworkSettings.Gateway.title,
|
||||||
content: .constant(settings.localizedDefaultGateway)
|
content: .constant(settings.localizedDescription(style: .defaultGateway))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
builder.routes4.map { routes in
|
builder.routes4.map { routes in
|
||||||
|
@ -112,11 +112,11 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
if let settings = builder.ipv6 {
|
if let settings = builder.ipv6 {
|
||||||
themeLongContentLinkDefault(
|
themeLongContentLinkDefault(
|
||||||
L10n.Global.Strings.address,
|
L10n.Global.Strings.address,
|
||||||
content: .constant(settings.localizedAddress)
|
content: .constant(settings.localizedDescription(style: .address))
|
||||||
)
|
)
|
||||||
themeLongContentLinkDefault(
|
themeLongContentLinkDefault(
|
||||||
L10n.NetworkSettings.Gateway.title,
|
L10n.NetworkSettings.Gateway.title,
|
||||||
content: .constant(settings.localizedDefaultGateway)
|
content: .constant(settings.localizedDescription(style: .defaultGateway))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
builder.routes6.map { routes in
|
builder.routes6.map { routes in
|
||||||
|
@ -137,17 +137,17 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
Section {
|
Section {
|
||||||
settings.cipher.map {
|
settings.cipher.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption)
|
||||||
.withTrailingText($0.localizedDescription)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
settings.digest.map {
|
settings.digest.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.Digest.caption)
|
||||||
.withTrailingText($0.localizedDescription)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
if let xor = settings.xor {
|
if let xor = settings.xor {
|
||||||
themeLongContentLink(
|
themeLongContentLink(
|
||||||
Unlocalized.VPN.xor,
|
Unlocalized.VPN.xor,
|
||||||
content: .constant(xor.localizedLongDescription),
|
content: .constant(xor.longDescription),
|
||||||
withPreview: xor.localizedDescription
|
withPreview: xor.shortDescription
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(Unlocalized.VPN.xor)
|
Text(Unlocalized.VPN.xor)
|
||||||
|
@ -176,8 +176,8 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
if let xor = builder.xorMethod {
|
if let xor = builder.xorMethod {
|
||||||
themeLongContentLink(
|
themeLongContentLink(
|
||||||
Unlocalized.VPN.xor,
|
Unlocalized.VPN.xor,
|
||||||
content: .constant(xor.localizedLongDescription),
|
content: .constant(xor.localizedDescription(style: .long)),
|
||||||
withPreview: xor.localizedDescription
|
withPreview: xor.localizedDescription(style: .short)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(Unlocalized.VPN.xor)
|
Text(Unlocalized.VPN.xor)
|
||||||
|
@ -193,11 +193,11 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
Section {
|
Section {
|
||||||
settings.framing.map {
|
settings.framing.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption)
|
||||||
.withTrailingText($0.localizedDescription)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
settings.algorithm.map {
|
settings.algorithm.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionAlgorithm.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionAlgorithm.caption)
|
||||||
.withTrailingText($0.localizedDescription)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header)
|
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header)
|
||||||
|
@ -246,11 +246,11 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
Section {
|
Section {
|
||||||
settings.proxy.map {
|
settings.proxy.map {
|
||||||
Text(L10n.Global.Strings.address)
|
Text(L10n.Global.Strings.address)
|
||||||
.withTrailingText($0.rawValue, copyOnTap: true)
|
.withTrailingText($0, copyOnTap: true)
|
||||||
}
|
}
|
||||||
settings.pac.map {
|
settings.pac.map {
|
||||||
Text(Unlocalized.Network.proxyAutoConfiguration)
|
Text(Unlocalized.Network.proxyAutoConfiguration)
|
||||||
.withTrailingText($0.absoluteString, copyOnTap: true)
|
.withTrailingText($0, copyOnTap: true)
|
||||||
}
|
}
|
||||||
ForEach(settings.bypass, id: \.self) {
|
ForEach(settings.bypass, id: \.self) {
|
||||||
Text(L10n.NetworkSettings.Items.ProxyBypass.caption)
|
Text(L10n.NetworkSettings.Items.ProxyBypass.caption)
|
||||||
|
@ -286,11 +286,11 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
themeLongContentLink(
|
themeLongContentLink(
|
||||||
L10n.Endpoint.Advanced.Openvpn.Items.TlsWrapping.caption,
|
L10n.Endpoint.Advanced.Openvpn.Items.TlsWrapping.caption,
|
||||||
content: .constant(wrap.key.hexString),
|
content: .constant(wrap.key.hexString),
|
||||||
withPreview: builder.tlsWrap.localizedDescription
|
withPreview: builder.localizedDescription(style: .tlsWrap)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.Eku.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.Eku.caption)
|
||||||
.withTrailingText(builder.checksEKU.localizedDescriptionAsEKU)
|
.withTrailingText(builder.localizedDescription(style: .eku))
|
||||||
} header: {
|
} header: {
|
||||||
Text(Unlocalized.Network.tls)
|
Text(Unlocalized.Network.tls)
|
||||||
}
|
}
|
||||||
|
@ -301,19 +301,19 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
Section {
|
Section {
|
||||||
settings.keepAlive.map {
|
settings.keepAlive.map {
|
||||||
Text(L10n.Global.Strings.keepalive)
|
Text(L10n.Global.Strings.keepalive)
|
||||||
.withTrailingText($0.localizedDescriptionAsKeepAlive)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
settings.reneg.map {
|
settings.reneg.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.RenegotiationSeconds.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.RenegotiationSeconds.caption)
|
||||||
.withTrailingText($0.localizedDescriptionAsRenegotiatesAfter)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
settings.randomizeEndpoint.map {
|
settings.randomizeEndpoint.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomEndpoint.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomEndpoint.caption)
|
||||||
.withTrailingText($0.localizedDescriptionAsRandomizeEndpoint)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
settings.randomizeHostnames.map {
|
settings.randomizeHostnames.map {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomHostname.caption)
|
Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomHostname.caption)
|
||||||
.withTrailingText($0.localizedDescriptionAsRandomizeHostnames)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Other.header)
|
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Other.header)
|
||||||
|
@ -324,17 +324,17 @@ private extension EndpointAdvancedView.OpenVPNView {
|
||||||
|
|
||||||
private extension OpenVPN.Configuration {
|
private extension OpenVPN.Configuration {
|
||||||
struct CommunicationOptions {
|
struct CommunicationOptions {
|
||||||
let cipher: OpenVPN.Cipher?
|
let cipher: String?
|
||||||
|
|
||||||
let digest: OpenVPN.Digest?
|
let digest: String?
|
||||||
|
|
||||||
let xor: OpenVPN.XORMethod?
|
let xor: (shortDescription: String, longDescription: String)?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CompressionOptions {
|
struct CompressionOptions {
|
||||||
let framing: OpenVPN.CompressionFraming?
|
let framing: String?
|
||||||
|
|
||||||
let algorithm: OpenVPN.CompressionAlgorithm?
|
let algorithm: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DNSOptions {
|
struct DNSOptions {
|
||||||
|
@ -344,35 +344,44 @@ private extension OpenVPN.Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProxyOptions {
|
struct ProxyOptions {
|
||||||
let proxy: Proxy?
|
let proxy: String?
|
||||||
|
|
||||||
let pac: URL?
|
let pac: String?
|
||||||
|
|
||||||
let bypass: [String]
|
let bypass: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OtherOptions {
|
struct OtherOptions {
|
||||||
let keepAlive: TimeInterval?
|
let keepAlive: String?
|
||||||
|
|
||||||
let reneg: TimeInterval?
|
let reneg: String?
|
||||||
|
|
||||||
let randomizeEndpoint: Bool?
|
let randomizeEndpoint: String?
|
||||||
|
|
||||||
let randomizeHostnames: Bool?
|
let randomizeHostnames: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
var communicationSettings: CommunicationOptions? {
|
var communicationSettings: CommunicationOptions? {
|
||||||
guard cipher != nil || digest != nil || xorMethod != nil else {
|
guard cipher != nil || digest != nil || xorMethod != nil else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(cipher: cipher, digest: digest, xor: xorMethod)
|
return .init(
|
||||||
|
cipher: cipher?.localizedDescription,
|
||||||
|
digest: digest?.localizedDescription,
|
||||||
|
xor: xorMethod.map {
|
||||||
|
($0.localizedDescription(style: .short), $0.localizedDescription(style: .long))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var compressionSettings: CompressionOptions? {
|
var compressionSettings: CompressionOptions? {
|
||||||
guard compressionFraming != nil || compressionAlgorithm != nil else {
|
guard compressionFraming != nil || compressionAlgorithm != nil else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(framing: compressionFraming, algorithm: compressionAlgorithm)
|
return .init(
|
||||||
|
framing: compressionFraming?.localizedDescription,
|
||||||
|
algorithm: compressionAlgorithm?.localizedDescription
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsSettings: DNSOptions? {
|
var dnsSettings: DNSOptions? {
|
||||||
|
@ -388,8 +397,8 @@ private extension OpenVPN.Configuration {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(
|
return .init(
|
||||||
proxy: httpsProxy ?? httpProxy,
|
proxy: (httpsProxy ?? httpProxy)?.rawValue,
|
||||||
pac: proxyAutoConfigurationURL,
|
pac: proxyAutoConfigurationURL?.absoluteString,
|
||||||
bypass: proxyBypassDomains ?? []
|
bypass: proxyBypassDomains ?? []
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -400,10 +409,10 @@ private extension OpenVPN.Configuration {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(
|
return .init(
|
||||||
keepAlive: keepAliveInterval,
|
keepAlive: localizedDescription(optionalStyle: .keepAlive),
|
||||||
reneg: renegotiatesAfter,
|
reneg: localizedDescription(optionalStyle: .renegotiatesAfter),
|
||||||
randomizeEndpoint: randomizeEndpoint,
|
randomizeEndpoint: localizedDescription(optionalStyle: .randomizeEndpoint),
|
||||||
randomizeHostnames: randomizeHostnames
|
randomizeHostnames: localizedDescription(optionalStyle: .randomizeHostnames)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ private extension EndpointAdvancedView.WireGuardView {
|
||||||
builder.mtu.map { mtu in
|
builder.mtu.map { mtu in
|
||||||
Section {
|
Section {
|
||||||
Text(Unlocalized.Network.mtu)
|
Text(Unlocalized.Network.mtu)
|
||||||
.withTrailingText(Int(mtu).localizedDescriptionAsMTU)
|
.withTrailingText(Int(mtu).localizedDescription(style: .mtu))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,9 +125,9 @@ private extension EndpointView.WireGuardView {
|
||||||
Text(L10n.Endpoint.Wireguard.Items.AllowedIp.caption)
|
Text(L10n.Endpoint.Wireguard.Items.AllowedIp.caption)
|
||||||
.withTrailingText($0)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
builder.keepAlive(ofPeer: peerIndex).map {
|
builder.localizedDescription(optionalStyle: .keepAlive(peerIndex: peerIndex)).map {
|
||||||
Text(L10n.Global.Strings.keepalive)
|
Text(L10n.Global.Strings.keepalive)
|
||||||
.withTrailingText(TimeInterval($0).localizedDescriptionAsKeepAlive)
|
.withTrailingText($0)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.Endpoint.Wireguard.Items.Peer.caption)
|
Text(L10n.Endpoint.Wireguard.Items.Peer.caption)
|
||||||
|
|
|
@ -257,7 +257,7 @@ private extension NetworkSettingsView {
|
||||||
L10n.Global.Strings.bytes,
|
L10n.Global.Strings.bytes,
|
||||||
selection: $settings.mtu.mtuBytes,
|
selection: $settings.mtu.mtuBytes,
|
||||||
values: Network.MTUSettings.availableBytes,
|
values: Network.MTUSettings.availableBytes,
|
||||||
description: \.localizedDescriptionAsMTU
|
description: { $0.localizedDescription(style: .mtu) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
|
|
|
@ -130,9 +130,9 @@ private extension ProfileView.ProviderSection {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if currentProfile.value.providerRandomizesServer ?? false {
|
if currentProfile.value.providerRandomizesServer ?? false {
|
||||||
return server.localizedCountry(withCategory: true)
|
return server.localizedDescription(style: .countryWithCategory(withCategory: true))
|
||||||
} else {
|
} else {
|
||||||
return server.localizedLongDescription(withCategory: true)
|
return server.localizedDescription(style: .longWithCategory(withCategory: true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,11 +144,11 @@ private extension ProfileView.ProviderSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentProviderPreset: String? {
|
var currentProviderPreset: String? {
|
||||||
providerManager.localizedPreset(forProfile: profile)
|
providerManager.localizedDescription(optionalStyle: .preset(profile: profile))
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastInfrastructureUpdate: String? {
|
var lastInfrastructureUpdate: String? {
|
||||||
providerManager.localizedInfrastructureUpdate(forProfile: profile)
|
providerManager.localizedDescription(optionalStyle: .infrastructureUpdate(profile: profile))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,13 +101,15 @@ extension ProviderLocationView {
|
||||||
HStack {
|
HStack {
|
||||||
themeAssetsCountryImage(location.countryCode).asAssetImage
|
themeAssetsCountryImage(location.countryCode).asAssetImage
|
||||||
VStack {
|
VStack {
|
||||||
if let singleServer = location.onlyServer, singleServer.localizedShortDescription != nil {
|
if let singleServer = location.onlyServer,
|
||||||
Text(location.localizedCountry)
|
let shortServerDescription = singleServer.localizedDescription(optionalStyle: .short) {
|
||||||
|
|
||||||
|
Text(location.localizedDescription(style: .country))
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
Text(singleServer.localizedShortDescription ?? "")
|
Text(shortServerDescription)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
} else {
|
} else {
|
||||||
Text(location.localizedCountry)
|
Text(location.localizedDescription(style: .country))
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
}.withTrailingCheckmark(when: location.id == selectedLocationId)
|
}.withTrailingCheckmark(when: location.id == selectedLocationId)
|
||||||
|
@ -132,7 +134,7 @@ extension ProviderLocationView {
|
||||||
ScrollViewReader { scrollProxy in
|
ScrollViewReader { scrollProxy in
|
||||||
List {
|
List {
|
||||||
ForEach(servers) { server in
|
ForEach(servers) { server in
|
||||||
Button(server.localizedShortDescriptionWithDefault) {
|
Button(server.localizedDescription(style: .shortWithDefault)) {
|
||||||
selectedServer = server
|
selectedServer = server
|
||||||
}.withTrailingCheckmark(when: server.id == selectedServer?.id)
|
}.withTrailingCheckmark(when: server.id == selectedServer?.id)
|
||||||
}
|
}
|
||||||
|
@ -211,7 +213,7 @@ private extension ProviderLocationView {
|
||||||
ServerListView(
|
ServerListView(
|
||||||
location: location,
|
location: location,
|
||||||
selectedServer: $selectedServer
|
selectedServer: $selectedServer
|
||||||
).navigationTitle(location.localizedCountry)
|
).navigationTitle(location.localizedDescription(style: .country))
|
||||||
}, label: {
|
}, label: {
|
||||||
LocationRow(
|
LocationRow(
|
||||||
location: location,
|
location: location,
|
||||||
|
|
|
@ -45,10 +45,10 @@ struct VPNStatusText: View {
|
||||||
|
|
||||||
private extension VPNStatusText {
|
private extension VPNStatusText {
|
||||||
var statusText: String {
|
var statusText: String {
|
||||||
currentVPNState.localizedStatusDescription(
|
currentVPNState.localizedDescription(style: .status(
|
||||||
isActiveProfile: isActiveProfile,
|
isActiveProfile: isActiveProfile,
|
||||||
withErrors: true,
|
withErrors: true,
|
||||||
dataCountIfAvailable: true
|
dataCountIfAvailable: true
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// LocalizableEntity.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 7/4/23.
|
||||||
|
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of Passepartout.
|
||||||
|
//
|
||||||
|
// Passepartout is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Passepartout is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol LocalizableEntity {
|
||||||
|
var localizedDescription: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol StyledLocalizableEntity {
|
||||||
|
associatedtype Style
|
||||||
|
|
||||||
|
func localizedDescription(style: Style) -> String
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol StyledOptionalLocalizableEntity {
|
||||||
|
associatedtype OptionalStyle
|
||||||
|
|
||||||
|
func localizedDescription(optionalStyle: OptionalStyle) -> String?
|
||||||
|
}
|
|
@ -25,8 +25,8 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Validators {
|
public struct Validators {
|
||||||
enum ValidationError: Error {
|
public enum ValidationError: Error {
|
||||||
case notSet
|
case notSet
|
||||||
|
|
||||||
case empty
|
case empty
|
||||||
|
@ -44,20 +44,20 @@ struct Validators {
|
||||||
|
|
||||||
private static let rxWildcardDomainName = NSRegularExpression("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.|\\*\\.)+([A-Za-z]{2,}|\\*)$")
|
private static let rxWildcardDomainName = NSRegularExpression("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.|\\*\\.)+([A-Za-z]{2,}|\\*)$")
|
||||||
|
|
||||||
static func notNil(_ string: String?) throws {
|
public static func notNil(_ string: String?) throws {
|
||||||
guard string != nil else {
|
guard string != nil else {
|
||||||
throw ValidationError.notSet
|
throw ValidationError.notSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func notEmpty(_ string: String) throws {
|
public static func notEmpty(_ string: String) throws {
|
||||||
let valid = string.trimmingCharacters(in: .whitespacesAndNewlines)
|
let valid = string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
guard !valid.isEmpty else {
|
guard !valid.isEmpty else {
|
||||||
throw ValidationError.empty
|
throw ValidationError.empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ipAddress(_ string: String) throws {
|
public static func ipAddress(_ string: String) throws {
|
||||||
var sin = sockaddr_in()
|
var sin = sockaddr_in()
|
||||||
var sin6 = sockaddr_in6()
|
var sin6 = sockaddr_in6()
|
||||||
|
|
||||||
|
@ -68,25 +68,25 @@ struct Validators {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func domainName(_ string: String) throws {
|
public static func domainName(_ string: String) throws {
|
||||||
guard rxDomainName.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)) > 0 else {
|
guard rxDomainName.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)) > 0 else {
|
||||||
throw ValidationError.domainName
|
throw ValidationError.domainName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func wildcardDomainName(_ string: String) throws {
|
public static func wildcardDomainName(_ string: String) throws {
|
||||||
guard rxWildcardDomainName.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)) > 0 else {
|
guard rxWildcardDomainName.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)) > 0 else {
|
||||||
throw ValidationError.wildcardDomainName
|
throw ValidationError.wildcardDomainName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func url(_ string: String) throws {
|
public static func url(_ string: String) throws {
|
||||||
guard URL(string: string) != nil else {
|
guard URL(string: string) != nil else {
|
||||||
throw ValidationError.url
|
throw ValidationError.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func dnsOverTLSServerName(_ string: String) throws {
|
public static func dnsOverTLSServerName(_ string: String) throws {
|
||||||
do {
|
do {
|
||||||
try domainName(string)
|
try domainName(string)
|
||||||
} catch {
|
} catch {
|
Loading…
Reference in New Issue