diff --git a/.bartycrouch.toml b/.bartycrouch.toml deleted file mode 100644 index 7da2af3..0000000 --- a/.bartycrouch.toml +++ /dev/null @@ -1,23 +0,0 @@ -[update] -tasks = ["code", "normalize"] - -[update.code] -codePaths = ["."] -localizablePaths = ["dns"] -defaultToKeys = true -additive = false -unstripped = false -plistArguments = false -ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"] - -[update.normalize] -paths = ["dns"] -subpathsToIgnore = ["InfoPlist.strings"] -sourceLocale = "en" -harmonizeWithSource = true -sortByKeys = true - -[lint] -paths = ["dns"] -duplicateKeys = true -emptyValues = true diff --git a/Config.xcconfig b/Config.xcconfig deleted file mode 100644 index fb44ceb..0000000 --- a/Config.xcconfig +++ /dev/null @@ -1,17 +0,0 @@ -// Avoid accidental checkins: -// git update-index --skip-worktree Config.xcconfig -// git update-index --no-skip-worktree Config.xcconfig - - -DEVELOPMENT_TEAM[config=Debug] = -APP_PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = -APP_PROVISIONING_PROFILE_SPECIFIER[config=Debug] = -STICKERS_PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = -STICKERS_PROVISIONING_PROFILE_SPECIFIER[config=Debug] = - - -DEVELOPMENT_TEAM[config=Release] = -APP_PRODUCT_BUNDLE_IDENTIFIER[config=Release] = uk.sr2.dns -APP_PROVISIONING_PROFILE_SPECIFIER[config=Release] = -STICKERS_PRODUCT_BUNDLE_IDENTIFIER[config=Release] = uk.sr2.dns.Stickers -STICKERS_PROVISIONING_PROFILE_SPECIFIER[config=Release] = diff --git a/dns.xcodeproj/project.pbxproj b/dns.xcodeproj/project.pbxproj index 604c92f..bdf3b2a 100644 --- a/dns.xcodeproj/project.pbxproj +++ b/dns.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */ = {isa = PBXBuildFile; productRef = A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */; }; A08264132FC718790077B227 /* Stickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A082640C2FC718790077B227 /* Stickers.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -37,8 +36,6 @@ /* Begin PBXFileReference section */ 069DCCFA2F8C0DCE00F1EB16 /* dns.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dns.app; sourceTree = BUILT_PRODUCTS_DIR; }; - A051B50B2FC9DA9100EACDC0 /* .bartycrouch.toml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .bartycrouch.toml; sourceTree = ""; }; - A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; A06A74772F8E95410093A9E4 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; A06A74782F8E95410093A9E4 /* LICENCE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENCE; sourceTree = ""; }; A06A74792F8E95410093A9E4 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -76,7 +73,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -89,8 +85,6 @@ A06A74772F8E95410093A9E4 /* .gitignore */, A06A74782F8E95410093A9E4 /* LICENCE */, A06A74792F8E95410093A9E4 /* README.md */, - A051B50B2FC9DA9100EACDC0 /* .bartycrouch.toml */, - A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */, 069DCCFC2F8C0DCE00F1EB16 /* dns */, A082640D2FC718790077B227 /* Stickers */, 069DCCFB2F8C0DCE00F1EB16 /* Products */, @@ -113,7 +107,6 @@ isa = PBXNativeTarget; buildConfigurationList = 069DCD052F8C0DCE00F1EB16 /* Build configuration list for PBXNativeTarget "dns" */; buildPhases = ( - A051B50A2FC9DA4300EACDC0 /* BartyCrouch */, 069DCCF62F8C0DCE00F1EB16 /* Sources */, 069DCCF72F8C0DCE00F1EB16 /* Frameworks */, 069DCCF82F8C0DCE00F1EB16 /* Resources */, @@ -129,7 +122,6 @@ ); name = dns; packageProductDependencies = ( - A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */, ); productName = dns; productReference = 069DCCFA2F8C0DCE00F1EB16 /* dns.app */; @@ -163,7 +155,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 2640; - LastUpgradeCheck = 2650; + LastUpgradeCheck = 2640; TargetAttributes = { 069DCCF92F8C0DCE00F1EB16 = { CreatedOnToolsVersion = 26.4; @@ -179,13 +171,9 @@ knownRegions = ( en, Base, - de, ); mainGroup = 069DCCF12F8C0DCD00F1EB16; minimizedProjectReferenceProxies = 1; - packageReferences = ( - A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */, - ); preferredProjectObjectVersion = 77; productRefGroup = 069DCCFB2F8C0DCE00F1EB16 /* Products */; projectDirPath = ""; @@ -214,28 +202,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - A051B50A2FC9DA4300EACDC0 /* BartyCrouch */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = BartyCrouch; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f /opt/homebrew/bin/bartycrouch ]; then\n /opt/homebrew/bin/bartycrouch update -x\n /opt/homebrew/bin/bartycrouch lint -x\nelse\n echo \"warning: BartyCrouch not installed. Download it form https://github.com/Flinesoft/BartyCrouch\"\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 069DCCF62F8C0DCE00F1EB16 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -257,11 +223,9 @@ /* Begin XCBuildConfiguration section */ 069DCD032F8C0DCE00F1EB16 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -323,11 +287,9 @@ }; 069DCD042F8C0DCE00F1EB16 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -386,10 +348,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = dns/dns.entitlements; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "SR2 Cloud DNS"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -405,10 +368,10 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PRODUCT_BUNDLE_IDENTIFIER)"; + PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)"; - STRING_CATALOG_GENERATE_SYMBOLS = NO; + PROVISIONING_PROFILE_SPECIFIER = ""; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -424,10 +387,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = dns/dns.entitlements; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "SR2 Cloud DNS"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; @@ -443,10 +407,10 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PRODUCT_BUNDLE_IDENTIFIER)"; + PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)"; - STRING_CATALOG_GENERATE_SYMBOLS = NO; + PROVISIONING_PROFILE_SPECIFIER = ""; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -460,17 +424,19 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Stickers; INFOPLIST_KEY_NSStickerSharingLevel = OS; IPHONEOS_DEPLOYMENT_TARGET = 26.5; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(STICKERS_PRODUCT_BUNDLE_IDENTIFIER)"; + PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns.Stickers; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "$(STICKERS_PROVISIONING_PROFILE_SPECIFIER)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -482,17 +448,19 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Stickers; INFOPLIST_KEY_NSStickerSharingLevel = OS; IPHONEOS_DEPLOYMENT_TARGET = 26.5; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(STICKERS_PRODUCT_BUNDLE_IDENTIFIER)"; + PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns.Stickers; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "$(STICKERS_PROVISIONING_PROFILE_SPECIFIER)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -531,25 +499,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-async-dns-resolver/"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.7.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */ = { - isa = XCSwiftPackageProductDependency; - package = A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */; - productName = AsyncDNSResolver; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 069DCCF22F8C0DCD00F1EB16 /* Project object */; } diff --git a/dns.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/dns.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 6e3a053..0000000 --- a/dns.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "8d197ac71cfa33d9e3396085f37fbee66795a894a6d74e24ff28bd887e01b9f2", - "pins" : [ - { - "identity" : "swift-async-dns-resolver", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-async-dns-resolver/", - "state" : { - "revision" : "9900d9b4113427a170d5f26075c2128be94984df", - "version" : "0.7.0" - } - } - ], - "version" : 3 -} diff --git a/dns/BlockedCount.swift b/dns/BlockedCount.swift index 3957fdf..24df33f 100644 --- a/dns/BlockedCount.swift +++ b/dns/BlockedCount.swift @@ -1,13 +1,9 @@ import SwiftUI import Foundation -import AsyncDNSResolver struct BlockedCount: View { - @Environment(\.scenePhase) - private var scenePhase - @EnvironmentObject private var viewModel: ViewModel @@ -25,39 +21,53 @@ struct BlockedCount: View { var body: some View { Text(txtRecord) .onAppear { - fetchTxtRecord() + fetchTXTRecord() } .onChange(of: viewModel.blocklist) { _ in - fetchTxtRecord() - } - .onChange(of: scenePhase) { newPhase in - if newPhase == .active { - fetchTxtRecord() - } + fetchTXTRecord() } } - private func fetchTxtRecord() { + func parseResponse(data: Data) -> String? { + // This is a DNS wire format response and we make a lot of assumptions + // It is not critical functionality so just let it fail if it fails + + guard data.count > Self.startIndex else { + return nil + } + + // Find the first space character (ASCII 32). + guard let endIndex = data.suffix(from: Self.startIndex).firstIndex(of: 32) else { + return nil + } + + guard let numberString = String(data: data[Self.startIndex ..< endIndex], encoding: .utf8) + else { + return nil + } + + return Self.formatter.string(for: Int(numberString)) + } + + func fetchTXTRecord() { + let dohURL = URL(string: "https://\(viewModel.blocklist.server)/dns-query?dns=DoQBAAABAAAAAAAABXN0YXRzB2ludmFsaWQAABAAAQ")! + let request = URLRequest(url: dohURL) + + txtRecord = "…" + Task { do { - let resolver = try AsyncDNSResolver() + let (data, _) = try await URLSession.shared.data(for: request) - let records = try await resolver.queryTXT(name: "stats.invalid") - - let text = records.map { $0.txt }.joined() - - if let number = text.split(separator: " ").first, - let intValue = Int(number), - let formatted = Self.formatter.string(for: intValue) - { - txtRecord = formatted + if let count = parseResponse(data: data) { + txtRecord = count } else { - txtRecord = NSLocalizedString("Error", comment: "") + txtRecord = "Error" } } catch { - txtRecord = NSLocalizedString("Error", comment: "") + txtRecord = "Error" } } } diff --git a/dns/BlocklistOption.swift b/dns/BlocklistOption.swift index c67c349..2afffe1 100644 --- a/dns/BlocklistOption.swift +++ b/dns/BlocklistOption.swift @@ -12,15 +12,7 @@ enum BlocklistOption: String, CaseIterable, Identifiable { case secure = "Secure" case securePlusAdblock = "Secure + Adblock" - var id: String { - switch self { - case .secure: - return NSLocalizedString("Secure", comment: "") - - case .securePlusAdblock: - return NSLocalizedString("Secure + Adblock", comment: "") - } - } + var id: String { rawValue } var enabled: Bool { switch self { @@ -34,16 +26,12 @@ enum BlocklistOption: String, CaseIterable, Identifiable { var description: String { switch self { case .secure: - return NSLocalizedString("Malware and phishing protection", comment: "") + return "Malware and phishing protection" case .securePlusAdblock: - return NSLocalizedString("Security plus ad and tracker blocking", comment: "") + return "Security plus ad and tracker blocking" } } - var title: String { - return String(format: "%1$@: %2$@", ViewModel.title, id) - } - var icon: String { switch self { case .secure: diff --git a/dns/BlocklistRow.swift b/dns/BlocklistRow.swift index 9cdec60..8f0b64d 100644 --- a/dns/BlocklistRow.swift +++ b/dns/BlocklistRow.swift @@ -17,7 +17,7 @@ struct BlocklistRow: View { } VStack(alignment: .leading, spacing: 4) { - Text(option.enabled ? option.id : String(format: NSLocalizedString("%@ (Coming Soon)", comment: ""), option.id)) + Text(option.id + " " + (option.enabled ? "" : "(Coming Soon)")) .font(.body) .fontWeight(isSelected ? .semibold : .regular) diff --git a/dns/HomeView.swift b/dns/HomeView.swift index f9295ea..f1df067 100644 --- a/dns/HomeView.swift +++ b/dns/HomeView.swift @@ -3,9 +3,6 @@ import SwiftUI struct HomeView: View { - @Environment(\.scenePhase) - private var scenePhase - @EnvironmentObject private var viewModel: ViewModel @@ -23,9 +20,9 @@ struct HomeView: View { Section { HStack { VStack(alignment: .leading, spacing: 4) { - Text(NSLocalizedString("DNS Protection", comment: "")) + Text("DNS Protection") .font(.headline) - Text(viewModel.isDnsEnabled ? NSLocalizedString("Active", comment: "") : NSLocalizedString("Inactive", comment: "")) + Text(viewModel.isDnsEnabled ? "Active" : "Inactive") .font(.caption) .foregroundStyle(viewModel.isDnsEnabled ? .green : .secondary) } @@ -34,37 +31,11 @@ struct HomeView: View { Toggle("", isOn: $viewModel.isDnsEnabled) .labelsHidden() - .disabled(true) .tint(.green) } .padding(.vertical, 4) } - if !viewModel.isDnsEnabled { - Section { - VStack(alignment: .leading, spacing: 4) { - Text(String(format: NSLocalizedString("To enable %@:", comment: ""), ViewModel.title)) - Text(String(format: NSLocalizedString("%1$@ Tap \"%2$@\"", comment: ""), "–", NSLocalizedString("Open Settings", comment: ""))) - Text(String(format: NSLocalizedString("%1$@ Go to General", comment: ""), "–")) - Text(String(format: NSLocalizedString("%1$@ VPN & Network", comment: ""), "–")) - Text(String(format: NSLocalizedString("%1$@ DNS", comment: ""), "–")) - Text(String(format: NSLocalizedString("%1$@ Select \"%2$@\"", comment: ""), "–", viewModel.blocklist.title)) - - Spacer() - - Button { - if let url = URL(string: UIApplication.openSettingsURLString) { - UIApplication.shared.open(url) - } - } label: { - Text(NSLocalizedString("Open Settings", comment: "")) - .frame(maxWidth: .infinity, minHeight: 32) - } - .buttonStyle(.borderedProminent) - } - } - } - // Blocklist selection Section { ForEach(BlocklistOption.allCases) { option in @@ -82,24 +53,24 @@ struct HomeView: View { .opacity(option.enabled ? 1 : 0.6) } } header: { - Text(NSLocalizedString("Blocklist", comment: "")) + Text("Blocklist") } footer: { - Text(NSLocalizedString("Select the level of protection for your DNS queries", comment: "")) + Text("Select the level of protection for your DNS queries") } // Status section if viewModel.isDnsEnabled { Section { HStack { - Label(NSLocalizedString("Status", comment: ""), systemImage: "checkmark.circle.fill") + Label("Status", systemImage: "checkmark.circle.fill") .foregroundStyle(.green) Spacer() - Text(NSLocalizedString("Connected", comment: "")) + Text("Connected") .foregroundStyle(.secondary) } HStack { - Label(NSLocalizedString("Server", comment: ""), systemImage: "server.rack") + Label("Server", systemImage: "server.rack") Spacer() Text(viewModel.blocklist.server) .foregroundStyle(.secondary) @@ -123,13 +94,13 @@ struct HomeView: View { } HStack { - Label(NSLocalizedString("Domains in blocklist", comment: ""), systemImage: "xmark.shield.fill") + Label("Domains in blocklist", systemImage: "xmark.shield.fill") Spacer() BlockedCount() .foregroundStyle(.secondary) } } header: { - Text(NSLocalizedString("Connection Details", comment: "")) + Text("Connection Details") } } @@ -138,7 +109,7 @@ struct HomeView: View { Link(destination: falsePositiveURL) { HStack { Label { - Text(NSLocalizedString("Report False Positive", comment: "")) + Text("Report False Positive") } icon: { Image(systemName: "exclamationmark.bubble") .foregroundStyle(.orange) @@ -152,7 +123,7 @@ struct HomeView: View { } }.foregroundStyle(.primary) } footer: { - Text(NSLocalizedString("Submit incorrectly blocked domains for review", comment: "")) + Text("Submit incorrectly blocked domains for review") } // Service status section @@ -164,7 +135,7 @@ struct HomeView: View { .frame(width: 12, height: 12) VStack(alignment: .leading, spacing: 4) { - Text(NSLocalizedString("Service Status", comment: "")) + Text("Service Status") .font(.headline) Text(viewModel.summaryStatus.description) .font(.caption) @@ -183,7 +154,7 @@ struct HomeView: View { Link(destination: tosURL) { HStack(spacing: 12) { Image(systemName: "doc.text") - Text(NSLocalizedString("Terms of Service", comment: "")) + Text("Terms of Service") Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) @@ -195,7 +166,7 @@ struct HomeView: View { Link(destination: privacyPolicyURL) { HStack(spacing: 12) { Image(systemName: "doc.text") - Text(NSLocalizedString("Privacy Policy", comment: "")) + Text("Privacy Policy") Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) @@ -205,15 +176,8 @@ struct HomeView: View { }.foregroundStyle(.primary) } } - .navigationTitle(ViewModel.title) + .navigationTitle("SR2® Cloud DNS") .animation(.default, value: viewModel.isDnsEnabled) - .onChange(of: scenePhase) { newPhase in - if newPhase == .active { - Task { - await viewModel.refreshConfig() - } - } - } } } } diff --git a/dns/Localizable.xcstrings b/dns/Localizable.xcstrings new file mode 100644 index 0000000..aeb6242 --- /dev/null +++ b/dns/Localizable.xcstrings @@ -0,0 +1,263 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "" : { + + }, + "(Coming Soon)" : { + "comment" : "Indicates that this feature is not yet implemented but will be soon", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "(Coming Soon)" + } + } + } + }, + "Active" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Active" + } + } + } + }, + "Blocklist" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blocklist" + } + } + } + }, + "Connected" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connected" + } + } + } + }, + "Connection Details" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connection Details" + } + } + } + }, + "DNS Protection" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DNS Protection" + } + } + } + }, + "Domains in blocklist" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Domains in blocklist" + } + } + } + }, + "Inactive" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inactive" + } + } + } + }, + "IPv4" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "IPv4" + } + } + } + }, + "IPv6" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "IPv6" + } + } + } + }, + "Malware and phishing protection" : { + "comment" : "Description of the blocklist contents", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malware and phishing protection" + } + } + } + }, + "No issues detected" : { + "comment" : "No current issues detected with the service", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No issues detected" + } + } + } + }, + "Privacy Policy" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacy Policy" + } + } + } + }, + "Report False Positive" : { + "comment" : "Link to report that a domain name has been incorrectly blocked", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Report False Positive" + } + } + } + }, + "Secure" : { + "comment" : "Name of the blocklist that only includes malware and security threats", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secure" + } + } + } + }, + "Secure + Adblock" : { + "comment" : "Name of the blocklist that contains “Secure” plus ad blocking", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secure + Adblock" + } + } + } + }, + "Security plus ad and tracker blocking" : { + "comment" : "Description of the blocklist contents", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Security plus ad and tracker blocking" + } + } + } + }, + "Select the level of protection for your DNS queries" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select the level of protection for your DNS queries" + } + } + } + }, + "Server" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server" + } + } + } + }, + "Service Status" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Service Status" + } + } + } + }, + "SR2® Cloud DNS" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "SR2® Cloud DNS" + } + } + } + }, + "Status" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" + } + } + } + }, + "Submit incorrectly blocked domains for review" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Submit incorrectly blocked domains for review" + } + } + } + }, + "Terms of Service" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terms of Service" + } + } + } + } + }, + "version" : "1.2" +} \ No newline at end of file diff --git a/dns/Localizations/ar.xcloc/Localized Contents/ar.xliff b/dns/Localizations/ar.xcloc/Localized Contents/ar.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/en.xcloc/Localized Contents/en.xliff b/dns/Localizations/en.xcloc/Localized Contents/en.xliff new file mode 100644 index 0000000..ad7a3b4 --- /dev/null +++ b/dns/Localizations/en.xcloc/Localized Contents/en.xliff @@ -0,0 +1,147 @@ + + + +
+ +
+ + + dns + dns + Bundle name + + +
+ +
+ +
+ + + + + + + + (Coming Soon) + (Coming Soon) + Indicates that this feature is not yet implemented but will be soon + + + Active + Active + + + + Blocklist + Blocklist + + + + Connected + Connected + + + + Connection Details + Connection Details + + + + DNS Protection + DNS Protection + + + + Domains in blocklist + Domains in blocklist + + + + IPv4 + IPv4 + + + + IPv6 + IPv6 + + + + Inactive + Inactive + + + + Malware and phishing protection + Malware and phishing protection + Description of the blocklist contents + + + No issues detected + No issues detected + No current issues detected with the service + + + Privacy Policy + Privacy Policy + + + + Report False Positive + Report False Positive + Link to report that a domain name has been incorrectly blocked + + + SR2® Cloud DNS + SR2® Cloud DNS + + + + Secure + Secure + Name of the blocklist that only includes malware and security threats + + + Secure + Adblock + Secure + Adblock + Name of the blocklist that contains “Secure” plus ad blocking + + + Security plus ad and tracker blocking + Security plus ad and tracker blocking + Description of the blocklist contents + + + Select the level of protection for your DNS queries + Select the level of protection for your DNS queries + + + + Server + Server + + + + Service Status + Service Status + + + + Status + Status + + + + Submit incorrectly blocked domains for review + Submit incorrectly blocked domains for review + + + + Terms of Service + Terms of Service + + + +
+
diff --git a/dns/Localizations/en.xcloc/Source Contents/dns/Localizable.xcstrings b/dns/Localizations/en.xcloc/Source Contents/dns/Localizable.xcstrings new file mode 100644 index 0000000..aeb6242 --- /dev/null +++ b/dns/Localizations/en.xcloc/Source Contents/dns/Localizable.xcstrings @@ -0,0 +1,263 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "" : { + + }, + "(Coming Soon)" : { + "comment" : "Indicates that this feature is not yet implemented but will be soon", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "(Coming Soon)" + } + } + } + }, + "Active" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Active" + } + } + } + }, + "Blocklist" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blocklist" + } + } + } + }, + "Connected" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connected" + } + } + } + }, + "Connection Details" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connection Details" + } + } + } + }, + "DNS Protection" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DNS Protection" + } + } + } + }, + "Domains in blocklist" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Domains in blocklist" + } + } + } + }, + "Inactive" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inactive" + } + } + } + }, + "IPv4" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "IPv4" + } + } + } + }, + "IPv6" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "IPv6" + } + } + } + }, + "Malware and phishing protection" : { + "comment" : "Description of the blocklist contents", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malware and phishing protection" + } + } + } + }, + "No issues detected" : { + "comment" : "No current issues detected with the service", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No issues detected" + } + } + } + }, + "Privacy Policy" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacy Policy" + } + } + } + }, + "Report False Positive" : { + "comment" : "Link to report that a domain name has been incorrectly blocked", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Report False Positive" + } + } + } + }, + "Secure" : { + "comment" : "Name of the blocklist that only includes malware and security threats", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secure" + } + } + } + }, + "Secure + Adblock" : { + "comment" : "Name of the blocklist that contains “Secure” plus ad blocking", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secure + Adblock" + } + } + } + }, + "Security plus ad and tracker blocking" : { + "comment" : "Description of the blocklist contents", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Security plus ad and tracker blocking" + } + } + } + }, + "Select the level of protection for your DNS queries" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select the level of protection for your DNS queries" + } + } + } + }, + "Server" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server" + } + } + } + }, + "Service Status" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Service Status" + } + } + } + }, + "SR2® Cloud DNS" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "SR2® Cloud DNS" + } + } + } + }, + "Status" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" + } + } + } + }, + "Submit incorrectly blocked domains for review" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Submit incorrectly blocked domains for review" + } + } + } + }, + "Terms of Service" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terms of Service" + } + } + } + } + }, + "version" : "1.2" +} \ No newline at end of file diff --git a/dns/Localizations/en.xcloc/Source Contents/dns/dns-InfoPlist.xcstrings b/dns/Localizations/en.xcloc/Source Contents/dns/dns-InfoPlist.xcstrings new file mode 100644 index 0000000..a3786f7 --- /dev/null +++ b/dns/Localizations/en.xcloc/Source Contents/dns/dns-InfoPlist.xcstrings @@ -0,0 +1,18 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "CFBundleName" : { + "comment" : "Bundle name", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "dns" + } + } + } + } + }, + "version" : "1.2" +} \ No newline at end of file diff --git a/dns/Localizations/en.xcloc/contents.json b/dns/Localizations/en.xcloc/contents.json new file mode 100644 index 0000000..2423001 --- /dev/null +++ b/dns/Localizations/en.xcloc/contents.json @@ -0,0 +1,12 @@ +{ + "developmentRegion" : "en", + "project" : "dns.xcodeproj", + "targetLocale" : "en", + "toolInfo" : { + "toolBuildNumber" : "17E192", + "toolID" : "com.apple.dt.xcode", + "toolName" : "Xcode", + "toolVersion" : "26.4" + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/dns/Localizations/fa.xcloc/Localized Contents/fa.xliff b/dns/Localizations/fa.xcloc/Localized Contents/fa.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/fa.xcloc/Localized Contents/fa.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/fr.xcloc/Localized Contents/fr.xliff b/dns/Localizations/fr.xcloc/Localized Contents/fr.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/prs.xcloc/Localized Contents/prs.xliff b/dns/Localizations/prs.xcloc/Localized Contents/prs.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/prs.xcloc/Localized Contents/prs.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/ps.xcloc/Localized Contents/ps.xliff b/dns/Localizations/ps.xcloc/Localized Contents/ps.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/ps.xcloc/Localized Contents/ps.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/ro.xcloc/Localized Contents/ro.xliff b/dns/Localizations/ro.xcloc/Localized Contents/ro.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/ro.xcloc/Localized Contents/ro.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/ru.xcloc/Localized Contents/ru.xliff b/dns/Localizations/ru.xcloc/Localized Contents/ru.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Localizations/tok.xcloc/Localized Contents/tok.xliff b/dns/Localizations/tok.xcloc/Localized Contents/tok.xliff new file mode 100644 index 0000000..93abab9 --- /dev/null +++ b/dns/Localizations/tok.xcloc/Localized Contents/tok.xliff @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dns/Settings.swift b/dns/Settings.swift new file mode 100644 index 0000000..1d4a914 --- /dev/null +++ b/dns/Settings.swift @@ -0,0 +1,24 @@ +// +// Settings.swift +// dns +// +// Created by Benjamin Erhart on 15.04.26. +// + +import Foundation + +class Settings { + + private static let blocklistKey = "blocklist" + + private static let defaults = UserDefaults.standard + + class var blocklist: BlocklistOption { + get { + BlocklistOption(rawValue: defaults.string(forKey: blocklistKey) ?? BlocklistOption.secure.rawValue) ?? .secure + } + set { + defaults.set(newValue.rawValue, forKey: blocklistKey) + } + } +} diff --git a/dns/SummaryStatus.swift b/dns/SummaryStatus.swift index 5cdc15f..971ba0e 100644 --- a/dns/SummaryStatus.swift +++ b/dns/SummaryStatus.swift @@ -23,19 +23,19 @@ enum SummaryStatus: String, Codable, CustomStringConvertible { var description: String { switch self { case .pending: - return NSLocalizedString("Fetching service status", comment: "") + return "Fetching service status" case .ok: - return NSLocalizedString("No issues detected", comment: "") + return "No issues detected" case .notice: - return NSLocalizedString("In maintenance", comment: "") + return "In maintenance" case .disrupted: - return NSLocalizedString("Service disruption", comment: "") + return "Service disruption" case .down: - return NSLocalizedString("Service down", comment: "") + return "Service down" } } diff --git a/dns/ViewModel.swift b/dns/ViewModel.swift index b07ae51..5b331d0 100644 --- a/dns/ViewModel.swift +++ b/dns/ViewModel.swift @@ -12,31 +12,28 @@ import OSLog class ViewModel: NSObject, ObservableObject { - static let title = "SR2® Cloud DNS" - // MARK: Public Properties @Published var blocklist: BlocklistOption = .secure { didSet { - Task { - manager.dnsSettings = blocklist.settings - manager.localizedDescription = blocklist.title + Settings.blocklist = blocklist - do { - try await manager.saveToPreferences() - } - catch { - log.error("Error storing preferences: \(error)") - } - - isDnsEnabled = manager.isEnabled + if isDnsEnabled { + toggleDns() + } + else { + isProgrammaticChange = false } } } @Published - var isDnsEnabled = false + var isDnsEnabled = false { + didSet { + toggleDns() + } + } @Published var summaryStatus: SummaryStatus = .pending @@ -44,6 +41,8 @@ class ViewModel: NSObject, ObservableObject { // MARK: Private Properties + private var isProgrammaticChange = false + private let manager = NEDNSSettingsManager.shared() private let log = Logger(subsystem: String(describing: ViewModel.self), category: String(describing: ViewModel.self)) @@ -52,8 +51,33 @@ class ViewModel: NSObject, ObservableObject { override init() { super.init() + isProgrammaticChange = true + blocklist = Settings.blocklist + Task { - await refreshConfig() + do { + try await manager.loadFromPreferences() + } + catch { + log.error("Error loading preferences: \(error)") + + return + } + + if manager.isEnabled, let settings = manager.dnsSettings { + for dnsServer in BlocklistOption.allCases { + if settings.servers.contains(dnsServer.ipv4) { + await MainActor.run { + isProgrammaticChange = true + blocklist = dnsServer + isProgrammaticChange = true + isDnsEnabled = true + } + + break + } + } + } } Task { @@ -64,30 +88,42 @@ class ViewModel: NSObject, ObservableObject { // MARK: Public Methods - func refreshConfig() async { - do { - try await manager.loadFromPreferences() - } - catch { - log.error("Error loading preferences: \(error)") + func toggleDns() { + guard !isProgrammaticChange else { + // Reset, so next one is recognized as coming from the user again. + isProgrammaticChange = false + + return } - isDnsEnabled = manager.isEnabled + Task { + if isDnsEnabled { + manager.dnsSettings = blocklist.settings + manager.localizedDescription = blocklist.description - if let settings = manager.dnsSettings { - for dnsServer in BlocklistOption.allCases { - if settings.servers.contains(dnsServer.ipv4) { - await MainActor.run { - blocklist = dnsServer - } + do { + try await manager.saveToPreferences() + } + catch { + log.error("Error storing preferences: \(error)") - break + delayedToggle(false) + } + + if !manager.isEnabled { + delayedToggle(false) + } + } + else { + do { + try await manager.removeFromPreferences() + } + catch { + log.error("Error removing preferences: \(error)") + + delayedToggle(true) } } - } - else { - // Trigger `blocklist.didSet` to store right away, so user has something to select. - blocklist = blocklist } } @@ -103,4 +139,18 @@ class ViewModel: NSObject, ObservableObject { log.error("Error while checking status: \(error)") } } + + + // MARK: Private Methods + + private func delayedToggle(_ enabled: Bool) { + Task { + try? await Task.sleep(nanoseconds: 500_000_000) + + await MainActor.run { + isProgrammaticChange = true + isDnsEnabled = enabled + } + } + } } diff --git a/dns/de.lproj/Localizable.strings b/dns/de.lproj/Localizable.strings deleted file mode 100644 index 01a3d6e..0000000 --- a/dns/de.lproj/Localizable.strings +++ /dev/null @@ -1,98 +0,0 @@ -/* No comment provided by engineer. */ -"%1$@ DNS" = "%1$@ DNS"; - -/* No comment provided by engineer. */ -"%1$@ Go to General" = "%1$@ Gehe zu Allgemein"; - -/* No comment provided by engineer. */ -"%1$@ Select \"%2$@\"" = "%1$@ Wähle \"%2$@\""; - -/* No comment provided by engineer. */ -"%1$@ Tap \"%2$@\"" = "%1$@ Tippe auf \"%2$@\""; - -/* No comment provided by engineer. */ -"%1$@ VPN & Network" = "%1$@ VPN und Geräteverwaltung"; - -/* No comment provided by engineer. */ -"%@ (Coming Soon)" = "%@ (bald)"; - -/* No comment provided by engineer. */ -"Active" = "Aktiv"; - -/* No comment provided by engineer. */ -"Blocklist" = "Sperrliste"; - -/* No comment provided by engineer. */ -"Connected" = "Verbunden"; - -/* No comment provided by engineer. */ -"Connection Details" = "Verbindungsdetails"; - -/* No comment provided by engineer. */ -"DNS Protection" = "DNS–Schutz"; - -/* No comment provided by engineer. */ -"Domains in blocklist" = "Domains in Sperrliste"; - -/* No comment provided by engineer. */ -"Error" = "Fehler"; - -/* No comment provided by engineer. */ -"Fetching service status" = "Servicestatus ermitteln"; - -/* No comment provided by engineer. */ -"In maintenance" = "Wartung"; - -/* No comment provided by engineer. */ -"Inactive" = "Inaktiv"; - -/* No comment provided by engineer. */ -"Malware and phishing protection" = "Malware- und Phishingschutz"; - -/* No comment provided by engineer. */ -"No issues detected" = "Keine Probleme erkannt"; - -/* No comment provided by engineer. */ -"Open Settings" = "Öffne Einstellungen"; - -/* No comment provided by engineer. */ -"Privacy Policy" = "Datenschutzerklärung"; - -/* No comment provided by engineer. */ -"Report False Positive" = "Berichte über falsch-positive Sperre"; - -/* No comment provided by engineer. */ -"Secure" = "Sicherheit"; - -/* No comment provided by engineer. */ -"Secure + Adblock" = "Sicherheit + Werbeblocker"; - -/* No comment provided by engineer. */ -"Security plus ad and tracker blocking" = "Sicherheit und Werbe- und Trackerschutz"; - -/* No comment provided by engineer. */ -"Select the level of protection for your DNS queries" = "Wähle das Schutzniveau für Deine DNS-Anfragen"; - -/* No comment provided by engineer. */ -"Server" = "Server"; - -/* No comment provided by engineer. */ -"Service disruption" = "Serviceunterbrechung"; - -/* No comment provided by engineer. */ -"Service down" = "Service unerreichbar"; - -/* No comment provided by engineer. */ -"Service Status" = "Servicestatus"; - -/* No comment provided by engineer. */ -"Status" = "Status"; - -/* No comment provided by engineer. */ -"Submit incorrectly blocked domains for review" = "Falsch blockierte Domains zur Überprüfung einreichen"; - -/* No comment provided by engineer. */ -"Terms of Service" = "Geschäftsbedingungen"; - -/* No comment provided by engineer. */ -"To enable %@:" = "Um %@ zu aktivieren:"; diff --git a/dns/en.lproj/Localizable.strings b/dns/en.lproj/Localizable.strings deleted file mode 100644 index 69eebe5..0000000 --- a/dns/en.lproj/Localizable.strings +++ /dev/null @@ -1,98 +0,0 @@ -/* No comment provided by engineer. */ -"%1$@ DNS" = "%1$@ DNS"; - -/* No comment provided by engineer. */ -"%1$@ Go to General" = "%1$@ Go to General"; - -/* No comment provided by engineer. */ -"%1$@ Select \"%2$@\"" = "%1$@ Select \"%2$@\""; - -/* No comment provided by engineer. */ -"%1$@ Tap \"%2$@\"" = "%1$@ Tap \"%2$@\""; - -/* No comment provided by engineer. */ -"%1$@ VPN & Network" = "%1$@ VPN & Network"; - -/* No comment provided by engineer. */ -"%@ (Coming Soon)" = "%@ (Coming Soon)"; - -/* No comment provided by engineer. */ -"Active" = "Active"; - -/* No comment provided by engineer. */ -"Blocklist" = "Blocklist"; - -/* No comment provided by engineer. */ -"Connected" = "Connected"; - -/* No comment provided by engineer. */ -"Connection Details" = "Connection Details"; - -/* No comment provided by engineer. */ -"DNS Protection" = "DNS Protection"; - -/* No comment provided by engineer. */ -"Domains in blocklist" = "Domains in blocklist"; - -/* No comment provided by engineer. */ -"Error" = "Error"; - -/* No comment provided by engineer. */ -"Fetching service status" = "Fetching service status"; - -/* No comment provided by engineer. */ -"In maintenance" = "In maintenance"; - -/* No comment provided by engineer. */ -"Inactive" = "Inactive"; - -/* No comment provided by engineer. */ -"Malware and phishing protection" = "Malware and phishing protection"; - -/* No comment provided by engineer. */ -"No issues detected" = "No issues detected"; - -/* No comment provided by engineer. */ -"Open Settings" = "Open Settings"; - -/* No comment provided by engineer. */ -"Privacy Policy" = "Privacy Policy"; - -/* No comment provided by engineer. */ -"Report False Positive" = "Report False Positive"; - -/* No comment provided by engineer. */ -"Secure" = "Secure"; - -/* No comment provided by engineer. */ -"Secure + Adblock" = "Secure + Adblock"; - -/* No comment provided by engineer. */ -"Security plus ad and tracker blocking" = "Security plus ad and tracker blocking"; - -/* No comment provided by engineer. */ -"Select the level of protection for your DNS queries" = "Select the level of protection for your DNS queries"; - -/* No comment provided by engineer. */ -"Server" = "Server"; - -/* No comment provided by engineer. */ -"Service disruption" = "Service disruption"; - -/* No comment provided by engineer. */ -"Service down" = "Service down"; - -/* No comment provided by engineer. */ -"Service Status" = "Service Status"; - -/* No comment provided by engineer. */ -"Status" = "Status"; - -/* No comment provided by engineer. */ -"Submit incorrectly blocked domains for review" = "Submit incorrectly blocked domains for review"; - -/* No comment provided by engineer. */ -"Terms of Service" = "Terms of Service"; - -/* No comment provided by engineer. */ -"To enable %@:" = "To enable %@:";