summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/Assets/Localizable.xcstrings1086
-rw-r--r--ios/MullvadTypes/TransportLayer.swift9
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj18
-rw-r--r--ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift5
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift18
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel.swift30
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/DAITA/SettingsDAITAView.swift88
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift11
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideStatus.swift10
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift7
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift3
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/Views/SettingsInfoView.swift7
-rw-r--r--ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift29
-rw-r--r--ios/MullvadVPN/Extensions/String+Helpers.swift7
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift6
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift2
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedContentView.swift14
-rw-r--r--ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift12
-rw-r--r--ios/MullvadVPN/View controllers/DeviceList/DeviceManagementView.swift30
-rw-r--r--ios/MullvadVPN/View controllers/Login/LoginViewController.swift14
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift25
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift6
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift19
-rw-r--r--ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift13
-rw-r--r--ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift4
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/DAITAInfoView.swift21
-rw-r--r--ios/MullvadVPN/View controllers/Settings/Obfuscation/ShadowsocksObfuscationSettingsView.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift7
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift7
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDataSourceDelegate.swift1
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsInfoButtonItem.swift42
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift20
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift5
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift4
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift101
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift6
-rw-r--r--ios/MullvadVPN/Views/MullvadAlert.swift22
42 files changed, 576 insertions, 1147 deletions
diff --git a/ios/Assets/Localizable.xcstrings b/ios/Assets/Localizable.xcstrings
index ca2823228f..373b321c21 100644
--- a/ios/Assets/Localizable.xcstrings
+++ b/ios/Assets/Localizable.xcstrings
@@ -23,68 +23,40 @@
}
}
},
- "**Attention: This increases network traffic and will also negatively affect speed, latency, and battery usage. Use with caution on limited plans.**\n\nDAITA (Defense against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic.\n\nBy using sophisticated AI it’s possible to analyze the traffic of data packets going in and out of your device (even if the traffic is encrypted)." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "**Achtung: Dies erhöht den Netzwerkverkehr und wirkt sich auch negativ auf Geschwindigkeit, Latenz und Akkuverbrauch aus. Verwende mit Vorsicht auf begrenzte Tarife. *\n\nDAITA (Verteidigung gegen AI-geführte Verkehrsanalyse) versteckt Muster in deinem verschlüsselten VPN-Verkehr.\n\nDurch die Verwendung einer ausgefeilten KI ist es möglich, den Datenverkehr von Datenpaketen zu analysieren, die auf Ihrem Gerät ein- und auslaufen (selbst wenn der Datenverkehr verschlüsselt ist)."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "**Attention : Ceci augmente le trafic réseau et affectera également la vitesse, la latence et l'utilisation de la batterie. À utiliser avec prudence sur des plans limités. *\n\nDAITA (Défense contre l'analyse du trafic guidée par AI) masque les patrons dans votre trafic VPN chiffré.\n\nEn utilisant une IA sophistiquée, il est possible d'analyser le trafic des paquets de données entrants et sortants de votre appareil (même si le trafic est chiffré)."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "**Uppmärksamhet: Detta ökar nätverkstrafiken och kommer också att påverka hastighet, latens och batterianvändning. Använd med försiktighet på begränsade planer. *\n\nDAITA (Försvar mot AI-styrda Trafikanalys) döljer mönster i din krypterade VPN-trafik.\n\nGenom att använda sofistikerad AI är det möjligt att analysera trafiken av datapaket som går in och ut från din enhet (även om trafiken är krypterad)."
- }
- }
- }
+ "**Attention: This increases network traffic and will also negatively affect speed, latency, and battery usage. Use with caution on limited plans.**" : {
+
},
- "**Tap here** to see what’s new." : {
+ "**Tap here** to see what’s new" : {
+
+ },
+ "%@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
- "value" : "**Tippe hier** um zu sehen, was neu ist."
+ "value" : "%@"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
- "value" : "**Tapotez ici** pour voir les nouveautés."
+ "value" : "%@"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
- "value" : "**Tryck här** för att se vad som är nytt."
+ "value" : "%@"
}
}
}
},
- "%@" : {
+ "%@ (%@) hides patterns in your encrypted VPN traffic." : {
"localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "%@"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "%@"
- }
- },
- "sv" : {
+ "en" : {
"stringUnit" : {
- "state" : "translated",
- "value" : "%@"
+ "state" : "new",
+ "value" : "%1$@ (%2$@) hides patterns in your encrypted VPN traffic."
}
}
}
@@ -111,6 +83,9 @@
}
}
},
+ "%@ does this by carefully adding network noise and making all network packets the same size." : {
+
+ },
"%@ have been added to your account" : {
"localizations" : {
"de" : {
@@ -155,6 +130,16 @@
}
}
},
+ "%@ via %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@ via %2$@"
+ }
+ }
+ }
+ },
"%@ were added to your account." : {
"localizations" : {
"de" : {
@@ -177,6 +162,19 @@
}
}
},
+ "%@, %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%1$@, %2$@"
+ }
+ }
+ }
+ },
+ "%@: Multihop" : {
+
+ },
"%d more..." : {
"localizations" : {
"de" : {
@@ -199,6 +197,9 @@
}
}
},
+ "%lld more..." : {
+
+ },
"A custom list with this name exists, please choose a unique name." : {
"localizations" : {
"de" : {
@@ -463,6 +464,9 @@
}
}
},
+ "Active features" : {
+
+ },
"Add" : {
"localizations" : {
"de" : {
@@ -705,6 +709,9 @@
}
}
},
+ "Agree and continue" : {
+
+ },
"All" : {
"localizations" : {
"de" : {
@@ -815,28 +822,6 @@
}
}
},
- "API could not be reached, save anyway?" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "API konnte nicht erreicht werden, trotzdem speichern?"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "L'API n'a pas pu être atteinte, enregistrer quand même ?"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "API kunde inte nås, spara ändå?"
- }
- }
- }
- },
"API reachable" : {
"localizations" : {
"de" : {
@@ -881,6 +866,9 @@
}
}
},
+ "API unreachable, save anyway?" : {
+
+ },
"App logs" : {
"localizations" : {
"de" : {
@@ -925,6 +913,9 @@
}
}
},
+ "Are you sure you want to log %@ out?" : {
+
+ },
"At least one method needs to be enabled." : {
"localizations" : {
"de" : {
@@ -947,6 +938,12 @@
}
}
},
+ "Attention: this setting cannot be used in combination with **“%@“**." : {
+
+ },
+ "Attention: toggling “Local network sharing” requires restarting the VPN connection." : {
+
+ },
"Authentication" : {
"localizations" : {
"de" : {
@@ -1079,49 +1076,18 @@
}
}
},
- "By enabling \"Direct only\" you will have to manually select a server that is DAITA-enabled. Multihop won't automatically be used to enable DAITA with any server." : {
+ "By enabling “%@” you will have to manually select a server that is %@-enabled. %@ won't automatically be used to enable DAITA with any server." : {
"localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Wenn Sie \"Nur Direkt\" aktivieren, müssen Sie manuell einen Server auswählen, der DAITA-aktiviert ist. Multihop wird nicht automatisch verwendet, um DAITA mit irgendeinem Server zu aktivieren."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "En activant \"Direct only\", vous devrez sélectionner manuellement un serveur qui est activé par DAITA. Multihop ne sera pas automatiquement utilisé pour activer DAITA avec aucun serveur."
- }
- },
- "sv" : {
+ "en" : {
"stringUnit" : {
- "state" : "translated",
- "value" : "Genom att aktivera \"Direct only\" måste du manuellt välja en server som är DAITA-aktiverad. Multihop används inte automatiskt för att aktivera DAITA med någon server."
+ "state" : "new",
+ "value" : "By enabling “%1$@” you will have to manually select a server that is %2$@-enabled. %3$@ won't automatically be used to enable DAITA with any server."
}
}
}
},
- "By enabling \"Direct only\" you will have to manually select a server that is DAITA-enabled. This can cause you to end up in a blocked state until you have selected a compatible server in the \"Select location\" view." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Wenn Sie \"Nur Direkt\" aktivieren, müssen Sie manuell einen Server auswählen, der DAITA-aktiviert ist. Dies kann dazu führen, dass Sie in einem blockierten Zustand enden, bis Sie einen kompatiblen Server in der Ansicht \"Standort auswählen\" ausgewählt haben."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "En activant \"Direct only\", vous devrez sélectionner manuellement un serveur qui est activé par DAITA. Cela peut vous faire tomber dans un état bloqué jusqu'à ce que vous ayez sélectionné un serveur compatible dans la vue « Sélectionner un emplacement »."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Genom att aktivera \"Direct only\" måste du manuellt välja en server som är DAITA-aktiverad. Detta kan leda till att du hamnar i ett blockerat tillstånd tills du har valt en kompatibel server i \"Välj plats\"-vyn."
- }
- }
- }
+ "By using sophisticated AI it’s possible to analyze the traffic of data packets going in and out of your device (even if the traffic is encrypted)." : {
+
},
"Cancel" : {
"localizations" : {
@@ -1387,6 +1353,9 @@
}
}
},
+ "Collapse %@" : {
+
+ },
"Collapses this location." : {
"localizations" : {
"de" : {
@@ -1431,6 +1400,9 @@
}
}
},
+ "Connect" : {
+
+ },
"Connected" : {
"localizations" : {
"de" : {
@@ -1453,6 +1425,35 @@
}
}
},
+ "Connected to %@, %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Connected to %1$@, %2$@"
+ }
+ }
+ }
+ },
+ "Connecting to %@, %@" : {
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Connecting to %1$@, %2$@"
+ }
+ }
+ }
+ },
+ "Connecting..." : {
+
+ },
+ "Connection details" : {
+
+ },
+ "Continue with login" : {
+
+ },
"Copy to pasteboard" : {
"localizations" : {
"de" : {
@@ -1544,6 +1545,9 @@
"Create new account" : {
},
+ "Created: %@" : {
+
+ },
"Creating new account" : {
"localizations" : {
"de" : {
@@ -1610,6 +1614,9 @@
}
}
},
+ "Current device" : {
+
+ },
"Custom" : {
"localizations" : {
"de" : {
@@ -1698,22 +1705,6 @@
}
}
},
- "DAITA (Defence against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting.\nIt does this by carefully adding network noise and making all network packets the same size.\nNot all our servers are DAITA-enabled. Therefore, we use multihop automatically to enable DAITA with any server.\nAttention: Be cautious if you have a limited data plan as this feature will increase your network traffic." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "DAITA (Verteidigung gegen AI-geführte Verkehrsanalyse) versteckt Muster in Ihrem verschlüsselten VPN-Verkehr. Wenn jemand Ihre Verbindung überwacht, erschwert dies es ihnen, deutlich zu erkennen, welche Websites Sie besuchen.\nDies geschieht durch das sorgfältige Hinzufügen von Netzwerkrauschen und die gleiche Größe aller Netzwerkpakete.\nNicht alle unsere Server sind DAITA-fähig. Daher verwenden wir Multihop automatisch, um DAITA mit jedem Server zu aktivieren.\nAchtung: Seien Sie vorsichtig, wenn Sie einen begrenzten Datenplan haben, da diese Funktion Ihren Netzwerkverkehr erhöhen wird."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "DAITA (Defence against AI-guided Traffic Analysis) döljer mönster i din krypterade VPN-trafik. Om någon övervakar din anslutning, detta gör det betydligt svårare för dem att identifiera vilka webbplatser du besöker.\nDet gör detta genom att noggrant lägga till nätverksljud och göra alla nätverkspaket till samma storlek.\nAlla våra servrar är inte DAITA-aktiverade. Därför använder vi multihop automatiskt för att aktivera DAITA med alla servrar.\nObservera: Var försiktig om du har en begränsad dataplan eftersom denna funktion kommer att öka din nätverkstrafik."
- }
- }
- }
- },
"DAITA isn't available at the currently selected location. After enabling, please go to the \"Select location\" view and select a location that supports DAITA." : {
"localizations" : {
"de" : {
@@ -1781,6 +1772,7 @@
}
},
"DAITA: Multihop" : {
+ "extractionState" : "stale",
"localizations" : {
"de" : {
"stringUnit" : {
@@ -1802,6 +1794,9 @@
}
}
},
+ "Defense against AI-guided Traffic Analysis" : {
+
+ },
"Delete" : {
"localizations" : {
"de" : {
@@ -1824,6 +1819,9 @@
}
}
},
+ "Delete %@?" : {
+
+ },
"Delete account" : {
"localizations" : {
"de" : {
@@ -1847,26 +1845,7 @@
}
},
"Delete Account" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Konto löschen"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Supprimer le compte"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Ta bort konto"
- }
- }
- }
+
},
"Delete list" : {
"localizations" : {
@@ -2044,27 +2023,8 @@
}
}
},
- "Disable all content blockers to activate this setting." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Deaktivieren Sie alle Inhaltsblocker, um diese Einstellung zu aktivieren."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Désactiver tous les bloqueurs de contenu pour activer ce paramètre."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Inaktivera alla innehållsblockerare för att aktivera denna inställning."
- }
- }
- }
+ "Disable all \"%@\" above to activate this setting." : {
+
},
"Disabled" : {
"localizations" : {
@@ -2154,6 +2114,9 @@
}
}
},
+ "Disconnected" : {
+
+ },
"Disconnecting" : {
"localizations" : {
"de" : {
@@ -2176,6 +2139,9 @@
}
}
},
+ "Disconnecting..." : {
+
+ },
"DNS content blockers" : {
"localizations" : {
"de" : {
@@ -2220,6 +2186,15 @@
}
}
},
+ "Do you agree to remaining anonymous?" : {
+
+ },
+ "Do you want to create a new account?" : {
+
+ },
+ "Do you want to delete the list **%@**?" : {
+
+ },
"Don’t have an account number?" : {
"localizations" : {
"de" : {
@@ -2352,6 +2327,12 @@
}
}
},
+ "Either buy credit on our website or make an in-app purchase via the **Add time** button below." : {
+
+ },
+ "Either buy credit on our website or redeem a voucher." : {
+
+ },
"Enable" : {
"localizations" : {
"de" : {
@@ -2572,6 +2553,9 @@
}
}
},
+ "Expand %@" : {
+
+ },
"Expands this location." : {
"localizations" : {
"de" : {
@@ -2781,26 +2765,10 @@
}
},
"Fetching devices..." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Geräte werden abgerufen..."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Récupération des appareils..."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Hämtar enheter..."
- }
- }
- }
+
+ },
+ "file" : {
+
},
"Filter" : {
"localizations" : {
@@ -2868,21 +2836,8 @@
}
}
},
- "Go ahead and start using the app to begin reclaiming your online privacy.\nTo continue your journey as a privacy ninja, visit our website to pick up other privacy-friendly habits and tools." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Gehen Sie voran und verwenden Sie die App, um Ihre Privatsphäre wiederherzustellen.\nUm Ihre Reise als privates Ninja fortzusetzen, besuchen Sie unsere Website, um andere datenschutzfreundliche Gewohnheiten und Werkzeuge zu übernehmen."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Gå vidare och börja använda appen för att börja återta din integritet online.\nFör att fortsätta din resa som privatliv ninja, besök vår hemsida för att plocka upp andra integritetsvänliga vanor och verktyg."
- }
- }
- }
+ "Go ahead and start using the app to begin reclaiming your online privacy." : {
+
},
"Go to login" : {
"localizations" : {
@@ -2994,27 +2949,8 @@
}
}
},
- "If an observer monitors these data packets, DAITA makes it significantly harder for them to identify which websites you are visiting or with whom you are communicating.\n\nDAITA does this by carefully adding network noise and making all network packets the same size.\n\nNot all our servers are DAITA-enabled. Therefore, we use multihop automatically to enable DAITA with any server.\n" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Wenn ein Beobachter diese Datenpakete überwacht, DAITA macht es für sie wesentlich schwieriger, herauszufinden, welche Websites Sie besuchen oder mit wem Sie kommunizieren.\n\nDAITA tut dies, indem es Netzwerkrauschen einfügt und alle Netzwerkpakete auf die gleiche Größe bringt.\n\nNicht alle unsere Server sind DAITA-fähig. Daher verwenden wir Multihop automatisch, um DAITA mit jedem Server zu aktivieren.\n"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Si un observateur surveille ces paquets de données, DAITA leur rend beaucoup plus difficile d'identifier les sites Web que vous visitez ou avec lesquels vous communiquez.\n\nDAITA le fait en ajoutant soigneusement du bruit du réseau et en faisant de tous les paquets du réseau la même taille.\n\nTous nos serveurs ne sont pas activés par DAITA. Par conséquent, nous utilisons le multilien automatiquement pour activer DAITA avec n'importe quel serveur.\n"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Om en observatör övervakar dessa datapaket, DAITA gör det betydligt svårare för dem att identifiera vilka webbplatser du besöker eller med vilka du kommunicerar.\n\nDAITA gör detta genom att noggrant lägga till nätverksljud och göra alla nätverkspaket till samma storlek.\n\nAlla våra servrar är inte DAITA-aktiverade. Därför använder vi multihop automatiskt för att aktivera DAITA med alla servrar.\n"
- }
- }
- }
+ "If an observer monitors these data packets, %@ makes it significantly harder for them to identify which websites you are visiting or with whom you are communicating." : {
+
},
"If needed we will contact you at %@" : {
"localizations" : {
@@ -3038,6 +2974,9 @@
}
}
},
+ "If so, click log out below to log in with the other account number." : {
+
+ },
"If you are having issues connecting to VPN servers, please contact support." : {
"localizations" : {
"de" : {
@@ -3060,21 +2999,8 @@
}
}
},
- "If you are not connected to our VPN, then the Encrypted DNS proxy will use your own non-VPN IP when connecting.\nThe DoH servers are hosted by one of the following providers: Quad9 or Cloudflare." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Wenn du nicht mit unserem VPN verbunden bist, wird der verschlüsselte DNS-Proxy deine eigene nicht-VPN-IP beim Verbinden verwenden.\nDie DoH-Server werden von einem der folgenden Anbieter gehostet: Quad9 oder Cloudflare."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Om du inte är ansluten till vårt VPN kommer den krypterade DNS-proxyn att använda din egen icke-VPN IP när du ansluter.\nDoH-servrarna är värd för en av följande leverantörer: Quad9 eller Cloudflare."
- }
- }
- }
+ "If you are not connected to our VPN, then the Encrypted DNS proxy will use your own non-VPN IP when connecting. The DoH servers are hosted by one of the following providers: Quad9 or Cloudflare." : {
+
},
"If you disconnect now, you won’t be able to secure your connection until the device is online." : {
"localizations" : {
@@ -3142,6 +3068,9 @@
}
}
},
+ "If you log out, the device and the device name is removed. When you log back in again, the device will get a new name." : {
+
+ },
"If you still experience issues you can email our support directly at **%@**. Please attach your app log to your email." : {
"localizations" : {
"de" : {
@@ -3186,50 +3115,6 @@
}
}
},
- "Import %@ was successful, overrides are now active." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Import %@ war erfolgreich, Überschreibungen sind jetzt aktiv."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Importation %@ réussie, les remplacements sont maintenant actifs."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Import %@ lyckades, åsidosättningar är nu aktiva."
- }
- }
- }
- },
- "Import %@ was unsuccessful, please try again." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Import %@ war nicht erfolgreich, bitte versuchen Sie es erneut."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "L'importation de %@ a échoué, veuillez réessayer."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Importen av %@ misslyckades, försök igen."
- }
- }
- }
- },
"Import file" : {
"localizations" : {
"de" : {
@@ -3252,27 +3137,14 @@
}
}
},
- "Import files or text with the new IP addresses for the servers in the Select location view." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Importieren Sie Dateien oder Text mit den neuen IP-Adressen für die Server in der Standortansicht auswählen."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Importer des fichiers ou du texte avec les nouvelles adresses IP pour les serveurs dans la vue Choisir un emplacement."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Importera filer eller text med de nya IP-adresserna för servrarna i Välj plats vyn."
- }
- }
- }
+ "Import files or text with new IP addresses for the servers in the Select location view." : {
+
+ },
+ "Import of %@ was successful, overrides are now active." : {
+
+ },
+ "Import of %@ was unsuccessful, please try again." : {
+
},
"Import successful" : {
"localizations" : {
@@ -3318,6 +3190,9 @@
}
}
},
+ "In" : {
+
+ },
"In use" : {
"localizations" : {
"de" : {
@@ -3472,27 +3347,11 @@
}
}
},
- "It looks like you have entered a Mullvad account number instead of a voucher code. Do you want to log in to an existing account?\nIf so, click log out below to log in with the other account number." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Es sieht so aus, als hätten Sie statt eines Gutscheincodes eine Mullvad-Kontonummer eingegeben. Möchten Sie sich bei einem bestehenden Konto anmelden?\nWenn ja, klicken Sie unten auf Abmelden, um sich mit der anderen Kontonummer anzumelden."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "On dirait que vous avez entré un numéro de compte Mullvad au lieu d'un code coupon. Voulez-vous vous connecter à un compte existant ?\nSi c'est le cas, cliquez sur déconnecter ci-dessous pour vous connecter avec l'autre numéro de compte."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Det verkar som om du har angett ett Mullva-kontonummer istället för en kupongkod. Vill du logga in på ett befintligt konto?\nOm så är fallet, klicka på logga ut nedan för att logga in med det andra kontonumret."
- }
- }
- }
+ "It does this by performing an extra key exchange using a quantum safe algorithm and mixing the result into WireGuard’s regular encryption. This extra step uses approximately 500 kiB of traffic every time a new tunnel is established." : {
+
+ },
+ "It looks like you’ve entered an account number instead of a voucher code. If you would like to change the active account, please log out first." : {
+
},
"Language" : {
"localizations" : {
@@ -3956,6 +3815,9 @@
}
}
},
+ "multihop" : {
+
+ },
"Multihop" : {
"localizations" : {
"de" : {
@@ -4000,22 +3862,6 @@
}
}
},
- "Multihop routes your traffic into one WireGuard server and out another, making it harder to trace.\nThis results in increased latency but increases anonymity online." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Multihop leitet Ihren Datenverkehr auf einen WireGuard-Server und einen anderen aus, was es schwieriger macht, ihn zu verfolgen.\nDies führt zu erhöhter Latenz, erhöht aber die Anonymität online."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Multihop rutter din trafik i en WireGuard server och ut en annan, vilket gör det svårare att spåra.\nDetta resulterar i ökad fördröjning men ökar anonymiteten på nätet."
- }
- }
- }
- },
"Multihop routes your traffic into one WireGuard server and out another, making it harder to trace. This results in increased latency but increases anonymity online." : {
"localizations" : {
"de" : {
@@ -4387,50 +4233,41 @@
}
}
},
- "Obfuscation" : {
+ "Not all our servers are %@-enabled. Therefore, we use multihop automatically to enable %@ with any server." : {
"localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Verschleierung"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Obfuscation"
- }
- },
- "sv" : {
+ "en" : {
"stringUnit" : {
- "state" : "translated",
- "value" : "Obfusk"
+ "state" : "new",
+ "value" : "Not all our servers are %1$@-enabled. Therefore, we use multihop automatically to enable %2$@ with any server."
}
}
}
},
- "Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connection would be blocked." : {
+ "Obfuscation" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
- "value" : "Verschleierung versteckt den WireGuard-Verkehr innerhalb eines anderen Protokolls. Es kann verwendet werden, um Zensur und andere Filterarten zu umgehen, wo eine einfache WireGuard-Verbindung blockiert würde."
+ "value" : "Verschleierung"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
- "value" : "L'obfuscation cache le trafic WireGuard à l'intérieur d'un autre protocole. Il peut être utilisé pour aider à contourner la censure et d'autres types de filtrage, où une simple connexion WireGuard serait bloquée."
+ "value" : "Obfuscation"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
- "value" : "Obduktion döljer WireGuard trafik inuti ett annat protokoll. Den kan användas för att kringgå censur och andra typer av filtrering, där en vanlig WireGuard-anslutning skulle blockeras."
+ "value" : "Obfusk"
}
}
}
},
+ "Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connection would be blocked." : {
+
+ },
"Obscured" : {
"localizations" : {
"de" : {
@@ -4541,27 +4378,8 @@
}
}
},
- "Open DAITA settings" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "DAITA-Einstellungen öffnen"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Ouvrir les paramètres DAITA"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Öppna DAITA-inställningar"
- }
- }
- }
+ "Open %@ settings" : {
+
},
"Optional" : {
"localizations" : {
@@ -4585,6 +4403,12 @@
}
}
},
+ "Out IPv4" : {
+
+ },
+ "Out IPv6" : {
+
+ },
"Out of time" : {
"localizations" : {
"de" : {
@@ -4805,6 +4629,9 @@
}
}
},
+ "Please log out of at least one by removing it from the list below. You can find the corresponding device name under the device’s Account settings." : {
+
+ },
"Please retry by using the \"Restore purchases\" button." : {
"localizations" : {
"de" : {
@@ -5194,6 +5021,9 @@
"Remove" : {
},
+ "Remove %@?" : {
+
+ },
"Remove last used account" : {
"localizations" : {
"de" : {
@@ -5643,49 +5473,8 @@
}
}
},
- "Setting: DAITA" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Einstellung: DAITA"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Paramètre: DAITA"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Inställning: DAITA"
- }
- }
- }
- },
- "Setting: Obfuscation" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Einstellung: Verschleierung"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Paramètre: Obfuscation"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Inställning: Obfusk"
- }
- }
- }
+ "Setting: %@" : {
+
},
"Settings" : {
"localizations" : {
@@ -5842,26 +5631,7 @@
}
},
"Super!" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Super!"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Super!"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Super!"
- }
- }
- }
+
},
"Switch location" : {
"localizations" : {
@@ -5907,6 +5677,9 @@
}
}
},
+ "TCP" : {
+
+ },
"Test method" : {
"localizations" : {
"de" : {
@@ -5951,6 +5724,9 @@
}
}
},
+ "text" : {
+
+ },
"Thanks for your purchase" : {
"localizations" : {
"de" : {
@@ -6105,27 +5881,14 @@
}
}
},
- "The automatic setting will randomly choose from the valid port ranges shown below.\nThe custom port can be any value inside the valid ranges:\n%@" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Die automatische Einstellung wird zufällig aus den unten angezeigten Portbereichen auswählen.\nDer benutzerdefinierte Port kann ein beliebiger Wert innerhalb der gültigen Bereiche sein:\n%@"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Le réglage automatique choisira aléatoirement parmi les plages de ports valides indiquées ci-dessous.\nLe port personnalisé peut ętre n'importe quelle valeur dans les plages valides :\n%@"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Den automatiska inställningen kommer slumpmässigt att välja från de giltiga portintervall som visas nedan.\nDen anpassade porten kan vara valfritt värde inom de giltiga intervallen:\n%@"
- }
- }
- }
+ "The automatic setting will randomly choose from the valid port ranges shown below." : {
+
+ },
+ "The custom port can be any value inside the valid ranges: %@." : {
+
+ },
+ "The device will be removed from the list and logged out." : {
+
},
"The entry and exit servers cannot be the same. Try changing one to a new server or location." : {
"localizations" : {
@@ -6149,24 +5912,12 @@
}
}
},
- "The entry server for multihop is currently overridden by DAITA. To select an entry server, please first enable “Direct only” or disable “DAITA” in the settings." : {
+ "The entry server for %@ is currently overridden by %@. To select an entry server, please first enable “%@” or disable “%@“ in the settings." : {
"localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Der Einstiegsserver für Multihop wird derzeit von DAITA überschrieben. Um einen Eintragsserver auszuwählen, aktivieren Sie bitte zuerst „Nur Direkt“ oder deaktivieren Sie „DAITA“ in den Einstellungen."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Le serveur d'entrée pour le multisaut est actuellement remplacé par DAITA. Pour sélectionner un serveur d'entrée, veuillez d'abord activer \"Direct only\" ou désactiver \"DAITA\" dans les paramètres."
- }
- },
- "sv" : {
+ "en" : {
"stringUnit" : {
- "state" : "translated",
- "value" : "Inmatningsservern för multihop är för närvarande åsidosatt av DAITA. För att välja en postserver, vänligen först aktivera “Direct only” eller inaktivera “DAITA” i inställningarna."
+ "state" : "new",
+ "value" : "The entry server for %1$@ is currently overridden by %2$@. To select an entry server, please first enable “%3$@” or disable “%4$@“ in the settings."
}
}
}
@@ -6303,27 +6054,8 @@
}
}
},
- "This feature allows access to other devices on the local network, such as for sharing, printing, streaming, etc.\nAttention: toggling “Local network sharing” requires restarting the VPN connection." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Diese Funktion ermöglicht den Zugriff auf andere Geräte im lokalen Netzwerk, wie zum Beispiel zum Teilen, Drucken, Streamen usw.\nAchtung: Das Umschalten von \"Lokales Netzwerkteilen\" erfordert einen Neustart der VPN-Verbindung."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Cette fonctionnalité permet d'accéder à d'autres périphériques du réseau local, tels que le partage, l'impression, le streaming, etc.\nAttention : activer le « Partage de réseau local » nécessite un redémarrage de la connexion VPN."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Den här funktionen ger åtkomst till andra enheter på det lokala nätverket, till exempel för att dela, skriva ut, strömma etc.\nObservera: växla mellan “Lokalt nätverk delning” kräver omstart av VPN-anslutningen."
- }
- }
- }
+ "This feature allows access to other devices on the local network, such as for sharing, printing, streaming, etc." : {
+
},
"This feature allows you to circumvent that censorship by adding custom ways to access the API via proxies and similar methods." : {
"localizations" : {
@@ -6347,37 +6079,11 @@
}
}
},
- "This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.\nIt does this by performing an extra key exchange using a quantum safe algorithm and mixing the result into WireGuard’s regular encryption.\nThis extra step uses approximately 500 kiB of traffic every time a new tunnel is established." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Dieses Feature macht den WireGuard-Tunnel gegen potentielle Angriffe von Quantencomputern resistent.\nDies geschieht, indem ein zusätzlicher Schlüsselaustausch mit einem quantensicheren Algorithmus durchgeführt wird und das Ergebnis in WireGuards regulärer Verschlüsselung gemischt wird.\nDieser zusätzliche Schritt verwendet bei jedem Bau eines neuen Tunnels etwa 500 kiB Verkehr."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Denna funktion gör WireGuard-tunneln resistent mot potentiella attacker från kvantdatorer.\nDet gör detta genom att utföra ett extra nyckelutbyte med hjälp av en kvantsäker algoritm och blanda resultatet i WireGuards vanliga kryptering.\nDetta extra steg använder cirka 500 kiB trafik varje gång en ny tunnel etableras."
- }
- }
- }
+ "This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers." : {
+
},
- "This is the name assigned to the device. Each device logged in on a Mullvad account gets a unique name that helps you identify it when you manage your devices in the app or on the website.\nYou can have up to 5 devices logged in on one Mullvad account.\nIf you log out, the device and the device name is removed. When you log back in again, the device will get a new name." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Dies ist der Name, der dem Gerät zugewiesen ist. Jedes Gerät, das sich auf einem Mullvad-Konto eingeloggt hat, erhält einen einzigartigen Namen, der Ihnen hilft, ihn zu identifizieren, wenn Sie Ihre Geräte in der App oder auf der Website verwalten.\nDu kannst bis zu 5 Geräte auf einem Mullvad Konto eingeloggt haben.\nWenn Sie sich abmelden, wird das Gerät und der Gerätename entfernt. Wenn Sie sich erneut anmelden, erhält das Gerät einen neuen Namen."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Detta är namnet som tilldelats enheten. Varje enhet som är inloggad på ett Mullva-konto får ett unikt namn som hjälper dig att identifiera det när du hanterar dina enheter i appen eller på webbplatsen.\nDu kan ha upp till 5 enheter inloggade på ett Mullva-konto.\nOm du loggar ut tas enheten och enhetsnamnet bort. När du loggar in igen får enheten ett nytt namn."
- }
- }
- }
+ "This is the name assigned to the device. Each device logged in on a Mullvad account gets a unique name that helps you identify it when you manage your devices in the app or on the website." : {
+
},
"This logs out all devices using this account and all VPN access will be denied even if there is time left on the account. Enter the last 4 digits of the account number and hit \"Delete account\" if you really want to delete the account:" : {
"localizations" : {
@@ -6401,6 +6107,9 @@
}
}
},
+ "This might cause issues on certain websites, services, and apps." : {
+
+ },
"This voucher code has already been used." : {
"localizations" : {
"de" : {
@@ -6445,6 +6154,9 @@
}
}
},
+ "To add more, you will need to disconnect and access the Internet with an unsecure connection." : {
+
+ },
"To assist you better, please write in English or Swedish and include which country you are connecting from." : {
"localizations" : {
"de" : {
@@ -6489,6 +6201,9 @@
}
}
},
+ "To continue your journey as a privacy ninja, visit our website to pick up other privacy-friendly habits and tools." : {
+
+ },
"To create a custom list, tap on \"...\" " : {
"localizations" : {
"de" : {
@@ -6555,49 +6270,11 @@
}
}
},
- "To start using the app, you first need to add time to your account. Either buy credit on our website or redeem a voucher." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Um die App zu verwenden, müssen Sie zuerst Zeit zu Ihrem Konto hinzufügen. Kaufen Sie ein Guthaben auf unserer Website oder einlösen Sie einen Gutschein."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Pour commencer à utiliser l'application, vous devez d'abord ajouter du temps à votre compte. Achetez du crédit sur notre site Web ou utilisez un bon d'achat."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "För att börja använda appen måste du först lägga till tid på ditt konto. Antingen köpa kredit på vår webbplats eller lösa in en kupong."
- }
- }
- }
+ "To start using the app, you first need to add time to your account." : {
+
},
"Too many devices" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Zu viele Geräte"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Trop d'appareils"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "För många enheter"
- }
- }
- }
+
},
"Too many devices registered with account" : {
"localizations" : {
@@ -6731,6 +6408,9 @@
}
}
},
+ "UDP" : {
+
+ },
"UDP-over-TCP" : {
"localizations" : {
"de" : {
@@ -6951,49 +6631,24 @@
}
}
},
- "Valid range: 1 - 65535" : {
+ "Valid range: %d - %d" : {
"localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Valid range: 1 - 65535"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Valid range: 1 - 65535"
- }
- },
- "sv" : {
+ "en" : {
"stringUnit" : {
- "state" : "translated",
- "value" : "Valid range: 1 - 65535"
+ "state" : "new",
+ "value" : "Valid range: %1$d - %2$d"
}
}
}
},
+ "value" : {
+
+ },
"Verifying voucher..." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Gutschein wird überprüft..."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Vérification du bon de réduction..."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Verifierar kupong..."
- }
- }
- }
+
+ },
+ "View and manage all your logged in devices. You can have up to 5 devices on one account at a time. Each device gets a name when logged in to help you tell them apart easily." : {
+
},
"View app logs" : {
"localizations" : {
@@ -7084,26 +6739,7 @@
}
},
"Warning: The malware blocker is not an anti-virus and should not be treated as such, this is just an extra layer of protection." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Warnung: Der Malware-Blocker ist kein Anti-Virus und sollte nicht als solche behandelt werden. Dies ist nur eine zusätzliche Schutzschicht."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Attention : Le bloqueur de logiciels malveillants n'est pas un anti-virus et ne doit pas être traité en tant que tel, il ne s'agit que d'une couche supplémentaire de protection."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Varning: malware blockeraren är inte en anti-virus och bör inte behandlas som sådan, detta är bara ett extra lager av skydd."
- }
- }
- }
+
},
"We are having some issues, please try again later" : {
"localizations" : {
@@ -7171,43 +6807,11 @@
}
}
},
- "What's new" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Was ist neu"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Quoi de neuf"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Vad är nytt"
- }
- }
- }
+ "What’s new" : {
+
},
- "When this feature is enabled it stops the device from contacting certain domains or websites known for distributing ads, malware, trackers and more. \nThis might cause issues on certain websites, services, and apps.\nAttention: this setting cannot be used in combination with **Use custom DNS server**." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Wenn diese Funktion aktiviert ist, hält es das Gerät davon ab, bestimmte Domains oder Websites zu kontaktieren, die für die Verbreitung von Werbung, Malware, Tracker und mehr bekannt sind. \nDies kann Probleme auf bestimmten Webseiten, Diensten und Apps verursachen.\nAchtung: diese Einstellung kann nicht in Kombination mit **Eigenen DNS-Server verwenden** verwendet werden."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "När den här funktionen är aktiverad stoppar den enheten från att kontakta vissa domäner eller webbplatser som är kända för att distribuera annonser, skadlig kod, trackers och mer. \nDetta kan orsaka problem på vissa webbplatser, tjänster och appar.\nObservera: den här inställningen kan inte användas i kombination med **Använd anpassad DNS-server**."
- }
- }
- }
+ "When this feature is enabled it stops the device from contacting certain domains or websites known for distributing ads, malware, trackers and more." : {
+
},
"When using DAITA, one provider with DAITA-enabled servers is required." : {
"localizations" : {
@@ -7253,115 +6857,20 @@
}
}
},
- "WireGuard Obfuscation" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "WireGuard Verschleierung"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Obfuscation WireGuard"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Trådvärnsförfalskning"
- }
- }
- }
+ "WireGuard obfuscation" : {
+
},
- "WireGuard ports" : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "WireGuard Ports"
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Ports WireGuard"
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "WireGuard portar"
- }
- }
- }
+ "WireGuard port" : {
+
},
- "With the \"Direct\" method, the app communicates with a Mullvad API server directly without any intermediate proxies." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Mit der \"Direct\" Methode kommuniziert die App direkt mit einem Mullvad API Server ohne Zwischenproxies."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Avec la méthode \"Direct\", l'application communique directement avec un serveur API Mullvad sans proxys intermédiaires."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Med \"Direkt\" metoden kommunicerar appen med en Mullvads API-server direkt utan några mellanliggande proxier."
- }
- }
- }
+ "With the “Direct” method, the app communicates with a Mullvad API server directly without any intermediate proxies." : {
+
},
- "With the \"Encrypted DNS proxy\" method, the app will communicate with our Mullvad API through a proxy address.\nIt does this by retrieving an address from a DNS over HTTPS (DoH) server and then using that to reach our API servers." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Mit der \"Verschlüsselten DNS-Proxy-Methode\" wird die App über eine Proxy-Adresse mit unserer Mullvad-API kommunizieren.\nDies geschieht durch das Abrufen einer Adresse von einem DNS über den HTTPS (DoH) Server und die Verwendung dieser Adresse, um unsere API-Server zu erreichen."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Avec la méthode \"proxy DNS chiffré\", l'application communiquera avec notre API Mullvad via une adresse proxy.\nIl fait cela en récupérant une adresse depuis un DNS via un serveur HTTPS (DoH) puis en utilisant cela pour atteindre nos serveurs API."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Med metoden \"Krypterad DNS-proxy\" kommer appen att kommunicera med vårt Mullvad API genom en proxyadress.\nDet gör detta genom att hämta en adress från en DNS över HTTPS (DoH) server och sedan använda det för att nå våra API-servrar."
- }
- }
- }
+ "With the “Encrypted DNS proxy” method, the app will communicate with our Mullvad API through a proxy address. It does this by retrieving an address from a DNS over HTTPS (DoH) server and then using that to reach our API servers." : {
+
},
- "With the \"Mullvad bridges\" method, the app communicates with a Mullvad API server via a Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Mit der \"Mullvad Bridges\"-Methode kommuniziert die App mit einem Mullvad API-Server über einen Mullvad Bridge-Server. Sie tut dies, indem sie den Verkehr verschleiert von Shadowsocks."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Avec la méthode \"pont Mullvad\", l'application communique avec un serveur API Mullvad via un serveur pont Mullvad. Il fait cela en envoyant le trafic obscurci par Shadowsocks."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Med \"Mullvad bridges\"-metoden kommunicerar appen med en Mullvads API-server via en Mullvads bryggserver. Det gör detta genom att skicka trafiken fördunklad av Shadowsocks."
- }
- }
- }
+ "With the “Mullvad bridges” method, the app communicates with a Mullvad API server via a Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks." : {
+
},
"Yes, continue" : {
"localizations" : {
@@ -7385,7 +6894,10 @@
}
}
},
- "You already have a saved account number, by creating a new account the saved account number will be removed from this device. This cannot be undone.\nDo you want to create a new account?" : {
+ "Yes, log out device" : {
+
+ },
+ "You already have a saved account number, by creating a new account the saved account number will be removed from this device. This cannot be undone." : {
},
"You are about to send the problem report without a way for us to get back to you. If you want an answer to your report you will have to enter an email address." : {
@@ -7454,6 +6966,12 @@
}
}
},
+ "You can have up to 5 devices logged in on one Mullvad account." : {
+
+ },
+ "You can now continue logging in on this device." : {
+
+ },
"You can use the \"restore purchases\" function to check for any in-app payments made via Apple services. If there is a payment that has not been credited, it will add the time to the currently logged in Mullvad account." : {
"localizations" : {
"de" : {
@@ -7498,49 +7016,11 @@
}
}
},
- "You have no more VPN time left on this account. Either buy credit on our website or make an in-app purchase via the **Add time** button below." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Du hast keine VPN-Zeit mehr auf diesem Konto. Kaufen Sie entweder Guthaben auf unserer Website oder tätigen Sie einen In-App-Kauf über die Schaltfläche **Zeit hinzufügen** unten."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Il ne vous reste plus de temps VPN sur ce compte. Achetez du crédit sur notre site Web ou effectuez un achat dans l'application via le bouton **Ajouter du temps** ci-dessous."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Du har ingen mer VPN-tid kvar på detta konto. Antingen köpa kredit på vår webbplats eller göra ett in-app köp via **Lägg till tid** knappen nedan."
- }
- }
- }
+ "You have a right to privacy. That’s why we never store activity logs, don’t ask for personal information, and encourage anonymous payments.\n\nIn some situations, as outlined in our privacy policy, we might process personal data that you choose to send, for example if you email us.\n\nWe strongly believe in retaining as little data as possible because we want you to remain anonymous." : {
+
},
- "You have no more VPN time left on this account. To add more, you will need to disconnect and access the Internet with an unsecure connection." : {
- "localizations" : {
- "de" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Du hast keine VPN-Zeit mehr auf diesem Konto. Um mehr hinzuzufügen, müssen Sie die Verbindung trennen und mit einer unsicheren Verbindung auf das Internet zugreifen."
- }
- },
- "fr" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Il ne vous reste plus de temps VPN sur ce compte. Pour en ajouter plus, vous devrez vous déconnecter et accéder à Internet avec une connexion non sécurisée."
- }
- },
- "sv" : {
- "stringUnit" : {
- "state" : "translated",
- "value" : "Du har ingen mer VPN-tid kvar på detta konto. För att lägga till mer, måste du koppla bort och komma åt Internet med en osäker anslutning."
- }
- }
- }
+ "You have no more VPN time left on this account. " : {
+
},
"You have one day left on this account. Please add more time to continue using the VPN." : {
"localizations" : {
diff --git a/ios/MullvadTypes/TransportLayer.swift b/ios/MullvadTypes/TransportLayer.swift
index 027b6e4d2e..eccbff48f8 100644
--- a/ios/MullvadTypes/TransportLayer.swift
+++ b/ios/MullvadTypes/TransportLayer.swift
@@ -11,4 +11,13 @@ import Foundation
public enum TransportLayer: Codable, Sendable {
case udp
case tcp
+
+ public var name: String {
+ switch self {
+ case .udp:
+ NSLocalizedString("UDP", comment: "")
+ case .tcp:
+ NSLocalizedString("TCP", comment: "")
+ }
+ }
}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 1cb47be61b..3f237153b4 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -489,7 +489,6 @@
7A1A26472A29CF0800B978AA /* RelayFilterDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26462A29CF0800B978AA /* RelayFilterDataSource.swift */; };
7A1A26492A29D48A00B978AA /* RelayFilterCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1A26482A29D48A00B978AA /* RelayFilterCellFactory.swift */; };
7A21DACF2A30AA3700A787A9 /* UITextField+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */; };
- 7A27E3C92CAE85710088BCFF /* SettingsInfoButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */; };
7A27E3CB2CAE861D0088BCFF /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */; };
7A27E3CD2CB814EF0088BCFF /* DAITAInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */; };
7A27E3CF2CBD4A8C0088BCFF /* SelectableSettingsDetailsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */; };
@@ -2079,7 +2078,6 @@
7A1A26482A29D48A00B978AA /* RelayFilterCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterCellFactory.swift; sourceTree = "<group>"; };
7A1A264A2A29D65E00B978AA /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = "<group>"; };
7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Appearance.swift"; sourceTree = "<group>"; };
- 7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoButtonItem.swift; sourceTree = "<group>"; };
7A27E3CA2CAE86170088BCFF /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
7A27E3CC2CB814EA0088BCFF /* DAITAInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITAInfoView.swift; sourceTree = "<group>"; };
7A27E3CE2CBD4A830088BCFF /* SelectableSettingsDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsDetailsCell.swift; sourceTree = "<group>"; };
@@ -3284,7 +3282,6 @@
7A83C4012A57FAA800DFB83A /* SettingsDNSInfoCell.swift */,
584D26C5270C8741004EA533 /* SettingsDNSTextCell.swift */,
7AC8A3AD2ABC6FBB00DC4939 /* SettingsHeaderView.swift */,
- 7A27E3C82CAE85660088BCFF /* SettingsInfoButtonItem.swift */,
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */,
58677711290976FB006F721F /* SettingsInteractor.swift */,
5867770F290975E8006F721F /* SettingsInteractorFactory.swift */,
@@ -6741,7 +6738,6 @@
58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */,
58B9EB152489139B00095626 /* RESTError+Display.swift in Sources */,
587B753F2668E5A700DEF7E9 /* NotificationContainerView.swift in Sources */,
- 7A27E3C92CAE85710088BCFF /* SettingsInfoButtonItem.swift in Sources */,
58F2E144276A13F300A79513 /* StartTunnelOperation.swift in Sources */,
58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */,
586E54FB27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift in Sources */,
@@ -7786,12 +7782,13 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -7848,12 +7845,13 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -8500,12 +8498,13 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -8973,6 +8972,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG MULLVAD_ENVIRONMENT_STAGING";
+ SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h";
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -8997,6 +8997,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = MULLVAD_ENVIRONMENT_PRODUCTION;
+ SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h";
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -9357,11 +9358,12 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
diff --git a/ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift b/ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift
index 7e56893c16..8f4a01e3dd 100644
--- a/ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/ChangeLogCoordinator.swift
@@ -34,7 +34,7 @@ final class ChangeLogCoordinator: Coordinator, Presentable, SettingsChildCoordin
func start(animated: Bool) {
let changeLogViewController = UIHostingController(rootView: ChangeLogView(viewModel: viewModel))
changeLogViewController.view.setAccessibilityIdentifier(.changeLogAlert)
- changeLogViewController.navigationItem.title = NSLocalizedString("What's new", comment: "")
+ changeLogViewController.navigationItem.title = NSLocalizedString("What’s new", comment: "")
switch route {
case .changelog:
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift b/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift
index 6fd41488d5..4ad6713ef1 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/CustomListViewController.swift
@@ -158,7 +158,10 @@ class CustomListViewController: UIViewController {
private func onDelete() {
let message = NSMutableAttributedString(
- markdownString: NSLocalizedString("Do you want to delete the list **\(subject.value.name)**?", comment: ""),
+ markdownString: String(
+ format: NSLocalizedString("Do you want to delete the list **%@**?", comment: ""),
+ subject.value.name
+ ),
options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body))
)
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift
index cc15a2cadc..68510213cc 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift
@@ -393,7 +393,7 @@ extension EditAccessMethodViewController: UITableViewDelegate {
let presentation = AlertPresentation(
id: "api-access-methods-delete-method-alert",
icon: .alert,
- message: NSLocalizedString("Delete \(methodName)?", comment: ""),
+ message: String(format: NSLocalizedString("Delete %@?", comment: ""), methodName),
buttons: [
AlertAction(
title: NSLocalizedString("Delete", comment: ""),
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift
index b66580800c..c2bfdc30de 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift
@@ -316,7 +316,7 @@ class MethodSettingsViewController: UITableViewController {
id: "api-access-methods-testing-status-failed-alert",
accessibilityIdentifier: .accessMethodUnreachableAlert,
icon: .warning,
- message: NSLocalizedString("API could not be reached, save anyway?", comment: ""),
+ message: NSLocalizedString("API unreachable, save anyway?", comment: ""),
buttons: [
AlertAction(
title: NSLocalizedString("Save anyway", comment: ""),
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift
index e6f8319a73..0b025e5565 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodCoordinator.swift
@@ -89,24 +89,18 @@ class ListAccessMethodCoordinator: Coordinator, Presenting, SettingsChildCoordin
)
let body = [
NSLocalizedString(
- """
- The app needs to communicate with a Mullvad API server to log you in, fetch server lists, \
- and other critical operations.
- """,
+ "The app needs to communicate with a Mullvad API server to log you in, " +
+ "fetch server lists, and other critical operations.",
comment: ""
),
NSLocalizedString(
- """
- On some networks, where various types of censorship are being used, the API servers might \
- not be directly reachable.
- """,
+ "On some networks, where various types of censorship are being used, " +
+ "the API servers might not be directly reachable.",
comment: ""
),
NSLocalizedString(
- """
- This feature allows you to circumvent that censorship by adding custom ways to access the \
- API via proxies and similar methods.
- """,
+ "This feature allows you to circumvent that censorship by adding custom ways " +
+ "to access the API via proxies and similar methods.",
comment: ""
),
]
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel.swift
index a9797fac7f..4224fc0f74 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel.swift
@@ -113,10 +113,8 @@ extension AccessMethodViewModel {
preamble: NSLocalizedString("The app communicates with a Mullvad API server directly.", comment: ""),
body: [
NSLocalizedString(
- """
- With the "Direct" method, the app communicates with a Mullvad API server \
- directly without any intermediate proxies.
- """,
+ "With the “Direct” method, the app communicates with a Mullvad API " +
+ "server directly without any intermediate proxies.",
comment: ""
),
NSLocalizedString("This can be useful when you are not affected by censorship.", comment: ""),
@@ -131,10 +129,8 @@ extension AccessMethodViewModel {
),
body: [
NSLocalizedString(
- """
- With the "Mullvad bridges" method, the app communicates with a Mullvad API server via a \
- Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks.
- """,
+ "With the “Mullvad bridges” method, the app communicates with a Mullvad API server via a " +
+ "Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks.",
comment: ""
),
NSLocalizedString(
@@ -152,20 +148,16 @@ extension AccessMethodViewModel {
),
body: [
NSLocalizedString(
- """
- With the "Encrypted DNS proxy" method, the app will communicate with our \
- Mullvad API through a proxy address.
- It does this by retrieving an address from a DNS over HTTPS (DoH) server and \
- then using that to reach our API servers.
- """,
+ "With the “Encrypted DNS proxy” method, the app will communicate with our " +
+ "Mullvad API through a proxy address. It does this by retrieving " +
+ "an address from a DNS over HTTPS (DoH) server and then using that " +
+ "to reach our API servers.",
comment: ""
),
NSLocalizedString(
- """
- If you are not connected to our VPN, then the Encrypted DNS proxy will use your own non-VPN IP \
- when connecting.
- The DoH servers are hosted by one of the following providers: Quad9 or Cloudflare.
- """,
+ "If you are not connected to our VPN, then the Encrypted DNS proxy will use " +
+ "your own non-VPN IP when connecting. The DoH servers are hosted by one of the " +
+ "following providers: Quad9 or Cloudflare.",
comment: ""
),
]
diff --git a/ios/MullvadVPN/Coordinators/Settings/DAITA/SettingsDAITAView.swift b/ios/MullvadVPN/Coordinators/Settings/DAITA/SettingsDAITAView.swift
index ce014999a2..7422b25391 100644
--- a/ios/MullvadVPN/Coordinators/Settings/DAITA/SettingsDAITAView.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/DAITA/SettingsDAITAView.swift
@@ -44,13 +44,16 @@ struct SettingsDAITAView<ViewModel>: View where ViewModel: TunnelSettingsObserva
}
SettingsRowViewFooter(
- text: NSLocalizedString(
- """
- By enabling "Direct only" you will have to manually select a server that \
- is DAITA-enabled. Multihop won't automatically be used to enable DAITA with \
- any server.
- """,
- comment: ""
+ text: String(
+ format:
+ NSLocalizedString(
+ "By enabling “%@” you will have to manually select a server that is %@-enabled. " +
+ "%@ won't automatically be used to enable DAITA with any server.",
+ comment: ""
+ ),
+ NSLocalizedString("Direct only", comment: ""),
+ NSLocalizedString("DAITA", comment: ""),
+ NSLocalizedString("Multihop", comment: "")
)
)
}
@@ -100,40 +103,53 @@ extension SettingsDAITAView {
extension SettingsDAITAView {
private var dataViewModel: SettingsInfoViewModel {
- SettingsInfoViewModel(
+ let daitafullTitle = NSLocalizedString("Defense against AI-guided Traffic Analysis", comment: "")
+ let daitaTitle = NSLocalizedString("DAITA", comment: "")
+ return SettingsInfoViewModel(
pages: [
SettingsInfoViewModelPage(
- body: NSLocalizedString(
- """
- **Attention: This increases network traffic and will also negatively affect speed, latency, \
- and battery usage. Use with caution on limited plans.**
-
- DAITA (Defense against AI-guided Traffic Analysis) hides patterns in \
- your encrypted VPN traffic.
-
- By using sophisticated AI it’s possible to analyze the traffic of data \
- packets going in and out of your device (even if the traffic is encrypted).
- """,
- comment: ""
- ),
+ body: [
+ NSLocalizedString(
+ "**Attention: This increases network traffic and will also negatively affect " +
+ "speed, latency, and battery usage. Use with caution on limited plans.**",
+ comment: ""
+ ),
+ String(
+ format: NSLocalizedString(
+ "%@ (%@) hides patterns in your encrypted VPN traffic.",
+ comment: ""
+ ),
+ daitaTitle,
+ daitafullTitle
+ ),
+ NSLocalizedString(
+ "By using sophisticated AI it’s possible to analyze " +
+ "the traffic of data packets going in and out of your " +
+ "device (even if the traffic is encrypted).",
+ comment: ""
+ ),
+ ].joinedParagraphs(),
image: .daitaOffIllustration
),
SettingsInfoViewModelPage(
- body: NSLocalizedString(
- """
- If an observer monitors these data packets, DAITA makes it significantly \
- harder for them to identify which websites you are visiting or with whom \
- you are communicating.
-
- DAITA does this by carefully adding network noise and making all network \
- packets the same size.
-
- Not all our servers are DAITA-enabled. Therefore, we use multihop \
- automatically to enable DAITA with any server.
-
- """,
- comment: ""
- ),
+ body: [
+ String(format: NSLocalizedString(
+ "If an observer monitors these data packets, %@ makes it " +
+ "significantly harder for them to identify which websites " +
+ "you are visiting or with whom you are communicating.",
+ comment: ""
+ ), daitaTitle),
+ String(format: NSLocalizedString(
+ "%@ does this by carefully adding network noise and making " +
+ "all network packets the same size.",
+ comment: ""
+ ), daitaTitle),
+ String(format: NSLocalizedString(
+ "Not all our servers are %@-enabled. Therefore, we use multihop " +
+ "automatically to enable %@ with any server.",
+ comment: ""
+ ), daitaTitle, daitaTitle),
+ ].joinedParagraphs(),
image: .daitaOnIllustration
),
]
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
index 60e6ece8c6..7a9fcb90b0 100644
--- a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideCoordinator.swift
@@ -78,16 +78,13 @@ extension IPOverrideCoordinator: @preconcurrency IPOverrideViewControllerDelegat
comment: ""
),
NSLocalizedString(
- """
- To circumvent this you can import a file or a text, provided by our support team, \
- with new IP addresses that override the default addresses of the servers in the Select location view.
- """,
+ "To circumvent this you can import a file or a text, provided by our support team, " +
+ "with new IP addresses that override the default addresses of the servers " +
+ "in the Select location view.",
comment: ""
),
NSLocalizedString(
- """
- If you are having issues connecting to VPN servers, please contact support.
- """,
+ "If you are having issues connecting to VPN servers, please contact support.",
comment: ""
),
]
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideStatus.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideStatus.swift
index c09dea3f0d..5639f7043c 100644
--- a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideStatus.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideStatus.swift
@@ -17,8 +17,10 @@ enum IPOverrideStatus: Equatable, CustomStringConvertible {
// Used in "statusDescription" below to form a complete sentence and therefore not localized here.
var description: String {
switch self {
- case .file: "of file"
- case .text: "via text"
+ case .file:
+ NSLocalizedString("file", comment: "")
+ case .text:
+ NSLocalizedString("text", comment: "")
}
}
}
@@ -59,12 +61,12 @@ enum IPOverrideStatus: Equatable, CustomStringConvertible {
""
case let .importFailed(context):
String(
- format: NSLocalizedString("Import %@ was unsuccessful, please try again.", comment: ""),
+ format: NSLocalizedString("Import of %@ was unsuccessful, please try again.", comment: ""),
context.description
)
case let .importSuccessful(context):
String(
- format: NSLocalizedString("Import %@ was successful, overrides are now active.", comment: ""),
+ format: NSLocalizedString("Import of %@ was successful, overrides are now active.", comment: ""),
context.description
)
}
diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift
index 73df4fd447..287f054b45 100644
--- a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift
@@ -102,7 +102,7 @@ class IPOverrideViewController: UIViewController {
private func addHeaderView() {
let body = NSLocalizedString(
- "Import files or text with the new IP addresses for the servers in the Select location view.",
+ "Import files or text with new IP addresses for the servers in the Select location view.",
comment: ""
)
let link = NSLocalizedString("About Server IP override...", comment: "")
@@ -143,10 +143,7 @@ class IPOverrideViewController: UIViewController {
icon: .alert,
title: NSLocalizedString("Clear all overrides?", comment: ""),
message: NSLocalizedString(
- """
- Clearing the imported overrides changes the server IPs, in the Select location view, \
- back to default.
- """,
+ "Clearing the imported overrides changes the server IPs, in the Select location view, back to default.",
comment: ""
),
buttons: [
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
index b7435b0656..5124c541ba 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
@@ -258,8 +258,7 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
private func makeChild(for route: SettingsNavigationRoute) -> SettingsViewControllerFactory.MakeChildResult {
if route == .root {
let controller = SettingsViewController(
- interactor: interactorFactory.makeSettingsInteractor(),
- alertPresenter: AlertPresenter(context: self)
+ interactor: interactorFactory.makeSettingsInteractor()
)
controller.delegate = self
return .viewController(controller)
diff --git a/ios/MullvadVPN/Coordinators/Settings/Views/SettingsInfoView.swift b/ios/MullvadVPN/Coordinators/Settings/Views/SettingsInfoView.swift
index 4e43614302..8eebb0d481 100644
--- a/ios/MullvadVPN/Coordinators/Settings/Views/SettingsInfoView.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/Views/SettingsInfoView.swift
@@ -51,10 +51,11 @@ struct SettingsInfoView: View {
}
private func bodyText(_ page: SettingsInfoViewModelPage) -> some View {
- (try? AttributedString(
- markdown: page.body,
+ let message = page.body
+ return (try? AttributedString(
+ markdown: message,
options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)
- )).map(Text.init) ?? Text(page.body)
+ )).map(Text.init) ?? Text(message)
}
private func contentView() -> some View {
diff --git a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
index 34a656047f..6047763799 100644
--- a/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/WelcomeCoordinator.swift
@@ -82,12 +82,7 @@ final class WelcomeCoordinator: Coordinator, Poppable, Presenting {
extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
func didRequestToShowFailToFetchProducts(controller: WelcomeViewController) {
- let message = NSLocalizedString(
- """
- Failed to connect to App store, please try again later.
- """,
- comment: ""
- )
+ let message = NSLocalizedString("Failed to connect to App store, please try again later.", comment: "")
let presentation = AlertPresentation(
id: "welcome-failed-to-fetch-products-alert",
@@ -106,17 +101,19 @@ extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
}
func didRequestToShowInfo(controller: WelcomeViewController) {
- let message = NSLocalizedString(
- """
- This is the name assigned to the device. Each device logged in on a \
- Mullvad account gets a unique name that helps \
- you identify it when you manage your devices in the app or on the website.
- You can have up to 5 devices logged in on one Mullvad account.
- If you log out, the device and the device name is removed. \
- When you log back in again, the device will get a new name.
- """,
+ let message = [NSLocalizedString(
+ "This is the name assigned to the device. Each device logged in on a " +
+ "Mullvad account gets a unique name that helps " +
+ "you identify it when you manage your devices in the app or on the website.",
comment: ""
- )
+ ), NSLocalizedString(
+ "You can have up to 5 devices logged in on one Mullvad account.",
+ comment: ""
+ ), NSLocalizedString(
+ "If you log out, the device and the device name is removed. " +
+ "When you log back in again, the device will get a new name.",
+ comment: ""
+ )].joinedParagraphs(lineBreaks: 1)
let presentation = AlertPresentation(
id: "welcome-device-name-alert",
diff --git a/ios/MullvadVPN/Extensions/String+Helpers.swift b/ios/MullvadVPN/Extensions/String+Helpers.swift
index 3241c7442b..3f68489b72 100644
--- a/ios/MullvadVPN/Extensions/String+Helpers.swift
+++ b/ios/MullvadVPN/Extensions/String+Helpers.swift
@@ -24,3 +24,10 @@ extension String {
return self.size(withAttributes: fontAttributes).width
}
}
+
+extension Array where Element == String {
+ func joinedParagraphs(lineBreaks: Int = 2) -> String {
+ let separator = String(repeating: "\n", count: lineBreaks)
+ return self.joined(separator: separator)
+ }
+}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
index b106c7fe42..04d209631d 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpirySystemNotificationProvider.swift
@@ -149,10 +149,8 @@ extension AccountExpirySystemNotificationProvider {
private var expiredText: String {
NSLocalizedString(
- """
- Blocking internet: Your time on this account has expired. To continue using the internet, \
- please add more time or disconnect the VPN.
- """,
+ "Blocking internet: Your time on this account has expired. " +
+ "To continue using the internet, please add more time or disconnect the VPN.",
comment: ""
)
}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift
index 69e2531f46..112e111d58 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift
@@ -51,7 +51,7 @@ class LatestChangesNotificationProvider: NotificationProvider, InAppNotification
private func createNotificationBody() -> NSAttributedString {
NSAttributedString(
- markdownString: NSLocalizedString("**Tap here** to see what’s new.", comment: ""),
+ markdownString: NSLocalizedString("**Tap here** to see what’s new", comment: ""),
options: MarkdownStylingOptions(
font: .preferredFont(forTextStyle: .body)
)
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedContentView.swift b/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedContentView.swift
index 9796aace27..a7bfb40d05 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedContentView.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Completed/SetupAccountCompletedContentView.swift
@@ -28,14 +28,14 @@ class SetupAccountCompletedContentView: UIView {
private let commentLabel: UILabel = {
let label = UILabel()
- let message = NSMutableAttributedString(string: NSLocalizedString(
- """
- Go ahead and start using the app to begin reclaiming your online privacy.
- To continue your journey as a privacy ninja, \
- visit our website to pick up other privacy-friendly habits and tools.
- """,
+ let message = NSMutableAttributedString(string: [NSLocalizedString(
+ "Go ahead and start using the app to begin reclaiming your online privacy.",
comment: ""
- ))
+ ), NSLocalizedString(
+ "To continue your journey as a privacy ninja, visit our website to pick up" +
+ " other privacy-friendly habits and tools.",
+ comment: ""
+ )].joined(separator: " "))
message.apply(paragraphStyle: .alert)
label.attributedText = message
diff --git a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift
index dd401958d3..9054febbc9 100644
--- a/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift
+++ b/ios/MullvadVPN/View controllers/CreationAccount/Welcome/WelcomeContentView.swift
@@ -96,14 +96,12 @@ final class WelcomeContentView: UIView, Sendable {
label.numberOfLines = .zero
label.lineBreakMode = .byWordWrapping
label.lineBreakStrategy = []
- label.text = NSLocalizedString(
- """
- To start using the app, you first need to \
- add time to your account. Either buy credit \
- on our website or redeem a voucher.
- """,
+ label.text = [NSLocalizedString(
+ "To start using the app, you first need to add time to your account.",
comment: ""
- )
+ ), NSLocalizedString(
+ "Either buy credit on our website or redeem a voucher.", comment: ""
+ )].joined(separator: " ")
return label
}()
diff --git a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementView.swift b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementView.swift
index 4a2b2a6996..953fee18b8 100644
--- a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementView.swift
+++ b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementView.swift
@@ -24,7 +24,7 @@ struct DeviceManagementView: View {
}
}
- func warningMessage(deviceName: String) -> LocalizedStringKey {
+ func warningText(deviceName: String) -> [LocalizedStringKey] {
var attributedDeviceName: AttributedString {
var fullText = AttributedString(deviceName.capitalized)
fullText.foregroundColor = Color.mullvadTextPrimary
@@ -32,15 +32,14 @@ struct DeviceManagementView: View {
}
return switch self {
case .tooManyDevices:
- LocalizedStringKey(
+ [LocalizedStringKey(
"Are you sure you want to log \(attributedDeviceName) out?"
- )
+ )]
case .deviceManagement:
- LocalizedStringKey("""
- Remove \(attributedDeviceName)?
- The device will be removed from the list and logged out.
- """
- )
+ [
+ LocalizedStringKey("Remove \(attributedDeviceName)?"),
+ LocalizedStringKey("The device will be removed from the list and logged out."),
+ ]
}
}
}
@@ -60,13 +59,10 @@ struct DeviceManagementView: View {
}
var bodyText: LocalizedStringKey {
+ // swiftlint:disable line_length
switch style {
case .deviceManagement:
- """
- View and manage all your logged in devices. \
- You can have up to 5 devices on one account at a time. \
- Each device gets a name when logged in to help you tell them apart easily.
- """
+ "View and manage all your logged in devices. You can have up to 5 devices on one account at a time. Each device gets a name when logged in to help you tell them apart easily."
case .tooManyDevices:
if canLoginNewDevice {
"""
@@ -74,12 +70,10 @@ struct DeviceManagementView: View {
"""
} else {
- """
- Please log out of at least one by removing it from the list below. \
- You can find the corresponding device name under the device’s Account settings.
- """
+ "Please log out of at least one by removing it from the list below. You can find the corresponding device name under the device’s Account settings."
}
}
+ // swiftlint:enable line_length
}
private func fetchDevices() {
@@ -114,7 +108,7 @@ struct DeviceManagementView: View {
onRemoveDevice: { device in
deviceManagementAlert = MullvadAlert(
type: .warning,
- message: style.warningMessage(deviceName: device.name),
+ messages: style.warningText(deviceName: device.name),
action: .init(
type: style.actionButtonStyle,
title: style.actionButtonTitle,
diff --git a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
index 1a1da7b8b3..066368480b 100644
--- a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
+++ b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift
@@ -232,14 +232,16 @@ class LoginViewController: UIViewController, RootContainment {
@objc private func createNewAccount() {
if interactor.hasLastAccountNumber {
let message = NSMutableAttributedString(
- markdownString: NSLocalizedString("""
- You already have a saved account number, by creating a new account the saved account number \
- will be removed from this device. This cannot be undone.
- Do you want to create a new account?
- """, comment: ""),
+ markdownString: [
+ NSLocalizedString(
+ "You already have a saved account number, by creating a new account the " +
+ "saved account number will be removed from this device. This cannot be undone.",
+ comment: ""
+ ),
+ NSLocalizedString("Do you want to create a new account?", comment: ""),
+ ].joinedParagraphs(lineBreaks: 1),
options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body))
)
-
let presentation = AlertPresentation(
id: "create-account-confirmation-dialog",
icon: .info,
diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
index 21914abc27..e1c024797f 100644
--- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
+++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeViewController.swift
@@ -105,28 +105,29 @@ class OutOfTimeViewController: UIViewController, RootContainment {
private func applyViewState() {
let tunnelState = interactor.tunnelStatus.state
+ let baseMessage = NSLocalizedString("You have no more VPN time left on this account. ", comment: "")
contentView.enableDisconnectButton(tunnelState.isSecured, animated: true)
contentView.enablePurchaseButton(!tunnelState.isSecured)
if tunnelState.isSecured {
- contentView.setBodyLabelText(
+ contentView.setBodyLabelText([
+ baseMessage,
NSLocalizedString(
- """
- You have no more VPN time left on this account. To add more, you will need to \
- disconnect and access the Internet with an unsecure connection.
- """,
+ "To add more, you will need to " +
+ "disconnect and access the Internet with an unsecure connection.",
comment: ""
- )
+ ),
+ ].joined()
)
} else {
- contentView.setBodyLabelText(
+ contentView.setBodyLabelText([
+ baseMessage,
NSLocalizedString(
- """
- You have no more VPN time left on this account. Either buy credit on our website \
- or make an in-app purchase via the **Add time** button below.
- """,
+ "Either buy credit on our website " +
+ "or make an in-app purchase via the **Add time** button below.",
comment: ""
- )
+ ),
+ ].joined()
)
}
}
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift
index bdbb9e1819..dc48a678c7 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift
@@ -74,10 +74,8 @@ class ProblemReportSubmissionOverlayView: UIView {
return [
NSAttributedString(
string: NSLocalizedString(
- """
- If you exit the form and try again later, the information you already entered will still \
- be here.
- """,
+ "If you exit the form and try again later, the information you " +
+ "already entered will still be here.",
comment: ""
)
),
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift
index e7113b2b2c..58d4269326 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift
@@ -15,21 +15,16 @@ struct ProblemReportViewModel {
static let navigationTitle = NSLocalizedString("Report a problem", comment: "")
static let subheadLabelText = NSLocalizedString(
- """
- To help you more effectively, your app’s log file will be attached to \
- this message. Your data will remain secure and private, as it is anonymised \
- before being sent over an encrypted channel.
- """,
+ "To help you more effectively, your app’s log file will be attached to this message. " +
+ "Your data will remain secure and private, as it is anonymised before being " +
+ "sent over an encrypted channel.",
comment: ""
)
static let emailPlaceholderText = NSLocalizedString("Your email (optional)", comment: "")
static let messageTextViewPlaceholder = NSLocalizedString(
- """
- To assist you better, please write in English or Swedish and \
- include which country you are connecting from.
- """,
+ "To assist you better, please write in English or Swedish and include which country you are connecting from.",
comment: ""
)
@@ -38,10 +33,8 @@ struct ProblemReportViewModel {
static let sendLogsButtonTitle = NSLocalizedString("Send", comment: "")
static let emptyEmailAlertWarning = NSLocalizedString(
- """
- You are about to send the problem report without a way for us to get back to you. \
- If you want an answer to your report you will have to enter an email address.
- """,
+ "You are about to send the problem report without a way for us to get back to you. " +
+ "If you want an answer to your report you will have to enter an email address.",
comment: ""
)
diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift
index b56acc1754..95536e4de8 100644
--- a/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift
+++ b/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift
@@ -21,14 +21,13 @@ class LogoutDialogueView: UIView {
private let messageLabel: UILabel = {
let label = UILabel()
- let message = NSMutableAttributedString(string: NSLocalizedString(
- """
- It looks like you have entered a Mullvad account number instead of a voucher code. \
- Do you want to log in to an existing account?
- If so, click log out below to log in with the other account number.
- """,
+ let message = NSMutableAttributedString(string: [NSLocalizedString(
+ "It looks like you’ve entered an account number instead of a voucher code. " +
+ "If you would like to change the active account, please log out first.",
comment: ""
- ))
+ ), NSLocalizedString("If so, click log out below to log in with the other account number.", comment: "")]
+ .joinedParagraphs(lineBreaks: 0)
+ )
message.apply(paragraphStyle: .alert)
label.attributedText = message
diff --git a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
index dfed96cbfb..4b10f48ceb 100644
--- a/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
+++ b/ios/MullvadVPN/View controllers/RelayFilter/RelayFilterView.swift
@@ -54,7 +54,7 @@ class RelayFilterView: UIView {
func setDaita(_ enabled: Bool) {
let chip = ChipConfiguration(
group: .settings,
- title: NSLocalizedString("Setting: DAITA", comment: ""),
+ title: String(format: NSLocalizedString("Setting: %@", comment: ""), "DAITA"),
accessibilityId: .daitaFilterPill,
didTapButton: nil
)
@@ -65,7 +65,7 @@ class RelayFilterView: UIView {
func setObfuscation(_ enabled: Bool) {
let chip = ChipConfiguration(
group: .settings,
- title: NSLocalizedString("Setting: Obfuscation", comment: ""),
+ title: String(format: NSLocalizedString("Setting: %@", comment: ""), "Obfuscation"),
accessibilityId: .obfuscationFilterPill,
didTapButton: nil
)
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/DAITAInfoView.swift b/ios/MullvadVPN/View controllers/SelectLocation/DAITAInfoView.swift
index b4b56db8f9..34b5cbf2db 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/DAITAInfoView.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/DAITAInfoView.swift
@@ -19,10 +19,17 @@ class DAITAInfoView: UIView {
label.attributedText = NSAttributedString(
string: NSLocalizedString(
- """
- The entry server for multihop is currently overridden by DAITA. \
- To select an entry server, please first enable “Direct only” or disable “DAITA” in the settings.
- """,
+ String(
+ format: NSLocalizedString(
+ "The entry server for %@ is currently overridden by %@. To select an entry server, " +
+ "please first enable “%@” or disable “%@“ in the settings.",
+ comment: ""
+ ),
+ NSLocalizedString("multihop", comment: ""),
+ NSLocalizedString("DAITA", comment: ""),
+ NSLocalizedString("Direct only", comment: ""),
+ NSLocalizedString("DAITA", comment: "")
+ ),
comment: ""
),
attributes: [
@@ -38,7 +45,11 @@ class DAITAInfoView: UIView {
let settingsButton: UIButton = {
let settingsButton = AppButton(style: .default)
- settingsButton.setTitle(NSLocalizedString("Open DAITA settings", comment: ""), for: .normal)
+
+ settingsButton.setTitle(
+ String(format: NSLocalizedString("Open %@ settings", comment: ""), NSLocalizedString("DAITA", comment: "")),
+ for: .normal
+ )
return settingsButton
}()
diff --git a/ios/MullvadVPN/View controllers/Settings/Obfuscation/ShadowsocksObfuscationSettingsView.swift b/ios/MullvadVPN/View controllers/Settings/Obfuscation/ShadowsocksObfuscationSettingsView.swift
index 2978ad99cb..5b0f4125bc 100644
--- a/ios/MullvadVPN/View controllers/Settings/Obfuscation/ShadowsocksObfuscationSettingsView.swift
+++ b/ios/MullvadVPN/View controllers/Settings/Obfuscation/ShadowsocksObfuscationSettingsView.swift
@@ -32,7 +32,7 @@ struct ShadowsocksObfuscationSettingsView<VM>: View where VM: ShadowsocksObfusca
},
customLabel: NSLocalizedString("Custom", comment: ""),
customPrompt: NSLocalizedString("Port", comment: ""),
- customLegend: NSLocalizedString("Valid range: 1 - 65535", comment: ""),
+ customLegend: String(format: NSLocalizedString("Valid range: %d - %d", comment: ""), arguments: [1, 65535]),
customInputMinWidth: 100,
customInputMaxLength: 5,
customFieldMode: .numericText
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
index b74dcbf1cc..b02d8a3562 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
@@ -9,14 +9,9 @@
import MullvadSettings
import UIKit
-protocol SettingsCellEventHandler {
- func showInfo(for button: SettingsInfoButtonItem)
-}
-
@MainActor
final class SettingsCellFactory: @preconcurrency CellFactoryProtocol {
let tableView: UITableView
- var delegate: SettingsCellEventHandler?
var viewModel: SettingsViewModel
private let interactor: SettingsInteractor
@@ -57,7 +52,7 @@ final class SettingsCellFactory: @preconcurrency CellFactoryProtocol {
case .changelog:
guard let cell = cell as? SettingsCell else { return }
- cell.titleLabel.text = NSLocalizedString("What's new", comment: "")
+ cell.titleLabel.text = NSLocalizedString("What’s new", comment: "")
cell.detailTitleLabel.text = Bundle.main.productVersion
cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.disclosureType = .chevron
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
index 74c09d704a..017ba9cc4c 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
@@ -108,7 +108,6 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
tableView.sectionFooterHeight = 0
tableView.delegate = self
- settingsCellFactory.delegate = self
registerClasses()
updateDataSnapshot()
@@ -188,9 +187,3 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
apply(snapshot)
}
}
-
-extension SettingsDataSource: @preconcurrency SettingsCellEventHandler {
- func showInfo(for button: SettingsInfoButtonItem) {
- delegate?.showInfo(for: button)
- }
-}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDataSourceDelegate.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDataSourceDelegate.swift
index 3afb0d9eef..2f89d133c0 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDataSourceDelegate.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDataSourceDelegate.swift
@@ -11,5 +11,4 @@ import UIKit
protocol SettingsDataSourceDelegate: AnyObject {
func didSelectItem(item: SettingsDataSource.Item)
- func showInfo(for: SettingsInfoButtonItem)
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift b/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift
index 69878e50e0..ce07483272 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsHeaderView.swift
@@ -137,8 +137,8 @@ class SettingsHeaderView: UITableViewHeaderFooterView {
private func updateAccessibilityCustomActions() {
let actionName = isExpanded
- ? NSLocalizedString("Collapse \(accessibilityCustomActionName)", comment: "")
- : NSLocalizedString("Expand \(accessibilityCustomActionName)", comment: "")
+ ? String(format: NSLocalizedString("Collapse %@", comment: ""), accessibilityCustomActionName)
+ : String(format: NSLocalizedString("Expand %@", comment: ""), accessibilityCustomActionName)
accessibilityCustomActions = [
UIAccessibilityCustomAction(
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsInfoButtonItem.swift b/ios/MullvadVPN/View controllers/Settings/SettingsInfoButtonItem.swift
deleted file mode 100644
index 2d6326ed37..0000000000
--- a/ios/MullvadVPN/View controllers/Settings/SettingsInfoButtonItem.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// SettingsInfoButtonItem.swift
-// MullvadVPN
-//
-// Created by Jon Petersson on 2024-10-03.
-// Copyright © 2025 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-enum SettingsInfoButtonItem: CustomStringConvertible {
- case daita
- case daitaDirectOnly
-
- var description: String {
- switch self {
- case .daita:
- NSLocalizedString(
- """
- DAITA (Defence against AI-guided Traffic Analysis) hides patterns in your encrypted VPN traffic. \
- If anyone is monitoring your connection, this makes it significantly harder for them to identify \
- what websites you are visiting.
- It does this by carefully adding network noise and making all network packets the same size.
- Not all our servers are DAITA-enabled. Therefore, we use multihop automatically to enable DAITA \
- with any server.
- Attention: Be cautious if you have a limited data plan as this feature will increase your network \
- traffic.
- """,
- comment: ""
- )
- case .daitaDirectOnly:
- NSLocalizedString(
- """
- By enabling "Direct only" you will have to manually select a server that is DAITA-enabled. \
- This can cause you to end up in a blocked state until you have selected a compatible server \
- in the "Select location" view.
- """,
- comment: ""
- )
- }
- }
-}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
index 46aeb5a8f5..778c9faf0c 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
@@ -23,15 +23,13 @@ class SettingsViewController: UITableViewController {
weak var delegate: SettingsViewControllerDelegate?
private var dataSource: SettingsDataSource?
private let interactor: SettingsInteractor
- private let alertPresenter: AlertPresenter
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
- init(interactor: SettingsInteractor, alertPresenter: AlertPresenter) {
+ init(interactor: SettingsInteractor) {
self.interactor = interactor
- self.alertPresenter = alertPresenter
super.init(style: .grouped)
}
@@ -81,22 +79,6 @@ extension SettingsViewController: @preconcurrency SettingsDataSourceDelegate {
guard let route = item.navigationRoute else { return }
delegate?.settingsViewController(self, didRequestRoutePresentation: route)
}
-
- func showInfo(for item: SettingsInfoButtonItem) {
- let presentation = AlertPresentation(
- id: "settings-info-alert",
- icon: .info,
- message: item.description,
- buttons: [
- AlertAction(
- title: NSLocalizedString("Got it!", comment: ""),
- style: .default
- ),
- ]
- )
-
- alertPresenter.showAlert(presentation: presentation, animated: true)
- }
}
extension SettingsDataSource.Item {
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
index 8c236741a4..368fe5ab06 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift
@@ -37,7 +37,7 @@ struct DaitaFeature: ChipFeature {
// When multihop is enabled via DAITA without being explicitly enabled
// by the user, display combined indicator instead.
state.isMultihop && !settings.tunnelMultihopState.isEnabled
- ? NSLocalizedString("DAITA: Multihop", comment: "")
+ ? String(format: NSLocalizedString("%@: Multihop", comment: ""), NSLocalizedString("DAITA", comment: ""))
: NSLocalizedString("DAITA", comment: "")
}
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift
index a40881eb8d..4807c237ce 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift
@@ -201,8 +201,9 @@ extension ConnectionViewViewModel {
let observedTunnelState = tunnelStatus.observedState
var portAndTransport = ""
- if let inPort = observedTunnelState.connectionState?.remotePort {
- let protocolLayer = observedTunnelState.connectionState?.transportLayer == .tcp ? "TCP" : "UDP"
+ if let connectionState = observedTunnelState.connectionState {
+ let inPort = connectionState.remotePort
+ let protocolLayer = connectionState.transportLayer.name
portAndTransport = ":\(inPort) \(protocolLayer)"
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index 859787a8c0..caa855ebc4 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -553,7 +553,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}
private func configureWireguardPortsHeader(_ header: SettingsHeaderView) {
- let title = NSLocalizedString("WireGuard ports", comment: "")
+ let title = NSLocalizedString("WireGuard port", comment: "")
header.setAccessibilityIdentifier(.wireGuardPortsCell)
header.titleLabel.text = title
@@ -592,7 +592,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}
private func configureObfuscationHeader(_ header: SettingsHeaderView) {
- let title = NSLocalizedString("WireGuard Obfuscation", comment: "")
+ let title = NSLocalizedString("WireGuard obfuscation", comment: "")
header.setAccessibilityIdentifier(.wireGuardObfuscationCell)
header.titleLabel.text = title
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
index 1b0e76a397..1e7a5070cf 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsInfoButtonItem.swift
@@ -21,52 +21,59 @@ enum VPNSettingsInfoButtonItem: CustomStringConvertible {
var description: String {
switch self {
case .localNetworkSharing:
- NSLocalizedString(
- """
- This feature allows access to other devices on the local network, such as for sharing, printing, \
- streaming, etc.
- Attention: toggling “Local network sharing” requires restarting the VPN connection.
- """,
+ [NSLocalizedString(
+ "This feature allows access to other devices on the local network, " +
+ "such as for sharing, printing, streaming, etc.",
comment: ""
- )
- case .contentBlockers:
- NSLocalizedString(
- """
- When this feature is enabled it stops the device from contacting certain \
- domains or websites known for distributing ads, malware, trackers and more. \
-
- This might cause issues on certain websites, services, and apps.
- Attention: this setting cannot be used in combination with **Use custom DNS server**.
- """,
+ ), NSLocalizedString(
+ "Attention: toggling “Local network sharing” requires restarting the VPN connection.",
comment: ""
- )
+ )].joinedParagraphs(lineBreaks: 1)
+ case .contentBlockers:
+ [
+ NSLocalizedString(
+ "When this feature is enabled it stops the device from contacting certain " +
+ "domains or websites known for distributing ads, malware, trackers and more.",
+ comment: ""
+ ),
+ NSLocalizedString(
+ "This might cause issues on certain websites, services, and apps.",
+ comment: ""
+ ),
+ String(
+ format: NSLocalizedString(
+ "Attention: this setting cannot be used in combination with **“%@“**.",
+ comment: ""
+ ),
+ NSLocalizedString("Use custom DNS server", comment: "")
+ ),
+ ]
+ .joinedParagraphs(lineBreaks: 1)
case .blockMalware:
NSLocalizedString(
- """
- Warning: The malware blocker is not an anti-virus and should not \
- be treated as such, this is just an extra layer of protection.
- """,
+ "Warning: The malware blocker is not an anti-virus and should not be treated as such," +
+ " this is just an extra layer of protection.",
comment: ""
)
case let .wireGuardPorts(portsString):
- String(
- format: NSLocalizedString(
- """
- The automatic setting will randomly choose from the valid port ranges shown below.
- The custom port can be any value inside the valid ranges:
- %@
- """,
+ [
+ NSLocalizedString(
+ "The automatic setting will randomly choose from the valid port ranges shown below.",
comment: ""
),
- portsString
- )
+ String(
+ format: NSLocalizedString(
+ "The custom port can be any value inside the valid ranges: %@.",
+ comment: ""
+ ),
+ portsString
+ ),
+ ].joinedParagraphs(lineBreaks: 1)
case .wireGuardObfuscation:
NSLocalizedString(
- """
- Obfuscation hides the WireGuard traffic inside another protocol. \
- It can be used to help circumvent censorship and other types of filtering, \
- where a plain WireGuard connection would be blocked.
- """,
+ "Obfuscation hides the WireGuard traffic inside another protocol. " +
+ "It can be used to help circumvent censorship and other types of " +
+ "filtering, where a plain WireGuard connection would be blocked.",
comment: ""
)
case .wireGuardObfuscationPort:
@@ -75,21 +82,21 @@ enum VPNSettingsInfoButtonItem: CustomStringConvertible {
comment: ""
)
case .quantumResistance:
- NSLocalizedString(
- """
- This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.
- It does this by performing an extra key exchange using a quantum safe algorithm and mixing \
- the result into WireGuard’s regular encryption.
- This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.
- """,
+ [NSLocalizedString(
+ "This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.",
comment: ""
- )
+ ), NSLocalizedString(
+ "It does this by performing an extra key exchange using a quantum safe algorithm and mixing " +
+ "the result into WireGuard’s regular encryption. This extra step uses approximately 500 kiB " +
+ "of traffic every time a new tunnel is established.",
+ comment: ""
+ )].joinedParagraphs(lineBreaks: 1)
+
case .multihop:
NSLocalizedString(
- """
- Multihop routes your traffic into one WireGuard server and out another, making it harder to trace.
- This results in increased latency but increases anonymity online.
- """,
+ "Multihop routes your traffic into one WireGuard server and out another, " +
+ "making it harder to trace. This results in increased latency but " +
+ "increases anonymity online.",
comment: ""
)
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift
index 8823ef46a4..aeb9b27c04 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewModel.swift
@@ -58,10 +58,10 @@ enum CustomDNSPrecondition {
case .conflictsWithOtherSettings:
return NSAttributedString(
- string: NSLocalizedString(
- "Disable all content blockers to activate this setting.",
+ string: String(format: NSLocalizedString(
+ "Disable all \"%@\" above to activate this setting.",
comment: ""
- ),
+ ), "DNS content blockers"),
attributes: [.font: preferredFont]
)
}
diff --git a/ios/MullvadVPN/Views/MullvadAlert.swift b/ios/MullvadVPN/Views/MullvadAlert.swift
index bad2b8a08e..c03c4a6d7a 100644
--- a/ios/MullvadVPN/Views/MullvadAlert.swift
+++ b/ios/MullvadVPN/Views/MullvadAlert.swift
@@ -20,7 +20,7 @@ struct MullvadAlert: Identifiable {
let id = UUID()
let type: AlertType
- let message: LocalizedStringKey
+ let messages: [LocalizedStringKey]
let action: Action?
let dismissButtonTitle: LocalizedStringKey
}
@@ -55,7 +55,7 @@ struct AlertModifier: ViewModifier {
private func alertContent(for alert: MullvadAlert) -> some View {
VStack(spacing: 16) {
alertIcon(for: alert.type)
- alertMessage(alert.message)
+ alertMessage(alert.messages)
VStack(spacing: 16) {
alertAction(for: alert.action)
alertAction(for: MullvadAlert.Action(
@@ -82,12 +82,16 @@ struct AlertModifier: ViewModifier {
}
@ViewBuilder
- private func alertMessage(_ message: LocalizedStringKey) -> some View {
- HStack {
- Text(message)
- .font(.mullvadSmall)
- .foregroundColor(.mullvadTextPrimary.opacity(0.6))
- Spacer()
+ private func alertMessage(_ messages: [LocalizedStringKey]) -> some View {
+ VStack {
+ ForEach(Array(messages.enumerated()), id: \.offset) { _, text in
+ HStack {
+ Text(text)
+ .font(.mullvadSmall)
+ .foregroundColor(.mullvadTextPrimary.opacity(0.6))
+ Spacer()
+ }
+ }
}
}
@@ -125,7 +129,7 @@ extension View {
.constant(
.init(
type: .warning,
- message: "Something needs to be done",
+ messages: ["Something needs to be done"],
action: .init(
type: .danger,
title: "Do it!",