diff --git a/.bartycrouch.toml b/.bartycrouch.toml new file mode 100644 index 0000000..7da2af3 --- /dev/null +++ b/.bartycrouch.toml @@ -0,0 +1,23 @@ +[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 new file mode 100644 index 0000000..fb44ceb --- /dev/null +++ b/Config.xcconfig @@ -0,0 +1,17 @@ +// 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 bdf3b2a..604c92f 100644 --- a/dns.xcodeproj/project.pbxproj +++ b/dns.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ 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 */ @@ -36,6 +37,8 @@ /* 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 = ""; }; @@ -73,6 +76,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -85,6 +89,8 @@ A06A74772F8E95410093A9E4 /* .gitignore */, A06A74782F8E95410093A9E4 /* LICENCE */, A06A74792F8E95410093A9E4 /* README.md */, + A051B50B2FC9DA9100EACDC0 /* .bartycrouch.toml */, + A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */, 069DCCFC2F8C0DCE00F1EB16 /* dns */, A082640D2FC718790077B227 /* Stickers */, 069DCCFB2F8C0DCE00F1EB16 /* Products */, @@ -107,6 +113,7 @@ isa = PBXNativeTarget; buildConfigurationList = 069DCD052F8C0DCE00F1EB16 /* Build configuration list for PBXNativeTarget "dns" */; buildPhases = ( + A051B50A2FC9DA4300EACDC0 /* BartyCrouch */, 069DCCF62F8C0DCE00F1EB16 /* Sources */, 069DCCF72F8C0DCE00F1EB16 /* Frameworks */, 069DCCF82F8C0DCE00F1EB16 /* Resources */, @@ -122,6 +129,7 @@ ); name = dns; packageProductDependencies = ( + A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */, ); productName = dns; productReference = 069DCCFA2F8C0DCE00F1EB16 /* dns.app */; @@ -155,7 +163,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 2640; - LastUpgradeCheck = 2640; + LastUpgradeCheck = 2650; TargetAttributes = { 069DCCF92F8C0DCE00F1EB16 = { CreatedOnToolsVersion = 26.4; @@ -171,9 +179,13 @@ knownRegions = ( en, Base, + de, ); mainGroup = 069DCCF12F8C0DCD00F1EB16; minimizedProjectReferenceProxies = 1; + packageReferences = ( + A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */, + ); preferredProjectObjectVersion = 77; productRefGroup = 069DCCFB2F8C0DCE00F1EB16 /* Products */; projectDirPath = ""; @@ -202,6 +214,28 @@ }; /* 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; @@ -223,9 +257,11 @@ /* 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"; @@ -287,9 +323,11 @@ }; 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"; @@ -348,11 +386,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = dns/dns.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; 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"; @@ -368,10 +405,10 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - STRING_CATALOG_GENERATE_SYMBOLS = YES; + PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -387,11 +424,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = dns/dns.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; 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"; @@ -407,10 +443,10 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns; + PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - STRING_CATALOG_GENERATE_SYMBOLS = YES; + PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -424,19 +460,17 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; 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 = uk.sr2.dns.Stickers; + PRODUCT_BUNDLE_IDENTIFIER = "$(STICKERS_PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "$(STICKERS_PROVISIONING_PROFILE_SPECIFIER)"; SKIP_INSTALL = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -448,19 +482,17 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; 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 = uk.sr2.dns.Stickers; + PRODUCT_BUNDLE_IDENTIFIER = "$(STICKERS_PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "$(STICKERS_PROVISIONING_PROFILE_SPECIFIER)"; SKIP_INSTALL = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -499,6 +531,25 @@ 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 new file mode 100644 index 0000000..6e3a053 --- /dev/null +++ b/dns.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "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 24df33f..3957fdf 100644 --- a/dns/BlockedCount.swift +++ b/dns/BlockedCount.swift @@ -1,9 +1,13 @@ import SwiftUI import Foundation +import AsyncDNSResolver struct BlockedCount: View { + @Environment(\.scenePhase) + private var scenePhase + @EnvironmentObject private var viewModel: ViewModel @@ -21,53 +25,39 @@ struct BlockedCount: View { var body: some View { Text(txtRecord) .onAppear { - fetchTXTRecord() + fetchTxtRecord() } .onChange(of: viewModel.blocklist) { _ in - fetchTXTRecord() + fetchTxtRecord() + } + .onChange(of: scenePhase) { newPhase in + if newPhase == .active { + 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 = "…" - + private func fetchTxtRecord() { Task { do { - let (data, _) = try await URLSession.shared.data(for: request) + let resolver = try AsyncDNSResolver() - if let count = parseResponse(data: data) { - txtRecord = count + 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 } else { - txtRecord = "Error" + txtRecord = NSLocalizedString("Error", comment: "") } } catch { - txtRecord = "Error" + txtRecord = NSLocalizedString("Error", comment: "") } } } diff --git a/dns/BlocklistOption.swift b/dns/BlocklistOption.swift index 2afffe1..c67c349 100644 --- a/dns/BlocklistOption.swift +++ b/dns/BlocklistOption.swift @@ -12,7 +12,15 @@ enum BlocklistOption: String, CaseIterable, Identifiable { case secure = "Secure" case securePlusAdblock = "Secure + Adblock" - var id: String { rawValue } + var id: String { + switch self { + case .secure: + return NSLocalizedString("Secure", comment: "") + + case .securePlusAdblock: + return NSLocalizedString("Secure + Adblock", comment: "") + } + } var enabled: Bool { switch self { @@ -26,12 +34,16 @@ enum BlocklistOption: String, CaseIterable, Identifiable { var description: String { switch self { case .secure: - return "Malware and phishing protection" + return NSLocalizedString("Malware and phishing protection", comment: "") case .securePlusAdblock: - return "Security plus ad and tracker blocking" + return NSLocalizedString("Security plus ad and tracker blocking", comment: "") } } + 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 8f0b64d..9cdec60 100644 --- a/dns/BlocklistRow.swift +++ b/dns/BlocklistRow.swift @@ -17,7 +17,7 @@ struct BlocklistRow: View { } VStack(alignment: .leading, spacing: 4) { - Text(option.id + " " + (option.enabled ? "" : "(Coming Soon)")) + Text(option.enabled ? option.id : String(format: NSLocalizedString("%@ (Coming Soon)", comment: ""), option.id)) .font(.body) .fontWeight(isSelected ? .semibold : .regular) diff --git a/dns/HomeView.swift b/dns/HomeView.swift index f1df067..f9295ea 100644 --- a/dns/HomeView.swift +++ b/dns/HomeView.swift @@ -3,6 +3,9 @@ import SwiftUI struct HomeView: View { + @Environment(\.scenePhase) + private var scenePhase + @EnvironmentObject private var viewModel: ViewModel @@ -20,9 +23,9 @@ struct HomeView: View { Section { HStack { VStack(alignment: .leading, spacing: 4) { - Text("DNS Protection") + Text(NSLocalizedString("DNS Protection", comment: "")) .font(.headline) - Text(viewModel.isDnsEnabled ? "Active" : "Inactive") + Text(viewModel.isDnsEnabled ? NSLocalizedString("Active", comment: "") : NSLocalizedString("Inactive", comment: "")) .font(.caption) .foregroundStyle(viewModel.isDnsEnabled ? .green : .secondary) } @@ -31,11 +34,37 @@ 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 @@ -53,24 +82,24 @@ struct HomeView: View { .opacity(option.enabled ? 1 : 0.6) } } header: { - Text("Blocklist") + Text(NSLocalizedString("Blocklist", comment: "")) } footer: { - Text("Select the level of protection for your DNS queries") + Text(NSLocalizedString("Select the level of protection for your DNS queries", comment: "")) } // Status section if viewModel.isDnsEnabled { Section { HStack { - Label("Status", systemImage: "checkmark.circle.fill") + Label(NSLocalizedString("Status", comment: ""), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) Spacer() - Text("Connected") + Text(NSLocalizedString("Connected", comment: "")) .foregroundStyle(.secondary) } HStack { - Label("Server", systemImage: "server.rack") + Label(NSLocalizedString("Server", comment: ""), systemImage: "server.rack") Spacer() Text(viewModel.blocklist.server) .foregroundStyle(.secondary) @@ -94,13 +123,13 @@ struct HomeView: View { } HStack { - Label("Domains in blocklist", systemImage: "xmark.shield.fill") + Label(NSLocalizedString("Domains in blocklist", comment: ""), systemImage: "xmark.shield.fill") Spacer() BlockedCount() .foregroundStyle(.secondary) } } header: { - Text("Connection Details") + Text(NSLocalizedString("Connection Details", comment: "")) } } @@ -109,7 +138,7 @@ struct HomeView: View { Link(destination: falsePositiveURL) { HStack { Label { - Text("Report False Positive") + Text(NSLocalizedString("Report False Positive", comment: "")) } icon: { Image(systemName: "exclamationmark.bubble") .foregroundStyle(.orange) @@ -123,7 +152,7 @@ struct HomeView: View { } }.foregroundStyle(.primary) } footer: { - Text("Submit incorrectly blocked domains for review") + Text(NSLocalizedString("Submit incorrectly blocked domains for review", comment: "")) } // Service status section @@ -135,7 +164,7 @@ struct HomeView: View { .frame(width: 12, height: 12) VStack(alignment: .leading, spacing: 4) { - Text("Service Status") + Text(NSLocalizedString("Service Status", comment: "")) .font(.headline) Text(viewModel.summaryStatus.description) .font(.caption) @@ -154,7 +183,7 @@ struct HomeView: View { Link(destination: tosURL) { HStack(spacing: 12) { Image(systemName: "doc.text") - Text("Terms of Service") + Text(NSLocalizedString("Terms of Service", comment: "")) Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) @@ -166,7 +195,7 @@ struct HomeView: View { Link(destination: privacyPolicyURL) { HStack(spacing: 12) { Image(systemName: "doc.text") - Text("Privacy Policy") + Text(NSLocalizedString("Privacy Policy", comment: "")) Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) @@ -176,8 +205,15 @@ struct HomeView: View { }.foregroundStyle(.primary) } } - .navigationTitle("SR2® Cloud DNS") + .navigationTitle(ViewModel.title) .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 deleted file mode 100644 index aeb6242..0000000 --- a/dns/Localizable.xcstrings +++ /dev/null @@ -1,263 +0,0 @@ -{ - "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 deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/ar.xcloc/Localized Contents/ar.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/en.xcloc/Localized Contents/en.xliff b/dns/Localizations/en.xcloc/Localized Contents/en.xliff deleted file mode 100644 index ad7a3b4..0000000 --- a/dns/Localizations/en.xcloc/Localized Contents/en.xliff +++ /dev/null @@ -1,147 +0,0 @@ - - - -
- -
- - - 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 deleted file mode 100644 index aeb6242..0000000 --- a/dns/Localizations/en.xcloc/Source Contents/dns/Localizable.xcstrings +++ /dev/null @@ -1,263 +0,0 @@ -{ - "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 deleted file mode 100644 index a3786f7..0000000 --- a/dns/Localizations/en.xcloc/Source Contents/dns/dns-InfoPlist.xcstrings +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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 deleted file mode 100644 index 2423001..0000000 --- a/dns/Localizations/en.xcloc/contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "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 deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/fa.xcloc/Localized Contents/fa.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/fr.xcloc/Localized Contents/fr.xliff b/dns/Localizations/fr.xcloc/Localized Contents/fr.xliff deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/fr.xcloc/Localized Contents/fr.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/prs.xcloc/Localized Contents/prs.xliff b/dns/Localizations/prs.xcloc/Localized Contents/prs.xliff deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/prs.xcloc/Localized Contents/prs.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/ps.xcloc/Localized Contents/ps.xliff b/dns/Localizations/ps.xcloc/Localized Contents/ps.xliff deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/ps.xcloc/Localized Contents/ps.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/ro.xcloc/Localized Contents/ro.xliff b/dns/Localizations/ro.xcloc/Localized Contents/ro.xliff deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/ro.xcloc/Localized Contents/ro.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/ru.xcloc/Localized Contents/ru.xliff b/dns/Localizations/ru.xcloc/Localized Contents/ru.xliff deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/ru.xcloc/Localized Contents/ru.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Localizations/tok.xcloc/Localized Contents/tok.xliff b/dns/Localizations/tok.xcloc/Localized Contents/tok.xliff deleted file mode 100644 index 93abab9..0000000 --- a/dns/Localizations/tok.xcloc/Localized Contents/tok.xliff +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/dns/Settings.swift b/dns/Settings.swift deleted file mode 100644 index 1d4a914..0000000 --- a/dns/Settings.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// 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 971ba0e..5cdc15f 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 "Fetching service status" + return NSLocalizedString("Fetching service status", comment: "") case .ok: - return "No issues detected" + return NSLocalizedString("No issues detected", comment: "") case .notice: - return "In maintenance" + return NSLocalizedString("In maintenance", comment: "") case .disrupted: - return "Service disruption" + return NSLocalizedString("Service disruption", comment: "") case .down: - return "Service down" + return NSLocalizedString("Service down", comment: "") } } diff --git a/dns/ViewModel.swift b/dns/ViewModel.swift index 5b331d0..b07ae51 100644 --- a/dns/ViewModel.swift +++ b/dns/ViewModel.swift @@ -12,28 +12,31 @@ import OSLog class ViewModel: NSObject, ObservableObject { + static let title = "SR2® Cloud DNS" + // MARK: Public Properties @Published var blocklist: BlocklistOption = .secure { didSet { - Settings.blocklist = blocklist + Task { + manager.dnsSettings = blocklist.settings + manager.localizedDescription = blocklist.title - if isDnsEnabled { - toggleDns() - } - else { - isProgrammaticChange = false + do { + try await manager.saveToPreferences() + } + catch { + log.error("Error storing preferences: \(error)") + } + + isDnsEnabled = manager.isEnabled } } } @Published - var isDnsEnabled = false { - didSet { - toggleDns() - } - } + var isDnsEnabled = false @Published var summaryStatus: SummaryStatus = .pending @@ -41,8 +44,6 @@ 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)) @@ -51,33 +52,8 @@ class ViewModel: NSObject, ObservableObject { override init() { super.init() - isProgrammaticChange = true - blocklist = Settings.blocklist - Task { - 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 - } - } - } + await refreshConfig() } Task { @@ -88,43 +64,31 @@ class ViewModel: NSObject, ObservableObject { // MARK: Public Methods - func toggleDns() { - guard !isProgrammaticChange else { - // Reset, so next one is recognized as coming from the user again. - isProgrammaticChange = false - - return + func refreshConfig() async { + do { + try await manager.loadFromPreferences() + } + catch { + log.error("Error loading preferences: \(error)") } - Task { - if isDnsEnabled { - manager.dnsSettings = blocklist.settings - manager.localizedDescription = blocklist.description + isDnsEnabled = manager.isEnabled - do { - try await manager.saveToPreferences() - } - catch { - log.error("Error storing preferences: \(error)") + if let settings = manager.dnsSettings { + for dnsServer in BlocklistOption.allCases { + if settings.servers.contains(dnsServer.ipv4) { + await MainActor.run { + blocklist = dnsServer + } - delayedToggle(false) - } - - if !manager.isEnabled { - delayedToggle(false) - } - } - else { - do { - try await manager.removeFromPreferences() - } - catch { - log.error("Error removing preferences: \(error)") - - delayedToggle(true) + break } } } + else { + // Trigger `blocklist.didSet` to store right away, so user has something to select. + blocklist = blocklist + } } func fetchServerStatus() async { @@ -139,18 +103,4 @@ 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 new file mode 100644 index 0000000..01a3d6e --- /dev/null +++ b/dns/de.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* 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 new file mode 100644 index 0000000..69eebe5 --- /dev/null +++ b/dns/en.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* 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 %@:";