Compare commits

..

No commits in common. "d3b94aff2cc88a533922e1b5ae452c682c5a0408" and "2e069897db32a845c77319e89b1295edd6d71eee" have entirely different histories.

26 changed files with 947 additions and 454 deletions

View file

@ -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

View file

@ -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] =

View file

@ -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 = "<group>"; };
A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
A06A74772F8E95410093A9E4 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
A06A74782F8E95410093A9E4 /* LICENCE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENCE; sourceTree = "<group>"; };
A06A74792F8E95410093A9E4 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -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 */;
}

View file

@ -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
}

View file

@ -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"
}
}
}

View file

@ -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:

View file

@ -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)

View file

@ -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()
}
}
}
}
}
}

263
dns/Localizable.xcstrings Normal file
View file

@ -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"
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="dns/dns-InfoPlist.xcstrings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="26.4" build-num="17E192"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>dns</source>
<target state="new">dns</target>
<note>Bundle name</note>
</trans-unit>
</body>
</file>
<file original="dns/Localizable.xcstrings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="26.4" build-num="17E192"/>
</header>
<body>
<trans-unit id="" xml:space="preserve">
<source/>
<target state="new"/>
<note/>
</trans-unit>
<trans-unit id="(Coming Soon)" xml:space="preserve">
<source>(Coming Soon)</source>
<target state="translated">(Coming Soon)</target>
<note>Indicates that this feature is not yet implemented but will be soon</note>
</trans-unit>
<trans-unit id="Active" xml:space="preserve">
<source>Active</source>
<target state="translated">Active</target>
<note/>
</trans-unit>
<trans-unit id="Blocklist" xml:space="preserve">
<source>Blocklist</source>
<target state="translated">Blocklist</target>
<note/>
</trans-unit>
<trans-unit id="Connected" xml:space="preserve">
<source>Connected</source>
<target state="translated">Connected</target>
<note/>
</trans-unit>
<trans-unit id="Connection Details" xml:space="preserve">
<source>Connection Details</source>
<target state="translated">Connection Details</target>
<note/>
</trans-unit>
<trans-unit id="DNS Protection" xml:space="preserve">
<source>DNS Protection</source>
<target state="translated">DNS Protection</target>
<note/>
</trans-unit>
<trans-unit id="Domains in blocklist" xml:space="preserve">
<source>Domains in blocklist</source>
<target state="translated">Domains in blocklist</target>
<note/>
</trans-unit>
<trans-unit id="IPv4" xml:space="preserve">
<source>IPv4</source>
<target state="translated">IPv4</target>
<note/>
</trans-unit>
<trans-unit id="IPv6" xml:space="preserve">
<source>IPv6</source>
<target state="translated">IPv6</target>
<note/>
</trans-unit>
<trans-unit id="Inactive" xml:space="preserve">
<source>Inactive</source>
<target state="translated">Inactive</target>
<note/>
</trans-unit>
<trans-unit id="Malware and phishing protection" xml:space="preserve">
<source>Malware and phishing protection</source>
<target state="translated">Malware and phishing protection</target>
<note>Description of the blocklist contents</note>
</trans-unit>
<trans-unit id="No issues detected" xml:space="preserve">
<source>No issues detected</source>
<target state="translated">No issues detected</target>
<note>No current issues detected with the service</note>
</trans-unit>
<trans-unit id="Privacy Policy" xml:space="preserve">
<source>Privacy Policy</source>
<target state="translated">Privacy Policy</target>
<note/>
</trans-unit>
<trans-unit id="Report False Positive" xml:space="preserve">
<source>Report False Positive</source>
<target state="translated">Report False Positive</target>
<note>Link to report that a domain name has been incorrectly blocked</note>
</trans-unit>
<trans-unit id="SR2® Cloud DNS" xml:space="preserve">
<source>SR2® Cloud DNS</source>
<target state="translated">SR2® Cloud DNS</target>
<note/>
</trans-unit>
<trans-unit id="Secure" xml:space="preserve">
<source>Secure</source>
<target state="translated">Secure</target>
<note>Name of the blocklist that only includes malware and security threats</note>
</trans-unit>
<trans-unit id="Secure + Adblock" xml:space="preserve">
<source>Secure + Adblock</source>
<target state="translated">Secure + Adblock</target>
<note>Name of the blocklist that contains “Secure” plus ad blocking</note>
</trans-unit>
<trans-unit id="Security plus ad and tracker blocking" xml:space="preserve">
<source>Security plus ad and tracker blocking</source>
<target state="translated">Security plus ad and tracker blocking</target>
<note>Description of the blocklist contents</note>
</trans-unit>
<trans-unit id="Select the level of protection for your DNS queries" xml:space="preserve">
<source>Select the level of protection for your DNS queries</source>
<target state="translated">Select the level of protection for your DNS queries</target>
<note/>
</trans-unit>
<trans-unit id="Server" xml:space="preserve">
<source>Server</source>
<target state="translated">Server</target>
<note/>
</trans-unit>
<trans-unit id="Service Status" xml:space="preserve">
<source>Service Status</source>
<target state="translated">Service Status</target>
<note/>
</trans-unit>
<trans-unit id="Status" xml:space="preserve">
<source>Status</source>
<target state="translated">Status</target>
<note/>
</trans-unit>
<trans-unit id="Submit incorrectly blocked domains for review" xml:space="preserve">
<source>Submit incorrectly blocked domains for review</source>
<target state="translated">Submit incorrectly blocked domains for review</target>
<note/>
</trans-unit>
<trans-unit id="Terms of Service" xml:space="preserve">
<source>Terms of Service</source>
<target state="translated">Terms of Service</target>
<note/>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

24
dns/Settings.swift Normal file
View file

@ -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)
}
}
}

View file

@ -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"
}
}

View file

@ -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
}
}
}
}

View file

@ -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" = "DNSSchutz";
/* 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:";

View file

@ -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 %@:";