Extract strings to translate

This commit is contained in:
mojganii committed 2025-08-08 12:33:32 +02:00
1 parent 40db8f24fc
commit 1926fe29fb
148 files changed
+3991 -2666

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
@@ -1,7 +0,0 @@
/*
Localizable.strings
MullvadVPN
Created by Mojgan on 2025-07-16.
Copyright © 2025 Mullvad VPN AB. All rights reserved.
*/
+2 -12
View File
@@ -18,20 +18,10 @@ public enum CustomRelayListError: LocalizedError, Hashable {
public var errorDescription: String? {
switch self {
case .duplicateName:
NSLocalizedString(
"DUPLICATE_CUSTOM_LISTS_ERROR",
tableName: "CustomLists",
value: "A custom list with this name exists, please choose a unique name.",
comment: ""
)
NSLocalizedString("A custom list with this name exists, please choose a unique name.", comment: "")
case .nameTooLong:
String(
format: NSLocalizedString(
"CUSTOM_LIST_NAME_TOO_LONG_ERROR",
tableName: "CustomLists",
value: "Name should be no longer than %i characters.",
comment: ""
),
format: NSLocalizedString("Name should be no longer than %i characters.", comment: ""),
NameInputFormatter.maxLength
)
}
@@ -80,12 +80,7 @@ public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomString
public var description: String {
switch self {
case .automatic:
NSLocalizedString(
"WIREGUARD_OBFUSCATION_UDP_TCP_PORT_AUTOMATIC",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
)
NSLocalizedString("Automatic", comment: "")
case .port80:
"80"
case .port5001:
@@ -110,12 +105,7 @@ public enum WireGuardObfuscationShadowsocksPort: Codable, Equatable, CustomStrin
public var description: String {
switch self {
case .automatic:
NSLocalizedString(
"WIREGUARD_OBFUSCATION_SHADOWSOCKS_PORT_AUTOMATIC",
tableName: "VPNSettings",
value: "Automatic",
comment: ""
)
NSLocalizedString("Automatic", comment: "")
case let .custom(port):
String(port)
}
+2 -2
View File
@@ -48,9 +48,9 @@ public extension AccessMethodKind {
case .direct, .bridges, .encryptedDNS:
""
case .shadowsocks:
NSLocalizedString("SHADOWSOCKS", tableName: "APIAccess", value: "Shadowsocks", comment: "")
NSLocalizedString("Shadowsocks", comment: "")
case .socks5:
NSLocalizedString("SOCKS_V5", tableName: "APIAccess", value: "Socks5", comment: "").uppercased()
NSLocalizedString("Socks5", comment: "").uppercased()
}
}
+14 -83
View File
@@ -961,6 +961,7 @@
F041BE532C9878B60083EC28 /* ConnectionConfigurationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F041BE522C9878B60083EC28 /* ConnectionConfigurationBuilder.swift */; };
F04413612BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
F04413622BA45CE30018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
F04789ED2E3A4FEB0011E3A5 /* ApplicationLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04789EB2E3A4FEB0011E3A5 /* ApplicationLanguage.swift */; };
F048BFA22D31843000251CB9 /* ChangeLogModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F048BFA12D31842B00251CB9 /* ChangeLogModel.swift */; };
F04AF92D2C466013004A8314 /* EphemeralPeerNegotiationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04AF92C2C466013004A8314 /* EphemeralPeerNegotiationState.swift */; };
F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; };
@@ -980,16 +981,6 @@
F05769BB2C6661EE00D9778B /* TunnelSettingsStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05769BA2C6661EE00D9778B /* TunnelSettingsStrategy.swift */; };
F05919752C45194B00C301F3 /* EphemeralPeerKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05919742C45194B00C301F3 /* EphemeralPeerKey.swift */; };
F05919802C45515200C301F3 /* EphemeralPeerExchangeActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A948809A2BC9308D0090A44C /* EphemeralPeerExchangeActor.swift */; };
F05DCE9E2E38C563009A9B85 /* AppLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05DCE872E38C563009A9B85 /* AppLanguage.swift */; };
F05DCE9F2E38C563009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA02E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA12E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA22E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA32E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA42E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA52E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA62E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05DCEA72E38C5F1009A9B85 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F05DCE9C2E38C563009A9B85 /* Localizable.strings */; };
F05F39972B21C735006E60A7 /* RelayCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820675A26E6576800655B05 /* RelayCache.swift */; };
F05F39982B21C73C006E60A7 /* CachedRelays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA87626B024A600B8C587 /* CachedRelays.swift */; };
F06045E62B231EB700B2D37A /* URLSessionTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045E52B231EB700B2D37A /* URLSessionTransport.swift */; };
@@ -1069,6 +1060,7 @@
F0B894F32BF7526700817A42 /* RelaySelector+Wireguard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B894F22BF7526700817A42 /* RelaySelector+Wireguard.swift */; };
F0B894F52BF7528700817A42 /* RelaySelector+Shadowsocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B894F42BF7528700817A42 /* RelaySelector+Shadowsocks.swift */; };
F0BE65372B9F136A005CC385 /* LocationSectionHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0BE65362B9F136A005CC385 /* LocationSectionHeaderFooterView.swift */; };
F0C1374F2E45EF1F006307BF /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = F0C1374E2E45EF1F006307BF /* Localizable.xcstrings */; };
F0C13FE42C64F7CB00BD087D /* DAITASettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C13FE32C64F7CB00BD087D /* DAITASettings.swift */; };
F0C13FE62C64FB3400BD087D /* TunnelSettingsV6.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C13FE52C64FB3400BD087D /* TunnelSettingsV6.swift */; };
F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */; };
@@ -2431,6 +2423,7 @@
F041BE4E2C983C2B0083EC28 /* DAITASettingsPromptItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITASettingsPromptItem.swift; sourceTree = "<group>"; };
F041BE522C9878B60083EC28 /* ConnectionConfigurationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionConfigurationBuilder.swift; sourceTree = "<group>"; };
F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomListLocationNodeBuilder.swift; sourceTree = "<group>"; };
F04789EB2E3A4FEB0011E3A5 /* ApplicationLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationLanguage.swift; sourceTree = "<group>"; };
F048BFA12D31842B00251CB9 /* ChangeLogModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogModel.swift; sourceTree = "<group>"; };
F04AF92C2C466013004A8314 /* EphemeralPeerNegotiationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerNegotiationState.swift; sourceTree = "<group>"; };
F04DD3D72C130DF600E03E28 /* TunnelSettingsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManager.swift; sourceTree = "<group>"; };
@@ -2452,27 +2445,6 @@
F05919782C45402E00C301F3 /* SingleHopEphemeralPeerExchanger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleHopEphemeralPeerExchanger.swift; sourceTree = "<group>"; };
F059197C2C454C9200C301F3 /* MultiHopEphemeralPeerExchanger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiHopEphemeralPeerExchanger.swift; sourceTree = "<group>"; };
F059197E2C454CE000C301F3 /* EphemeralPeerExchangingProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerExchangingProtocol.swift; sourceTree = "<group>"; };
F05DCE872E38C563009A9B85 /* AppLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLanguage.swift; sourceTree = "<group>"; };
F05DCE882E38C563009A9B85 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE892E38C563009A9B85 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE8A2E38C563009A9B85 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE8B2E38C563009A9B85 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE8C2E38C563009A9B85 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE8D2E38C563009A9B85 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE8E2E38C563009A9B85 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE8F2E38C563009A9B85 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE902E38C563009A9B85 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE912E38C563009A9B85 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = my.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE922E38C563009A9B85 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE932E38C563009A9B85 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE942E38C563009A9B85 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE952E38C563009A9B85 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
F05DCE962E38C563009A9B85 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE972E38C563009A9B85 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE982E38C563009A9B85 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE992E38C563009A9B85 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
F05DCE9A2E38C563009A9B85 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
F05DCE9B2E38C563009A9B85 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
F06045E52B231EB700B2D37A /* URLSessionTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTransport.swift; sourceTree = "<group>"; };
F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksTransport.swift; sourceTree = "<group>"; };
F06045EB2B2322A500B2D37A /* Jittered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Jittered.swift; sourceTree = "<group>"; };
@@ -2526,6 +2498,7 @@
F0B894F22BF7526700817A42 /* RelaySelector+Wireguard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelaySelector+Wireguard.swift"; sourceTree = "<group>"; };
F0B894F42BF7528700817A42 /* RelaySelector+Shadowsocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelaySelector+Shadowsocks.swift"; sourceTree = "<group>"; };
F0BE65362B9F136A005CC385 /* LocationSectionHeaderFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationSectionHeaderFooterView.swift; sourceTree = "<group>"; };
F0C1374E2E45EF1F006307BF /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
F0C13FE32C64F7CB00BD087D /* DAITASettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAITASettings.swift; sourceTree = "<group>"; };
F0C13FE52C64FB3400BD087D /* TunnelSettingsV6.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV6.swift; sourceTree = "<group>"; };
F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderIdentifier.swift; sourceTree = "<group>"; };
@@ -2788,11 +2761,11 @@
06799ABD28F98E1D00ACD94E /* MullvadREST */ = {
isa = PBXGroup;
children = (
7A2C0E872D82E450003D8048 /* MullvadAPI */,
F06045F02B2324DA00B2D37A /* ApiHandlers */,
062B45A228FD4C0F00746E77 /* Assets */,
7AD63A422CDA661B00445268 /* Extensions */,
582FFA82290A84E700895745 /* Info.plist */,
7A2C0E872D82E450003D8048 /* MullvadAPI */,
06799ABE28F98E1D00ACD94E /* MullvadREST.h */,
F0DC779F2B2222D20087F09D /* Relay */,
06FAE67B28F83CA50033DD93 /* REST.swift */,
@@ -3717,6 +3690,7 @@
589A454A28DDF59B00565204 /* Shared */ = {
isa = PBXGroup;
children = (
F04789EB2E3A4FEB0011E3A5 /* ApplicationLanguage.swift */,
58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */,
58C76A072A33850E00100D75 /* ApplicationTarget.swift */,
F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */,
@@ -3949,7 +3923,6 @@
58F3C0A824A50C0E003E76BE /* Assets */,
58ECD29023F178FD004298B6 /* Configurations */,
584F991F2902CBDD001F858D /* Frameworks */,
F05DCE9D2E38C563009A9B85 /* Localizations */,
01EF6F2D2B6A51B100125696 /* mullvad-api.h */,
8556EB512B9A1C6900D26DD4 /* MullvadApi.swift */,
58D223F4294C8FF00029F5F8 /* MullvadLogging */,
@@ -4228,6 +4201,7 @@
children = (
5859A55429CD9DD800F66591 /* changes.txt */,
587DCCEE287D84A500CE821E /* countries.geo.json */,
F0C1374E2E45EF1F006307BF /* Localizable.xcstrings */,
);
path = Assets;
sourceTree = "<group>";
@@ -4691,15 +4665,6 @@
path = PostQuantum;
sourceTree = "<group>";
};
F05DCE9D2E38C563009A9B85 /* Localizations */ = {
isa = PBXGroup;
children = (
F05DCE872E38C563009A9B85 /* AppLanguage.swift */,
F05DCE9C2E38C563009A9B85 /* Localizable.strings */,
);
path = Localizations;
sourceTree = "<group>";
};
F06045F02B2324DA00B2D37A /* ApiHandlers */ = {
isa = PBXGroup;
children = (
@@ -5616,9 +5581,9 @@
"zh-Hant",
ru,
pl,
tr,
fi,
my,
tr,
);
mainGroup = 58CE5E57224146200008646E;
packageReferences = (
@@ -5658,7 +5623,6 @@
buildActionMask = 2147483647;
files = (
062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */,
F05DCEA22E38C5F1009A9B85 /* Localizable.strings in Resources */,
7A95B67B2D5F758300687524 /* relays.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -5681,7 +5645,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F05DCEA52E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5699,9 +5662,9 @@
A9BA08312BA32FA9005A7A2D /* PrivacyInfo.xcprivacy in Resources */,
7A83C3FF2A55B72E00DFB83A /* MullvadVPNApp.xctestplan in Resources */,
58727283265D173C00F315B2 /* LaunchScreen.storyboard in Resources */,
F05DCE9F2E38C563009A9B85 /* Localizable.strings in Resources */,
5859A55529CD9DD900F66591 /* changes.txt in Resources */,
587DCCEF287D84A500CE821E /* countries.geo.json in Resources */,
F0C1374F2E45EF1F006307BF /* Localizable.xcstrings in Resources */,
58CE5E6B224146210008646E /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -5711,7 +5674,6 @@
buildActionMask = 2147483647;
files = (
A9BA08322BA32FB6005A7A2D /* PrivacyInfo.xcprivacy in Resources */,
F05DCEA02E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5719,7 +5681,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F05DCEA12E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5727,7 +5688,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F05DCEA32E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5735,7 +5695,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F05DCEA42E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5750,7 +5709,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F05DCEA62E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5786,7 +5744,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F05DCEA72E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -6410,6 +6367,7 @@
7A8A190A2CE5FFE9000BCB5B /* SettingsDAITAView.swift in Sources */,
F0E8E4C92A604E7400ED26A3 /* AccountDeletionInteractor.swift in Sources */,
449E9A6D2D283A2500F8574A /* ConnectionViewComponentPreview.swift in Sources */,
F04789ED2E3A4FEB0011E3A5 /* ApplicationLanguage.swift in Sources */,
7A5869952B32E9C700640D27 /* LinkButton.swift in Sources */,
F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */,
5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */,
@@ -6524,7 +6482,6 @@
F91CCBFA2DFAC8ED007F1925 /* DeviceListView.swift in Sources */,
7A9CCCBF2A96302800DD6A34 /* SettingsCoordinator.swift in Sources */,
58F70FE52AEA707800E6890E /* StoreTransactionLog.swift in Sources */,
F05DCE9E2E38C563009A9B85 /* AppLanguage.swift in Sources */,
F9394EF02DC0B58D009595EA /* MullvadListNavigationItemView.swift in Sources */,
582AE3102440A6CA00E6733A /* InputTextFormatter.swift in Sources */,
7A6F2FAD2AFD3DA7006D0856 /* CustomDNSViewController.swift in Sources */,
@@ -7330,36 +7287,6 @@
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
F05DCE9C2E38C563009A9B85 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
F05DCE882E38C563009A9B85 /* da */,
F05DCE892E38C563009A9B85 /* de */,
F05DCE8A2E38C563009A9B85 /* en */,
F05DCE8B2E38C563009A9B85 /* es */,
F05DCE8C2E38C563009A9B85 /* fi */,
F05DCE8D2E38C563009A9B85 /* fr */,
F05DCE8E2E38C563009A9B85 /* it */,
F05DCE8F2E38C563009A9B85 /* ja */,
F05DCE902E38C563009A9B85 /* ko */,
F05DCE912E38C563009A9B85 /* my */,
F05DCE922E38C563009A9B85 /* nb */,
F05DCE932E38C563009A9B85 /* nl */,
F05DCE942E38C563009A9B85 /* pl */,
F05DCE952E38C563009A9B85 /* pt-PT */,
F05DCE962E38C563009A9B85 /* ru */,
F05DCE972E38C563009A9B85 /* sv */,
F05DCE982E38C563009A9B85 /* th */,
F05DCE992E38C563009A9B85 /* tr */,
F05DCE9A2E38C563009A9B85 /* zh-Hans */,
F05DCE9B2E38C563009A9B85 /* zh-Hant */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
06799AD428F98E1D00ACD94E /* Debug */ = {
isa = XCBuildConfiguration;
@@ -7771,6 +7698,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -7832,6 +7760,7 @@
ONLY_ACTIVE_ARCH = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -8483,6 +8412,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -9338,6 +9268,7 @@
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 6.0;
@@ -28,11 +28,7 @@ extension CustomDateComponentsFormatting {
let dateComponents = calendar.dateComponents([.year, .day], from: start, to: max(start, end))
guard !isLessThanOneDay(dateComponents: dateComponents) else {
return NSLocalizedString(
"CUSTOM_DATE_COMPONENTS_FORMATTING_LESS_THAN_ONE_DAY",
value: "Less than a day",
comment: ""
)
return NSLocalizedString("Less than a day", comment: "")
}
let formatter = DateComponentsFormatter()
@@ -64,12 +64,7 @@ class HeaderBarView: UIView {
let accountButton: UIButton = {
let button = makeHeaderBarButton(with: UIImage.Buttons.account)
button.setAccessibilityIdentifier(.accountButton)
button.accessibilityLabel = NSLocalizedString(
"HEADER_BAR_ACCOUNT_BUTTON_ACCESSIBILITY_LABEL",
tableName: "HeaderBar",
value: "Account",
comment: ""
)
button.accessibilityLabel = NSLocalizedString("Account", comment: "")
button.heightAnchor.constraint(equalToConstant: UIMetrics.Button.barButtonSize).isActive = true
button.widthAnchor.constraint(equalTo: button.heightAnchor, multiplier: 1).isActive = true
return button
@@ -78,12 +73,7 @@ class HeaderBarView: UIView {
let settingsButton: UIButton = {
let button = makeHeaderBarButton(with: UIImage.Buttons.settings)
button.setAccessibilityIdentifier(.settingsButton)
button.accessibilityLabel = NSLocalizedString(
"HEADER_BAR_SETTINGS_BUTTON_ACCESSIBILITY_LABEL",
tableName: "HeaderBar",
value: "Settings",
comment: ""
)
button.accessibilityLabel = NSLocalizedString("Settings", comment: "")
button.heightAnchor.constraint(equalToConstant: UIMetrics.Button.barButtonSize).isActive = true
button.widthAnchor.constraint(equalTo: button.heightAnchor, multiplier: 1).isActive = true
return button
@@ -123,12 +113,7 @@ class HeaderBarView: UIView {
private var timeLeft: Date? {
didSet {
if let timeLeft {
let formattedTimeLeft = NSLocalizedString(
"TIME_LEFT_HEADER_VIEW",
tableName: "Account",
value: "Time left: %@",
comment: ""
)
let formattedTimeLeft = NSLocalizedString("Time left: %@", comment: "")
timeLeftLabel.text = String(
format: formattedTimeLeft,
CustomDateComponentsFormatting.localizedString(
@@ -146,12 +131,7 @@ class HeaderBarView: UIView {
private var deviceName: String? {
didSet {
if let deviceName {
let formattedDeviceName = NSLocalizedString(
"DEVICE_NAME_HEADER_VIEW",
tableName: "Account",
value: "Device name: %@",
comment: ""
)
let formattedDeviceName = NSLocalizedString("Device name: %@", comment: "")
deviceNameLabel.text = String(format: formattedDeviceName, deviceName)
} else {
deviceNameLabel.text = ""
@@ -146,12 +146,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked
}
)
)
controller.title = NSLocalizedString(
"MANAGE_DEVICES_TITLE",
tableName: "DeviceManagement",
value: "Manage devices",
comment: ""
)
controller.title = NSLocalizedString("Manage devices", comment: "")
let doneButton = UIBarButtonItem(
systemItem: .done,
primaryAction: UIAction(handler: { _ in
@@ -172,12 +167,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked
message: message,
buttons: [
AlertAction(
title: NSLocalizedString(
"ERROR_ALERT_OK_ACTION",
tableName: "Common",
value: "Got it!",
comment: ""
),
title: NSLocalizedString("Got it!", comment: ""),
style: .default
),
]
@@ -249,9 +239,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked
private func showRestorePurchasesInfo() {
let message = NSLocalizedString(
"RESTORE_PURCHASES_DIALOG_MESSAGE",
tableName: "Account",
value: """
"""
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.
@@ -262,20 +250,10 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked
let presentation = AlertPresentation(
id: "account-device-info-alert",
icon: .info,
title: NSLocalizedString(
"RESTORE_PURCHASES_DIALOG_TITLE",
tableName: "Account",
value: "If you haven’t received additional VPN time after purchasing",
comment: ""
),
title: NSLocalizedString("If you haven’t received additional VPN time after purchasing", comment: ""),
message: message,
buttons: [AlertAction(
title: NSLocalizedString(
"RESTORE_PURCHASES_DIALOG_OK_ACTION",
tableName: "Account",
value: "Got it!",
comment: ""
),
title: NSLocalizedString("Got it!", comment: ""),
style: .default
)]
)
@@ -286,9 +264,6 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked
func showFailToFetchProducts() {
let message = NSLocalizedString(
"WELCOME_FAILED_TO_FETCH_PRODUCTS_DIALOG",
tableName: "Welcome",
value:
"""
Failed to connect to App store, please try again later.
""",
@@ -301,12 +276,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting, @unchecked
message: message,
buttons: [
AlertAction(
title: NSLocalizedString(
"WELCOME_FAILED_TO_FETCH_PRODUCTS_OK_ACTION",
tableName: "Welcome",
value: "Got it!",
comment: ""
),
title: NSLocalizedString("Got it!", comment: ""),
style: .default
),
]
@@ -34,22 +34,12 @@ 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(
"whats_new_title",
tableName: "Changelog",
value: "What's new",
comment: ""
)
changeLogViewController.navigationItem.title = NSLocalizedString("What's new", comment: "")
switch route {
case .changelog:
let barButtonItem = UIBarButtonItem(
title: NSLocalizedString(
"CHANGELOG_NAVIGATION_DONE_BUTTON",
tableName: "Changelog",
value: "Done",
comment: ""
),
title: NSLocalizedString("Done", comment: ""),
primaryAction: UIAction { [weak self] _ in
guard let self else { return }
didFinish?(self)
@@ -43,19 +43,9 @@ class AddCustomListCoordinator: Coordinator, Presentable, Presenting {
)
controller.delegate = self
controller.navigationItem.title = NSLocalizedString(
"CUSTOM_LISTS_NAVIGATION_EDIT_TITLE",
tableName: "CustomLists",
value: "New custom list",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("New custom list", comment: "")
controller.saveBarButton.title = NSLocalizedString(
"CUSTOM_LISTS_NAVIGATION_CREATE_BUTTON",
tableName: "CustomLists",
value: "Create",
comment: ""
)
controller.saveBarButton.title = NSLocalizedString("Create", comment: "")
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(
systemItem: .cancel,
@@ -40,12 +40,7 @@ class AddLocationsCoordinator: Coordinator, Presentable, Presenting {
)
controller.delegate = self
controller.navigationItem.title = NSLocalizedString(
"ADD_LOCATIONS_NAVIGATION_TITLE",
tableName: "AddLocations",
value: "Add locations",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("Add locations", comment: "")
navigationController.pushViewController(controller, animated: true)
}
@@ -40,13 +40,13 @@ enum CustomListItemIdentifier: Hashable, CaseIterable {
var text: String? {
switch self {
case .name:
NSLocalizedString("NAME", tableName: "CustomLists", value: "Name", comment: "")
NSLocalizedString("Name", comment: "")
case .addLocations:
NSLocalizedString("ADD", tableName: "CustomLists", value: "Add locations", comment: "")
NSLocalizedString("Add locations", comment: "")
case .editLocations:
NSLocalizedString("EDIT", tableName: "CustomLists", value: "Edit locations", comment: "")
NSLocalizedString("Edit locations", comment: "")
case .deleteList:
NSLocalizedString("Delete", tableName: "CustomLists", value: "Delete list", comment: "")
NSLocalizedString("Delete list", comment: "")
}
}
@@ -47,12 +47,7 @@ class CustomListViewController: UIViewController {
lazy var saveBarButton: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(
title: NSLocalizedString(
"CUSTOM_LIST_NAVIGATION_SAVE_BUTTON",
tableName: "CustomLists",
value: "Save",
comment: ""
),
title: NSLocalizedString("Save", comment: ""),
primaryAction: UIAction { [weak self] _ in
self?.onSave()
}
@@ -163,12 +158,7 @@ class CustomListViewController: UIViewController {
private func onDelete() {
let message = NSMutableAttributedString(
markdownString: NSLocalizedString(
"CUSTOM_LISTS_DELETE_PROMPT",
tableName: "CustomLists",
value: "Do you want to delete the list **\(subject.value.name)**?",
comment: ""
),
markdownString: NSLocalizedString("Do you want to delete the list **\(subject.value.name)**?", comment: ""),
options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body))
)
@@ -178,12 +168,7 @@ class CustomListViewController: UIViewController {
attributedMessage: message,
buttons: [
AlertAction(
title: NSLocalizedString(
"CUSTOM_LISTS_DELETE_BUTTON",
tableName: "CustomLists",
value: "Delete list",
comment: ""
),
title: NSLocalizedString("Delete list", comment: ""),
style: .destructive,
accessibilityId: .confirmDeleteCustomListButton,
handler: {
@@ -192,12 +177,7 @@ class CustomListViewController: UIViewController {
}
),
AlertAction(
title: NSLocalizedString(
"CUSTOM_LISTS_CANCEL_BUTTON",
tableName: "CustomLists",
value: "Cancel",
comment: ""
),
title: NSLocalizedString("Cancel", comment: ""),
style: .default,
accessibilityId: .cancelDeleteCustomListButton
),
@@ -58,12 +58,7 @@ class EditCustomListCoordinator: Coordinator, Presentable, Presenting {
)
controller.delegate = self
controller.navigationItem.title = NSLocalizedString(
"CUSTOM_LIST_NAVIGATION_TITLE",
tableName: "CustomLists",
value: subject.value.name,
comment: ""
)
controller.navigationItem.title = subject.value.name
navigationController.interactivePopGestureRecognizer?.delegate = self
navigationController.pushViewController(controller, animated: true)
@@ -97,12 +92,7 @@ class EditCustomListCoordinator: Coordinator, Presentable, Presenting {
private func presentUnsavedChangesDialog() {
let message = NSMutableAttributedString(
markdownString: NSLocalizedString(
"CUSTOM_LISTS_UNSAVED_CHANGES_PROMPT",
tableName: "CustomLists",
value: "You have unsaved changes.",
comment: ""
),
markdownString: NSLocalizedString("You have unsaved changes.", comment: ""),
options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body))
)
@@ -112,24 +102,14 @@ class EditCustomListCoordinator: Coordinator, Presentable, Presenting {
attributedMessage: message,
buttons: [
AlertAction(
title: NSLocalizedString(
"CUSTOM_LISTS_DISCARD_CHANGES_BUTTON",
tableName: "CustomLists",
value: "Discard changes",
comment: ""
),
title: NSLocalizedString("Discard changes", comment: ""),
style: .destructive,
handler: {
self.didCancel?(self)
}
),
AlertAction(
title: NSLocalizedString(
"CUSTOM_LISTS_BACK_TO_EDITING_BUTTON",
tableName: "CustomLists",
value: "Back to editing",
comment: ""
),
title: NSLocalizedString("Back to editing", comment: ""),
style: .default
),
]
@@ -41,12 +41,7 @@ class EditLocationsCoordinator: Coordinator, Presentable, Presenting {
)
controller.delegate = self
controller.navigationItem.title = NSLocalizedString(
"EDIT_LOCATIONS_NAVIGATION_TITLE",
tableName: "EditLocations",
value: "Edit locations",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("Edit locations", comment: "")
navigationController.pushViewController(controller, animated: true)
}
}
@@ -92,12 +92,7 @@ class ListCustomListViewController: UIViewController {
}
private func configureNavigationItem() {
navigationItem.title = NSLocalizedString(
"LIST_CUSTOM_LIST_NAVIGATION_TITLE",
tableName: "CustomList",
value: "Edit custom list",
comment: ""
)
navigationItem.title = NSLocalizedString("Edit custom list", comment: "")
navigationItem.rightBarButtonItem = UIBarButtonItem(
systemItem: .done,
@@ -181,9 +181,7 @@ extension LocationCoordinator: @preconcurrency LocationViewControllerWrapperDele
func navigateToCustomLists(nodes: [LocationNode]) {
let actionSheet = UIAlertController(
title: NSLocalizedString(
"ACTION_SHEET_TITLE", tableName: "CustomLists", value: "Custom lists", comment: ""
),
title: NSLocalizedString("Custom lists", comment: ""),
message: nil,
preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet
)
@@ -191,9 +189,7 @@ extension LocationCoordinator: @preconcurrency LocationViewControllerWrapperDele
actionSheet.view.tintColor = .AlertController.tintColor
let addCustomListAction = UIAlertAction(
title: NSLocalizedString(
"ACTION_SHEET_ADD_LIST_BUTTON", tableName: "CustomLists", value: "Add new list", comment: ""
),
title: NSLocalizedString("Add new list", comment: ""),
style: .default,
handler: { [weak self] _ in
self?.showAddCustomList(nodes: nodes)
@@ -203,9 +199,7 @@ extension LocationCoordinator: @preconcurrency LocationViewControllerWrapperDele
actionSheet.addAction(addCustomListAction)
let editAction = UIAlertAction(
title: NSLocalizedString(
"ACTION_SHEET_EDIT_LISTS_BUTTON", tableName: "CustomLists", value: "Edit lists", comment: ""
),
title: NSLocalizedString("Edit lists", comment: ""),
style: .default,
handler: { [weak self] _ in
self?.showEditCustomLists(nodes: nodes)
@@ -216,12 +210,7 @@ extension LocationCoordinator: @preconcurrency LocationViewControllerWrapperDele
actionSheet.addAction(editAction)
actionSheet.addAction(UIAlertAction(
title: NSLocalizedString(
"CUSTOM_LIST_ACTION_SHEET_CANCEL_BUTTON",
tableName: "CustomLists",
value: "Cancel",
comment: ""
),
title: NSLocalizedString("Cancel", comment: ""),
style: .cancel
))
@@ -126,12 +126,7 @@ final class LoginCoordinator: Coordinator, Presenting {
message: errorDescription,
buttons: [
AlertAction(
title: NSLocalizedString(
"ERROR_ALERT_OK_ACTION",
tableName: "DeviceManagement",
value: "Got it!",
comment: ""
),
title: NSLocalizedString("Got it!", comment: ""),
style: .default
),
]
@@ -65,20 +65,10 @@ extension ProfileVoucherCoordinator: @preconcurrency AddCreditSucceededViewContr
}
func header(in controller: AddCreditSucceededViewController) -> String {
NSLocalizedString(
"REDEEM_VOUCHER_SUCCESS_TITLE",
tableName: "ProfileRedeemVoucher",
value: "Voucher was successfully redeemed.",
comment: ""
)
NSLocalizedString("Voucher was successfully redeemed.", comment: "")
}
func titleForAction(in controller: AddCreditSucceededViewController) -> String {
NSLocalizedString(
"REDEEM_VOUCHER_DISMISS_BUTTON",
tableName: "ProfileRedeemVoucher",
value: "Got it!",
comment: ""
)
NSLocalizedString("Got it!", comment: "")
}
}
@@ -52,26 +52,11 @@ class AddAccessMethodCoordinator: Coordinator, Presentable, Presenting {
}
private func setUpControllerNavigationItem(_ controller: MethodSettingsViewController) {
controller.navigationItem.prompt = NSLocalizedString(
"METHOD_SETTINGS_NAVIGATION_ADD_PROMPT",
tableName: "APIAccess",
value: "The app will test the method before saving.",
comment: ""
)
controller.navigationItem.prompt = NSLocalizedString("The app will test the method before saving.", comment: "")
controller.navigationItem.title = NSLocalizedString(
"METHOD_SETTINGS_NAVIGATION_ADD_TITLE",
tableName: "APIAccess",
value: "Add access method",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("Add access method", comment: "")
controller.saveBarButton.title = NSLocalizedString(
"METHOD_SETTINGS_NAVIGATION_ADD_BUTTON",
tableName: "APIAccess",
value: "Add",
comment: ""
)
controller.saveBarButton.title = NSLocalizedString("Add", comment: "")
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(
systemItem: .cancel,
@@ -28,19 +28,9 @@ extension TextCellContentConfiguration {
var localizedDescription: String {
switch self {
case .required:
NSLocalizedString(
"REQUIRED_PLACEHOLDER",
tableName: "APIAccess",
value: "Required",
comment: ""
)
NSLocalizedString("Required", comment: "")
case .optional:
NSLocalizedString(
"OPTIONAL_PLACEHOLDER",
tableName: "APIAccess",
value: "Optional",
comment: ""
)
NSLocalizedString("Optional", comment: "")
}
}
}
@@ -34,13 +34,13 @@ enum ShadowsocksItemIdentifier: Hashable, CaseIterable {
var text: String {
switch self {
case .server:
NSLocalizedString("SHADOWSOCKS_SERVER", tableName: "APIAccess", value: "Server", comment: "")
NSLocalizedString("Server", comment: "")
case .port:
NSLocalizedString("SHADOWSOCKS_PORT", tableName: "APIAccess", value: "Port", comment: "")
NSLocalizedString("Port", comment: "")
case .password:
NSLocalizedString("SHADOWSOCKS_PASSWORD", tableName: "APIAccess", value: "Password", comment: "")
NSLocalizedString("Password", comment: "")
case .cipher:
NSLocalizedString("SHADOWSOCKS_CIPHER", tableName: "APIAccess", value: "Cipher", comment: "")
NSLocalizedString("Cipher", comment: "")
}
}
}
@@ -49,15 +49,15 @@ enum SocksItemIdentifier: Hashable, CaseIterable {
var text: String {
switch self {
case .server:
NSLocalizedString("SOCKS_SERVER", tableName: "APIAccess", value: "Server", comment: "")
NSLocalizedString("Server", comment: "")
case .port:
NSLocalizedString("SOCKS_PORT", tableName: "APIAccess", value: "Port", comment: "")
NSLocalizedString("Port", comment: "")
case .authentication:
NSLocalizedString("SOCKS_AUTHENTICATION", tableName: "APIAccess", value: "Authentication", comment: "")
NSLocalizedString("Authentication", comment: "")
case .username:
NSLocalizedString("SOCKS_USERNAME", tableName: "APIAccess", value: "Username", comment: "")
NSLocalizedString("Username", comment: "")
case .password:
NSLocalizedString("SOCKS_PASSWORD", tableName: "APIAccess", value: "Password", comment: "")
NSLocalizedString("Password", comment: "")
}
}
}
@@ -74,26 +74,11 @@ extension EditAccessMethodCoordinator: @preconcurrency EditAccessMethodViewContr
alertPresenter: AlertPresenter(context: self)
)
controller.navigationItem.prompt = NSLocalizedString(
"METHOD_SETTINGS_NAVIGATION_ADD_PROMPT",
tableName: "APIAccess",
value: "The app will test the method before saving.",
comment: ""
)
controller.navigationItem.prompt = NSLocalizedString("The app will test the method before saving.", comment: "")
controller.navigationItem.title = NSLocalizedString(
"METHOD_SETTINGS_NAVIGATION_ADD_TITLE",
tableName: "APIAccess",
value: "Method settings",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("Method settings", comment: "")
controller.saveBarButton.title = NSLocalizedString(
"METHOD_SETTINGS_NAVIGATION_ADD_BUTTON",
tableName: "APIAccess",
value: "Save",
comment: ""
)
controller.saveBarButton.title = NSLocalizedString("Save", comment: "")
controller.delegate = self
@@ -54,17 +54,17 @@ enum EditAccessMethodItemIdentifier: Hashable {
var text: String? {
switch self {
case .enableMethod:
NSLocalizedString("ENABLE_METHOD", tableName: "APIAccess", value: "Enable method", comment: "")
NSLocalizedString("Enable method", comment: "")
case .methodSettings:
NSLocalizedString("METHOD_SETTINGS", tableName: "APIAccess", value: "Method settings", comment: "")
NSLocalizedString("Method settings", comment: "")
case .testMethod:
NSLocalizedString("TEST_METHOD", tableName: "APIAccess", value: "Test method", comment: "")
NSLocalizedString("Test method", comment: "")
case .cancelTest:
NSLocalizedString("CANCEL_TEST", tableName: "APIAccess", value: "Cancel", comment: "")
NSLocalizedString("Cancel", comment: "")
case .testingStatus:
nil
case .deleteMethod:
NSLocalizedString("DELETE_METHOD", tableName: "APIAccess", value: "Delete method", comment: "")
NSLocalizedString("Delete method", comment: "")
}
}
}
@@ -20,19 +20,9 @@ enum EditAccessMethodSectionIdentifier: Hashable {
var sectionFooter: String? {
switch self {
case .testMethod:
NSLocalizedString(
"TEST_METHOD_FOOTER",
tableName: "APIAccess",
value: "Performs a connection test to a Mullvad API server via this access method.",
comment: ""
)
NSLocalizedString("Performs a connection test to a Mullvad API server via this access method.", comment: "")
case .enableMethod:
NSLocalizedString(
"METHOD_FOOTER",
tableName: "APIAccess",
value: "At least one method needs to be enabled.",
comment: ""
)
NSLocalizedString("At least one method needs to be enabled.", comment: "")
case .methodSettings, .cancelTest, .testingStatus, .deleteMethod:
nil
}
@@ -379,31 +379,16 @@ extension EditAccessMethodViewController: UITableViewDelegate {
private func onDelete() {
let methodName = subject.value.name.isEmpty
? NSLocalizedString(
"METHOD_SETTINGS_SAVE_PROMPT",
tableName: "APIAccess",
value: "method?",
comment: ""
)
? NSLocalizedString("method", comment: "")
: subject.value.name
let presentation = AlertPresentation(
id: "api-access-methods-delete-method-alert",
icon: .alert,
message: NSLocalizedString(
"METHOD_SETTINGS_DELETE_PROMPT",
tableName: "APIAccess",
value: "Delete \(methodName)?",
comment: ""
),
message: NSLocalizedString("Delete \(methodName)?", comment: ""),
buttons: [
AlertAction(
title: NSLocalizedString(
"METHOD_SETTINGS_DELETE_BUTTON",
tableName: "APIAccess",
value: "Delete",
comment: ""
),
title: NSLocalizedString("Delete", comment: ""),
style: .destructive,
accessibilityId: .accessMethodConfirmDeleteButton,
handler: { [weak self] in
@@ -413,12 +398,7 @@ extension EditAccessMethodViewController: UITableViewDelegate {
}
),
AlertAction(
title: NSLocalizedString(
"METHOD_SETTINGS_CANCEL_BUTTON",
tableName: "APIAccess",
value: "Cancel",
comment: ""
),
title: NSLocalizedString("Cancel", comment: ""),
style: .default
),
]
@@ -178,12 +178,7 @@ class MethodSettingsCellConfiguration {
var contentConfiguration = MethodTestingStatusCellContentConfiguration()
contentConfiguration.status = viewStatus
contentConfiguration.detailText = viewStatus == .reachable
? NSLocalizedString(
"METHOD_SETTINGS_SAVING_CHANGES",
tableName: "APIAccess",
value: "Saving changes...",
comment: ""
)
? NSLocalizedString("Saving changes...", comment: "")
: nil
cell.contentConfiguration = contentConfiguration
@@ -71,13 +71,13 @@ enum MethodSettingsItemIdentifier: Hashable {
var text: String? {
switch self {
case .name:
NSLocalizedString("NAME", tableName: "APIAccess", value: "Name", comment: "")
NSLocalizedString("Name", comment: "")
case .protocol:
NSLocalizedString("TYPE", tableName: "APIAccess", value: "Type", comment: "")
NSLocalizedString("Type", comment: "")
case .proxyConfiguration, .validationError:
nil
case .cancelTest:
NSLocalizedString("CANCEL_TEST", tableName: "APIAccess", value: "Cancel", comment: "")
NSLocalizedString("Cancel", comment: "")
case .testingStatus:
nil
}
@@ -21,12 +21,7 @@ enum MethodSettingsSectionIdentifier: Hashable {
case .name, .protocol, .validationError, .testingStatus, .cancelTest:
nil
case .proxyConfiguration:
NSLocalizedString(
"HOST_CONFIG_SECTION_TITLE",
tableName: "APIAccess",
value: "Server details",
comment: ""
)
NSLocalizedString("Server details", comment: "")
}
}
}
@@ -46,7 +46,7 @@ class MethodSettingsViewController: UITableViewController {
lazy var saveBarButton: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(
title: NSLocalizedString("SAVE_NAVIGATION_BUTTON", tableName: "APIAccess", value: "Save", comment: ""),
title: NSLocalizedString("Save", comment: ""),
primaryAction: UIAction { [weak self] _ in
self?.onTest()
}
@@ -314,20 +314,10 @@ class MethodSettingsViewController: UITableViewController {
id: "api-access-methods-testing-status-failed-alert",
accessibilityIdentifier: .accessMethodUnreachableAlert,
icon: .warning,
message: NSLocalizedString(
"METHOD_SETTINGS_SAVE_PROMPT",
tableName: "APIAccess",
value: "API could not be reached, save anyway?",
comment: ""
),
message: NSLocalizedString("API could not be reached, save anyway?", comment: ""),
buttons: [
AlertAction(
title: NSLocalizedString(
"METHOD_SETTINGS_SAVE_BUTTON",
tableName: "APIAccess",
value: "Save anyway",
comment: ""
),
title: NSLocalizedString("Save anyway", comment: ""),
style: .default,
accessibilityId: .accessMethodUnreachableSaveButton,
handler: { [weak self] in
@@ -335,12 +325,7 @@ class MethodSettingsViewController: UITableViewController {
}
),
AlertAction(
title: NSLocalizedString(
"METHOD_SETTINGS_BACK_BUTTON",
tableName: "APIAccess",
value: "Back to editing",
comment: ""
),
title: NSLocalizedString("Back to editing", comment: ""),
style: .default,
accessibilityId: .accessMethodUnreachableBackButton
),
@@ -45,11 +45,11 @@ extension MethodTestingStatusCellContentConfiguration.Status {
var text: String {
switch self {
case .unreachable:
NSLocalizedString("API_UNREACHABLE", tableName: "APIAccess", value: "API unreachable", comment: "")
NSLocalizedString("API unreachable", comment: "")
case .reachable:
NSLocalizedString("API_REACHABLE", tableName: "APIAccess", value: "API reachable", comment: "")
NSLocalizedString("API reachable", comment: "")
case .testing:
NSLocalizedString("API_TESTING_INPROGRESS", tableName: "APIAccess", value: "Testing...", comment: "")
NSLocalizedString("Testing...", comment: "")
}
}
@@ -37,12 +37,7 @@ class ListAccessMethodCoordinator: Coordinator, Presenting, SettingsChildCoordin
), delegate: self)
)
let host = UIHostingController(rootView: view)
host.title = NSLocalizedString(
"NAVIGATION_TITLE",
tableName: "Settings",
value: "API access",
comment: ""
)
host.title = NSLocalizedString("API access", comment: "")
host.view.setAccessibilityIdentifier(.apiAccessView)
navigationController.pushViewController(host, animated: animated)
@@ -87,41 +82,28 @@ class ListAccessMethodCoordinator: Coordinator, Presenting, SettingsChildCoordin
}
private func about() {
let header = NSLocalizedString(
"ABOUT_API_ACCESS_HEADER",
tableName: "APIAccess",
value: "API access",
comment: ""
)
let header = NSLocalizedString("API access", comment: "")
let preamble = NSLocalizedString(
"ABOUT_API_ACCESS_PREAMBLE",
tableName: "APIAccess",
value: "Manage default and setup custom methods to access the Mullvad API.",
"Manage default and setup custom methods to access the Mullvad API.",
comment: ""
)
let body = [
NSLocalizedString(
"ABOUT_API_ACCESS_BODY_1",
tableName: "APIAccess",
value: """
"""
The app needs to communicate with a Mullvad API server to log you in, fetch server lists, \
and other critical operations.
""",
comment: ""
),
NSLocalizedString(
"ABOUT_API_ACCESS_BODY_2",
tableName: "APIAccess",
value: """
"""
On some networks, where various types of censorship are being used, the API servers might \
not be directly reachable.
""",
comment: ""
),
NSLocalizedString(
"ABOUT_API_ACCESS_BODY_3",
tableName: "APIAccess",
value: """
"""
This feature allows you to circumvent that censorship by adding custom ways to access the \
API via proxies and similar methods.
""",
@@ -29,17 +29,10 @@ struct ListAccessMethodView<ViewModel>: View where ViewModel: ListAccessViewMode
var body: some View {
VStack(alignment: .leading, spacing: 0) {
let text = NSLocalizedString(
"ACCESS_METHOD_HEADER_BODY",
tableName: "APIAccess",
value: "Manage default and setup custom methods to access the Mullvad API. ",
comment: ""
)
let about = NSLocalizedString(
"ACCESS_METHOD_HEADER_BODY",
tableName: "APIAccess",
value: "About API access…",
"Manage default and setup custom methods to access the Mullvad API. ",
comment: ""
)
let about = NSLocalizedString("About API access…", comment: "")
MullvadList(viewModel.items, header: {
MullvadInfoHeaderView(
@@ -68,20 +61,10 @@ struct ListAccessMethodView<ViewModel>: View where ViewModel: ListAccessViewMode
nil
}
let state = viewModel.itemInUse?.id == item.id
? NSLocalizedString(
"LIST_ACCESS_METHODS_IN_USE_ITEM",
tableName: "APIAccess",
value: "In use",
comment: ""
)
? NSLocalizedString("In use", comment: "")
: (
!item.isEnabled
? NSLocalizedString(
"LIST_ACCESS_METHODS_DISABLED",
tableName: "APIAccess",
value: "Disabled",
comment: ""
)
? NSLocalizedString("Disabled", comment: "")
: nil
)
MullvadListNavigationItemView(
@@ -16,12 +16,7 @@ struct AccessMethodValidationError: LocalizedError, Equatable {
var errorDescription: String? {
if fieldErrors.count > 1 {
NSLocalizedString(
"VALIDATION_ERRORS_MULTIPLE",
tableName: "APIAccess",
value: "Multiple validation errors occurred.",
comment: ""
)
NSLocalizedString("Multiple validation errors occurred.", comment: "")
} else {
fieldErrors.first?.localizedDescription
}
@@ -75,34 +70,14 @@ struct AccessMethodFieldValidationError: LocalizedError, Equatable {
var errorDescription: String? {
switch kind {
case .emptyValue:
NSLocalizedString(
"VALIDATION_ERRORS_EMPTY_FIELD",
tableName: "APIAccess",
value: "\(field) cannot be empty.",
comment: ""
)
NSLocalizedString("\(field) cannot be empty.", comment: "")
case .invalidIPAddress:
NSLocalizedString(
"VALIDATION_ERRORS_INVALD ADDRESS",
tableName: "APIAccess",
value: "Please enter a valid IPv4 or IPv6 address.",
comment: ""
)
NSLocalizedString("Please enter a valid IPv4 or IPv6 address.", comment: "")
case .invalidPort:
NSLocalizedString(
"VALIDATION_ERRORS_INVALID_PORT",
tableName: "APIAccess",
value: "Please enter a valid port.",
comment: ""
)
NSLocalizedString("Please enter a valid port.", comment: "")
case .nameTooLong:
String(
format: NSLocalizedString(
"VALIDATION_ERRORS_NAME_TOO_LONG",
tableName: "APIAccess",
value: "Name should be no longer than %i characters.",
comment: ""
),
format: NSLocalizedString("Name should be no longer than %i characters.", comment: ""),
NameInputFormatter.maxLength
)
}
@@ -81,48 +81,24 @@ extension AccessMethodViewModel {
switch id {
case AccessMethodRepository.directId:
InfoHeaderConfig(
body: NSLocalizedString(
"DIRECT_ACCESS_METHOD_HEADER_BODY",
tableName: "APIAccess",
value: "The app communicates with a Mullvad API server directly.",
comment: ""
),
link: NSLocalizedString(
"DIRECT_ACCESS_METHOD_HEADER_LINK",
tableName: "APIAccess",
value: "About Direct method...",
comment: ""
)
body: NSLocalizedString("The app communicates with a Mullvad API server directly.", comment: ""),
link: NSLocalizedString("About Direct method...", comment: "")
)
case AccessMethodRepository.bridgeId:
InfoHeaderConfig(
body: NSLocalizedString(
"BRIDGES_ACCESS_METHOD_HEADER_BODY",
tableName: "APIAccess",
value: "The app communicates with a Mullvad API server via a Mullvad bridge server.",
"The app communicates with a Mullvad API server via a Mullvad bridge server.",
comment: ""
),
link: NSLocalizedString(
"BRIDGES_ACCESS_METHOD_HEADER_LINK",
tableName: "APIAccess",
value: "About Mullvad bridges method...",
comment: ""
)
link: NSLocalizedString("About Mullvad bridges method...", comment: "")
)
case AccessMethodRepository.encryptedDNSId:
InfoHeaderConfig(
body: NSLocalizedString(
"ENCRYPTED_DNS_ACCESS_METHOD_HEADER_BODY",
tableName: "APIAccess",
value: "The app communicates with a Mullvad API server via a proxy address.",
"The app communicates with a Mullvad API server via a proxy address.",
comment: ""
),
link: NSLocalizedString(
"ENCRYPTED_DNS_ACCESS_METHOD_HEADER_LINK",
tableName: "APIAccess",
value: "About Encrypted DNS proxy method...",
comment: ""
)
link: NSLocalizedString("About Encrypted DNS proxy method...", comment: "")
)
default:
nil
@@ -133,87 +109,50 @@ extension AccessMethodViewModel {
switch id {
case AccessMethodRepository.directId:
InfoModalConfig(
header: NSLocalizedString(
"DIRECT_ACCESS_METHOD_MODAL_HEADER",
tableName: "APIAccess",
value: "Direct",
comment: ""
),
preamble: NSLocalizedString(
"DIRECT_ACCESS_METHOD_MODAL_PREAMBLE",
tableName: "APIAccess",
value: "The app communicates with a Mullvad API server directly.",
comment: ""
),
header: NSLocalizedString("Direct", comment: ""),
preamble: NSLocalizedString("The app communicates with a Mullvad API server directly.", comment: ""),
body: [
NSLocalizedString(
"DIRECT_ACCESS_METHOD_MODAL_BODY_PART_1",
tableName: "APIAccess",
value: """
"""
With the "Direct" method, the app communicates with a Mullvad API server \
directly without any intermediate proxies.
""",
comment: ""
),
NSLocalizedString(
"DIRECT_ACCESS_METHOD_MODAL_BODY_PART_2",
tableName: "APIAccess",
value: "This can be useful when you are not affected by censorship.",
comment: ""
),
NSLocalizedString("This can be useful when you are not affected by censorship.", comment: ""),
]
)
case AccessMethodRepository.bridgeId:
InfoModalConfig(
header: NSLocalizedString(
"BRIDGES_ACCESS_METHOD_MODAL_HEADER",
tableName: "APIAccess",
value: "Mullvad bridges",
comment: ""
),
header: NSLocalizedString("Mullvad bridges", comment: ""),
preamble: NSLocalizedString(
"BRIDGES_ACCESS_METHOD_MODAL_PREAMBLE",
tableName: "APIAccess",
value: "The app communicates with a Mullvad API server via a Mullvad bridge server.",
"The app communicates with a Mullvad API server via a Mullvad bridge server.",
comment: ""
),
body: [
NSLocalizedString(
"BRIDGES_ACCESS_METHOD_MODAL_BODY_PART_1",
tableName: "APIAccess",
value: """
"""
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(
"BRIDGES_ACCESS_METHOD_MODAL_BODY_PART_2",
tableName: "APIAccess",
value: "This can be useful if the API is censored but Mullvad’s bridge servers are not.",
"This can be useful if the API is censored but Mullvad’s bridge servers are not.",
comment: ""
),
]
)
case AccessMethodRepository.encryptedDNSId:
InfoModalConfig(
header: NSLocalizedString(
"ENCRYPTED_DNS_ACCESS_METHOD_MODAL_HEADER",
tableName: "APIAccess",
value: "Encrypted DNS proxy",
comment: ""
),
header: NSLocalizedString("Encrypted DNS proxy", comment: ""),
preamble: NSLocalizedString(
"ENCRYPTED_DNS_ACCESS_METHOD_MODAL_PREAMBLE",
tableName: "APIAccess",
value: "The app communicates with a Mullvad API server via a proxy address.",
"The app communicates with a Mullvad API server via a proxy address.",
comment: ""
),
body: [
NSLocalizedString(
"ENCRYPTED_DNS_ACCESS_METHOD_MODAL_BODY_PART_1",
tableName: "APIAccess",
value: """
"""
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 \
@@ -222,9 +161,7 @@ extension AccessMethodViewModel {
comment: ""
),
NSLocalizedString(
"ENCRYPTED_DNS_ACCESS_METHOD_MODAL_BODY_PART_2",
tableName: "APIAccess",
value: """
"""
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.
@@ -27,12 +27,7 @@ struct AccessMethodProtocolPicker {
let controller = ListItemPickerViewController(dataSource: dataSource, selectedItemID: currentValue)
controller.view.setAccessibilityIdentifier(.accessMethodProtocolPickerView)
controller.navigationItem.title = NSLocalizedString(
"SELECT_PROTOCOL_NAV_TITLE",
tableName: "APIAccess",
value: "Type",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("Type", comment: "")
controller.onSelect = { selectedItem in
navigationController.popViewController(animated: true)
@@ -26,12 +26,7 @@ struct ShadowsocksCipherPicker {
let dataSource = ShadowsocksCipherPickerDataSource()
let controller = ListItemPickerViewController(dataSource: dataSource, selectedItemID: currentValue)
controller.navigationItem.title = NSLocalizedString(
"SELECT_SHADOWSOCKS_CIPHER_NAV_TITLE",
tableName: "APIAccess",
value: "Cipher",
comment: ""
)
controller.navigationItem.title = NSLocalizedString("Cipher", comment: "")
controller.onSelect = { selectedItem in
navigationController.popViewController(animated: true)
@@ -15,14 +15,9 @@ struct DAITAMultihopNotice: View {
.resizable()
.frame(width: 18, height: 18)
.foregroundStyle(Color(.primaryTextColor).opacity(0.6))
Text(NSLocalizedString(
"SETTINGS_DAITA_MULTIHOP_ENABLED",
tableName: "Settings",
value: "Multihop is being used to enable DAITA for your selected location.",
comment: ""
))
.font(.mullvadTinySemiBold)
.foregroundColor(Color(.primaryTextColor).opacity(0.6))
Text(NSLocalizedString("Multihop is being used to enable DAITA for your selected location.", comment: ""))
.font(.mullvadTinySemiBold)
.foregroundColor(Color(.primaryTextColor).opacity(0.6))
}
}
}
@@ -51,12 +51,7 @@ class DAITASettingsCoordinator: Coordinator, SettingsChildCoordinator, Presentab
}
let host = UIHostingController(rootView: view)
host.title = NSLocalizedString(
"NAVIGATION_TITLE_DAITA",
tableName: "Settings",
value: "DAITA",
comment: ""
)
host.title = NSLocalizedString("DAITA", comment: "")
host.view.setAccessibilityIdentifier(.daitaView)
customiseNavigation(on: host)
@@ -88,31 +83,16 @@ class DAITASettingsCoordinator: Coordinator, SettingsChildCoordinator, Presentab
id: "settings-daita-prompt",
accessibilityIdentifier: .daitaPromptAlert,
icon: .warning,
message: NSLocalizedString(
"SETTINGS_DAITA_ENABLE_TEXT",
tableName: "DAITA",
value: item.description,
comment: ""
),
message: item.description,
buttons: [
AlertAction(
title: String(format: NSLocalizedString(
"SETTINGS_DAITA_ENABLE_OK_ACTION",
tableName: "DAITA",
value: "Enable \"%@\"",
comment: ""
), item.title),
title: String(format: NSLocalizedString("Enable \"%@\"", comment: ""), item.title),
style: .default,
accessibilityId: .daitaConfirmAlertEnableButton,
handler: { onSave() }
),
AlertAction(
title: NSLocalizedString(
"SETTINGS_DAITA_ENABLE_CANCEL_ACTION",
tableName: "DAITA",
value: "Cancel",
comment: ""
),
title: NSLocalizedString("Cancel", comment: ""),
style: .default,
handler: { onDiscard() }
),
@@ -21,9 +21,9 @@ enum DAITASettingsPromptItem: CustomStringConvertible {
case let .daitaSettingIncompatibleWithSinglehop(setting), let .daitaSettingIncompatibleWithMultihop(setting):
switch setting {
case .daita:
"DAITA"
NSLocalizedString("DAITA", comment: "")
case .directOnly:
"Direct only"
NSLocalizedString("Direct only", comment: "")
}
}
}
@@ -31,15 +31,19 @@ enum DAITASettingsPromptItem: CustomStringConvertible {
var description: String {
switch self {
case .daitaSettingIncompatibleWithSinglehop:
"""
NSLocalizedString("""
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.
"""
""", comment: "")
case .daitaSettingIncompatibleWithMultihop:
"""
DAITA isn't available on the current entry server. After enabling, please go to the \
"Select location" view and select an entry location that supports DAITA.
"""
NSLocalizedString(
"""
DAITA isn't available on the current entry server. After enabling, please go to the \
"Select location" view and select an entry location that supports DAITA.
""",
comment: ""
)
}
}
}
@@ -31,33 +31,21 @@ struct SettingsDAITAView<ViewModel>: View where ViewModel: TunnelSettingsObserva
GroupedRowView {
SwitchRowView(
isOn: daitaIsEnabled,
text: NSLocalizedString(
"SETTINGS_SWITCH_DAITA_ENABLE",
tableName: "Settings",
value: "Enable",
comment: ""
),
text: NSLocalizedString("Enable", comment: ""),
accessibilityId: .daitaSwitch
)
RowSeparator()
SwitchRowView(
isOn: directOnlyIsEnabled,
disabled: !daitaIsEnabled.wrappedValue,
text: NSLocalizedString(
"SETTINGS_SWITCH_DAITA_DIRECT_ONLY",
tableName: "Settings",
value: "Direct only",
comment: ""
),
text: NSLocalizedString("Direct only", comment: ""),
accessibilityId: .daitaDirectOnlySwitch
)
}
SettingsRowViewFooter(
text: NSLocalizedString(
"SETTINGS_SWITCH_DAITA_ENABLE",
tableName: "Settings",
value: """
"""
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.
@@ -116,9 +104,7 @@ extension SettingsDAITAView {
pages: [
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_DAITA_PAGE_1",
tableName: "Settings",
value: """
"""
**Attention: This increases network traffic and will also negatively affect speed, latency, \
and battery usage. Use with caution on limited plans.**
@@ -134,9 +120,7 @@ extension SettingsDAITAView {
),
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_DAITA_PAGE_2",
tableName: "Settings",
value: """
"""
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.
@@ -68,35 +68,24 @@ extension IPOverrideCoordinator: @preconcurrency IPOverrideViewControllerDelegat
}
func presentAbout() {
let header = NSLocalizedString(
"IP_OVERRIDE_HEADER",
tableName: "IPOverride",
value: "Server IP override",
comment: ""
)
let header = NSLocalizedString("Server IP override", comment: "")
let body = [
NSLocalizedString(
"IP_OVERRIDE_BODY_1",
tableName: "IPOverride",
value: """
"""
On some networks, where various types of censorship are being used, our server IP addresses are \
sometimes blocked.
""",
comment: ""
),
NSLocalizedString(
"IP_OVERRIDE_BODY_2",
tableName: "IPOverride",
value: """
"""
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(
"IP_OVERRIDE_BODY_3",
tableName: "IPOverride",
value: """
"""
If you are having issues connecting to VPN servers, please contact support.
""",
comment: ""
@@ -26,26 +26,11 @@ enum IPOverrideStatus: Equatable, CustomStringConvertible {
var title: String {
switch self {
case .active:
NSLocalizedString(
"IP_OVERRIDE_STATUS_TITLE_ACTIVE",
tableName: "IPOverride",
value: "Overrides active",
comment: ""
)
NSLocalizedString("Overrides active", comment: "")
case .noImports, .importFailed:
NSLocalizedString(
"IP_OVERRIDE_STATUS_TITLE_NO_IMPORTS",
tableName: "IPOverride",
value: "No overrides imported",
comment: ""
)
NSLocalizedString("No overrides imported", comment: "")
case .importSuccessful:
NSLocalizedString(
"IP_OVERRIDE_STATUS_TITLE_IMPORT_SUCCESSFUL",
tableName: "IPOverride",
value: "Import successful",
comment: ""
)
NSLocalizedString("Import successful", comment: "")
}
}
@@ -73,19 +58,15 @@ enum IPOverrideStatus: Equatable, CustomStringConvertible {
case .active, .noImports:
""
case let .importFailed(context):
String(format: NSLocalizedString(
"IP_OVERRIDE_STATUS_DESCRIPTION_INACTIVE",
tableName: "IPOverride",
value: "Import %@ was unsuccessful, please try again.",
comment: ""
), context.description)
String(
format: NSLocalizedString("Import %@ was unsuccessful, please try again.", comment: ""),
context.description
)
case let .importSuccessful(context):
String(format: NSLocalizedString(
"IP_OVERRIDE_STATUS_DESCRIPTION_INACTIVE",
tableName: "IPOverride",
value: "Import %@ was successful, overrides are now active.",
comment: ""
), context.description)
String(
format: NSLocalizedString("Import %@ was successful, overrides are now active.", comment: ""),
context.description
)
}
}
}
@@ -14,12 +14,7 @@ class IPOverrideTextViewController: UIViewController {
private lazy var importButton: UIBarButtonItem = {
return UIBarButtonItem(
title: NSLocalizedString(
"IMPORT_TEXT_IMPORT_BUTTON",
tableName: "IPOverride",
value: "Import",
comment: ""
),
title: NSLocalizedString("Import", comment: ""),
primaryAction: UIAction(handler: { [weak self] _ in
self?.interactor.import(text: self?.textView.text ?? "")
self?.dismiss(animated: true)
@@ -42,12 +37,7 @@ class IPOverrideTextViewController: UIViewController {
view.backgroundColor = .secondaryColor
navigationItem.title = NSLocalizedString(
"IMPORT_TEXT_NAVIGATION_TITLE",
tableName: "IPOverride",
value: "Import via text",
comment: ""
)
navigationItem.title = NSLocalizedString("Import via text", comment: "")
navigationItem.leftBarButtonItem = UIBarButtonItem(
systemItem: .cancel,
@@ -27,12 +27,7 @@ class IPOverrideViewController: UIViewController {
private lazy var clearButton: AppButton = {
let button = AppButton(style: .danger)
button.addTarget(self, action: #selector(didTapClearButton), for: .touchUpInside)
button.setTitle(NSLocalizedString(
"IP_OVERRIDE_CLEAR_BUTTON",
tableName: "IPOverride",
value: "Clear all overrides",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Clear all overrides", comment: ""), for: .normal)
return button
}()
@@ -65,12 +60,7 @@ class IPOverrideViewController: UIViewController {
}
private func configureNavigation() {
title = NSLocalizedString(
"IP_OVERRIDE_HEADER",
tableName: "IPOverride",
value: "Server IP override",
comment: ""
)
title = NSLocalizedString("Server IP override", comment: "")
}
private func addScrollView() {
@@ -112,17 +102,10 @@ class IPOverrideViewController: UIViewController {
private func addHeaderView() {
let body = NSLocalizedString(
"IP_OVERRIDE_HEADER_BODY",
tableName: "IPOverride",
value: "Import files or text with the new IP addresses for the servers in the Select location view.",
comment: ""
)
let link = NSLocalizedString(
"IP_OVERRIDE_HEADER_LINK",
tableName: "IPOverride",
value: "About Server IP override...",
"Import files or text with the new IP addresses for the servers in the Select location view.",
comment: ""
)
let link = NSLocalizedString("About Server IP override...", comment: "")
let headerView = InfoHeaderView(config: InfoHeaderConfig(body: body, link: link))
@@ -136,22 +119,12 @@ class IPOverrideViewController: UIViewController {
private func addImportButtons() {
let importTextButton = AppButton(style: .default)
importTextButton.addTarget(self, action: #selector(didTapImportTextButton), for: .touchUpInside)
importTextButton.setTitle(NSLocalizedString(
"IP_OVERRIDE_IMPORT_TEXT_BUTTON",
tableName: "IPOverride",
value: "Import via text",
comment: ""
), for: .normal)
importTextButton.setTitle(NSLocalizedString("Import via text", comment: ""), for: .normal)
importTextButton.titleLabel?.textAlignment = .center
let importFileButton = AppButton(style: .default)
importFileButton.addTarget(self, action: #selector(didTapImportFileButton), for: .touchUpInside)
importFileButton.setTitle(NSLocalizedString(
"IP_OVERRIDE_IMPORT_FILE_BUTTON",
tableName: "IPOverride",
value: "Import file",
comment: ""
), for: .normal)
importFileButton.setTitle(NSLocalizedString("Import file", comment: ""), for: .normal)
importFileButton.titleLabel?.textAlignment = .center
let stackView = UIStackView(arrangedSubviews: [importTextButton, importFileButton])
stackView.distribution = .fillEqually
@@ -168,16 +141,9 @@ class IPOverrideViewController: UIViewController {
let presentation = AlertPresentation(
id: "ip-override-clear-alert",
icon: .alert,
title: NSLocalizedString(
"IP_OVERRIDE_CLEAR_DIALOG_TITLE",
tableName: "IPOverride",
value: "Clear all overrides?",
comment: ""
),
title: NSLocalizedString("Clear all overrides?", comment: ""),
message: NSLocalizedString(
"IP_OVERRIDE_CLEAR_DIALOG_MESSAGE",
tableName: "IPOverride",
value: """
"""
Clearing the imported overrides changes the server IPs, in the Select location view, \
back to default.
""",
@@ -185,24 +151,14 @@ class IPOverrideViewController: UIViewController {
),
buttons: [
AlertAction(
title: NSLocalizedString(
"IP_OVERRIDE_CLEAR_DIALOG_CLEAR_BUTTON",
tableName: "IPOverride",
value: "Clear",
comment: ""
),
title: NSLocalizedString("Clear", comment: ""),
style: .destructive,
handler: { [weak self] in
self?.interactor.deleteAllOverrides()
}
),
AlertAction(
title: NSLocalizedString(
"IP_OVERRIDE_CLEAR_DIALOG_CANCEL_BUTTON",
tableName: "IPOverride",
value: "Cancel",
comment: ""
),
title: NSLocalizedString("Cancel", comment: ""),
style: .default
),
]
@@ -39,12 +39,7 @@ class MultihopSettingsCoordinator: Coordinator, SettingsChildCoordinator, Presen
let view = SettingsMultihopView(tunnelViewModel: self.viewModel)
let host = UIHostingController(rootView: view)
host.title = NSLocalizedString(
"NAVIGATION_TITLE_MULTIHOP",
tableName: "Settings",
value: "Multihop",
comment: ""
)
host.title = NSLocalizedString("Multihop", comment: "")
host.view.setAccessibilityIdentifier(.multihopView)
customiseNavigation(on: host)
@@ -19,12 +19,7 @@ struct SettingsMultihopView<ViewModel>: View where ViewModel: TunnelSettingsObse
SwitchRowView(
isOn: $tunnelViewModel.value.isEnabled,
text: NSLocalizedString(
"SETTINGS_SWITCH_MULTIHOP",
tableName: "Settings",
value: "Enable",
comment: ""
),
text: NSLocalizedString("Enable", comment: ""),
accessibilityId: .multihopSwitch
)
.padding(.leading, UIMetrics.contentInsets.left)
@@ -44,9 +39,7 @@ extension SettingsMultihopView {
pages: [
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_MULTIHOP",
tableName: "Settings",
value: """
"""
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.
""",
@@ -114,12 +114,7 @@ struct SettingsViewControllerFactory {
let view = SettingsMultihopView(tunnelViewModel: viewModel)
let host = UIHostingController(rootView: view)
host.title = NSLocalizedString(
"NAVIGATION_TITLE_MULTIHOP",
tableName: "Settings",
value: "Multihop",
comment: ""
)
host.title = NSLocalizedString("Multihop", comment: "")
host.view.setAccessibilityIdentifier(.multihopView)
return .viewController(host)
@@ -134,9 +134,7 @@ struct SettingsInfoView: View {
pages: [
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_DAITA_PAGE_1",
tableName: "Settings",
value: """
"""
**Attention: This increases network traffic and will also negatively affect speed, latency, \
and battery usage. Use with caution on limited plans.**
@@ -152,9 +150,7 @@ struct SettingsInfoView: View {
),
SettingsInfoViewModelPage(
body: NSLocalizedString(
"SETTINGS_INFO_DAITA_PAGE_2",
tableName: "Settings",
value: """
"""
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.
@@ -93,31 +93,19 @@ class TunnelCoordinator: Coordinator, Presenting {
id: "main-cancel-tunnel-alert",
icon: .alert,
message: NSLocalizedString(
"CANCEL_TUNNEL_ALERT_MESSAGE",
tableName: "Main",
value: "If you disconnect now, you won’t be able to secure your connection until the device is online.",
"If you disconnect now, you won’t be able to secure your connection until the device is online.",
comment: ""
),
buttons: [
AlertAction(
title: NSLocalizedString(
"CANCEL_TUNNEL_ALERT_DISCONNECT_ACTION",
tableName: "Main",
value: "Disconnect",
comment: ""
),
title: NSLocalizedString("Disconnect", comment: ""),
style: .destructive,
handler: { [weak self] in
self?.tunnelManager.stopTunnel()
}
),
AlertAction(
title: NSLocalizedString(
"CANCEL_TUNNEL_ALERT_CANCEL_ACTION",
tableName: "Main",
value: "Cancel",
comment: ""
),
title: NSLocalizedString("Cancel", comment: ""),
style: .default
),
]
@@ -83,9 +83,6 @@ final class WelcomeCoordinator: Coordinator, Poppable, Presenting {
extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
func didRequestToShowFailToFetchProducts(controller: WelcomeViewController) {
let message = NSLocalizedString(
"WELCOME_FAILED_TO_FETCH_PRODUCTS_DIALOG",
tableName: "Welcome",
value:
"""
Failed to connect to App store, please try again later.
""",
@@ -98,12 +95,7 @@ extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
message: message,
buttons: [
AlertAction(
title: NSLocalizedString(
"WELCOME_FAILED_TO_FETCH_PRODUCTS_OK_ACTION",
tableName: "Welcome",
value: "Got it!",
comment: ""
),
title: NSLocalizedString("Got it!", comment: ""),
style: .default
),
]
@@ -115,9 +107,6 @@ extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
func didRequestToShowInfo(controller: WelcomeViewController) {
let message = NSLocalizedString(
"WELCOME_DEVICE_CONCEPT_TEXT_DIALOG",
tableName: "Welcome",
value:
"""
This is the name assigned to the device. Each device logged in on a \
Mullvad account gets a unique name that helps \
@@ -135,12 +124,7 @@ extension WelcomeCoordinator: @preconcurrency WelcomeViewControllerDelegate {
message: message,
buttons: [
AlertAction(
title: NSLocalizedString(
"WELCOME_DEVICE_NAME_DIALOG_OK_ACTION",
tableName: "Welcome",
value: "Got it!",
comment: ""
),
title: NSLocalizedString("Got it!", comment: ""),
style: .default
),
]
@@ -17,19 +17,9 @@ extension REST.CreateApplePaymentResponse {
func alertTitle(context: Context) -> String {
switch context {
case .purchase:
return NSLocalizedString(
"TIME_ADDED_ALERT_SUCCESS_TITLE",
tableName: "REST",
value: "Thanks for your purchase",
comment: ""
)
return NSLocalizedString("Thanks for your purchase", comment: "")
case .restoration:
return NSLocalizedString(
"RESTORE_PURCHASES_ALERT_TITLE",
tableName: "REST",
value: "Restore purchases",
comment: ""
)
return NSLocalizedString("Restore purchases", comment: "")
}
}
@@ -37,31 +27,19 @@ extension REST.CreateApplePaymentResponse {
switch context {
case .purchase:
return String(
format: NSLocalizedString(
"TIME_ADDED_ALERT_SUCCESS_MESSAGE",
tableName: "REST",
value: "%@ have been added to your account",
comment: ""
),
format: NSLocalizedString("%@ have been added to your account", comment: ""),
formattedTimeAdded ?? ""
)
case .restoration:
switch self {
case .noTimeAdded:
return NSLocalizedString(
"RESTORE_PURCHASES_ALERT_NO_TIME_ADDED_MESSAGE",
tableName: "REST",
value: "Your previous purchases have already been added to this account.",
"Your previous purchases have already been added to this account.",
comment: ""
)
case .timeAdded:
return String(
format: NSLocalizedString(
"RESTORE_PURCHASES_ALERT_TIME_ADDED_MESSAGE",
tableName: "REST",
value: "%@ have been added to your account",
comment: ""
),
format: NSLocalizedString("%@ have been added to your account", comment: ""),
formattedTimeAdded ?? ""
)
}
@@ -73,19 +51,9 @@ extension REST.CreateApplePaymentResponse.Context {
var errorTitle: String {
switch self {
case .purchase:
return NSLocalizedString(
"CANNOT_COMPLETE_PURCHASE_ALERT_TITLE",
tableName: "Payment",
value: "Cannot complete the purchase",
comment: ""
)
return NSLocalizedString("Cannot complete the purchase", comment: "")
case .restoration:
return NSLocalizedString(
"RESTORE_PURCHASES_FAILURE_ALERT_TITLE",
tableName: "Payment",
value: "Cannot restore purchases",
comment: ""
)
return NSLocalizedString("Cannot restore purchases", comment: "")
}
}
}
@@ -15,78 +15,38 @@ extension REST.Error: MullvadTypes.DisplayError {
switch self {
case let .network(urlError):
return String(
format: NSLocalizedString(
"NETWORK_ERROR",
tableName: "REST",
value: "Network error: %@",
comment: ""
),
format: NSLocalizedString("Network error: %@", comment: ""),
urlError.localizedDescription
)
case let .unhandledResponse(statusCode, serverResponse):
guard let serverResponse else {
return String(format: NSLocalizedString(
"UNEXPECTED_RESPONSE",
tableName: "REST",
value: "Unexpected server response: %d",
comment: ""
), statusCode)
return String(format: NSLocalizedString("Unexpected server response: %d", comment: ""), statusCode)
}
switch serverResponse.code {
case .invalidAccount:
return NSLocalizedString(
"INVALID_ACCOUNT_ERROR",
tableName: "REST",
value: "Invalid account",
comment: ""
)
return NSLocalizedString("Invalid account", comment: "")
case .maxDevicesReached:
return NSLocalizedString(
"MAX_DEVICES_REACHED_ERROR",
tableName: "REST",
value: "Too many devices registered with account",
comment: ""
)
return NSLocalizedString("Too many devices registered with account", comment: "")
case .serviceUnavailable:
return NSLocalizedString(
"SERVICE_UNAVAILABLE",
tableName: "REST",
value: "We are having some issues, please try again later",
comment: ""
)
return NSLocalizedString("We are having some issues, please try again later", comment: "")
case .tooManyRequests:
return NSLocalizedString(
"TOO_MANY_REQUESTS",
tableName: "REST",
value: "We are having some issues, please try again later",
comment: ""
)
return NSLocalizedString("We are having some issues, please try again later", comment: "")
default:
return String(
format: NSLocalizedString(
"SERVER_ERROR",
tableName: "REST",
value: "Unexpected server response: %1$@ (HTTP status: %2$d)",
comment: ""
),
format: NSLocalizedString("Unexpected server response: %1$@ (HTTP status: %2$d)", comment: ""),
serverResponse.code.rawValue,
statusCode
)
}
default:
return NSLocalizedString(
"INTERNAL_ERROR",
tableName: "REST",
value: "Internal error.",
comment: ""
)
return NSLocalizedString("Internal error.", comment: "")
}
}
}
@@ -12,40 +12,15 @@ extension SKError: Foundation.LocalizedError {
public var errorDescription: String? {
switch code {
case .unknown:
return NSLocalizedString(
"UNKNOWN_ERROR",
tableName: "StoreKitErrors",
value: "Unknown error.",
comment: ""
)
return NSLocalizedString("Unknown error.", comment: "")
case .clientInvalid:
return NSLocalizedString(
"CLIENT_INVALID",
tableName: "StoreKitErrors",
value: "Client is not allowed to issue the request.",
comment: ""
)
return NSLocalizedString("Client is not allowed to issue the request.", comment: "")
case .paymentCancelled:
return NSLocalizedString(
"PAYMENT_CANCELLED",
tableName: "StoreKitErrors",
value: "The payment request was cancelled.",
comment: ""
)
return NSLocalizedString("The payment request was cancelled.", comment: "")
case .paymentInvalid:
return NSLocalizedString(
"PAYMENT_INVALID",
tableName: "StoreKitErrors",
value: "Invalid purchase identifier.",
comment: ""
)
return NSLocalizedString("Invalid purchase identifier.", comment: "")
case .paymentNotAllowed:
return NSLocalizedString(
"PAYMENT_NOT_ALLOWED",
tableName: "StoreKitErrors",
value: "This device is not allowed to make the payment.",
comment: ""
)
return NSLocalizedString("This device is not allowed to make the payment.", comment: "")
default:
return localizedDescription
}
@@ -14,64 +14,32 @@ extension StorePaymentManagerError: DisplayError {
var displayErrorDescription: String? {
switch self {
case .noAccountSet:
return NSLocalizedString(
"INTERNAL_ERROR",
tableName: "StorePaymentManager",
value: "Internal error.",
comment: ""
)
return NSLocalizedString("Internal error.", comment: "")
case let .validateAccount(error):
let reason = (error as? DisplayError)?.displayErrorDescription ?? ""
return String(
format: NSLocalizedString(
"VALIDATE_ACCOUNT_ERROR",
tableName: "StorePaymentManager",
value: "Failed to validate account number: %@",
comment: ""
), reason
format: NSLocalizedString("Failed to validate account number: %@", comment: ""), reason
)
case let .readReceipt(readReceiptError):
if readReceiptError is StoreReceiptNotFound {
return NSLocalizedString(
"RECEIPT_NOT_FOUND_ERROR",
tableName: "StorePaymentManager",
value: "AppStore receipt is not found on disk.",
comment: ""
)
return NSLocalizedString("AppStore receipt is not found on disk.", comment: "")
} else if let storeError = readReceiptError as? SKError {
return String(
format: NSLocalizedString(
"REFRESH_RECEIPT_ERROR",
tableName: "StorePaymentManager",
value: "Cannot refresh the AppStore receipt: %@",
comment: ""
),
format: NSLocalizedString("Cannot refresh the AppStore receipt: %@", comment: ""),
storeError.localizedDescription
)
} else {
return NSLocalizedString(
"READ_RECEIPT_ERROR",
tableName: "StorePaymentManager",
value: "Cannot read the AppStore receipt from disk",
comment: ""
)
return NSLocalizedString("Cannot read the AppStore receipt from disk", comment: "")
}
case let .sendReceipt(error):
let reason = (error as? DisplayError)?.displayErrorDescription ?? ""
let errorFormat = NSLocalizedString(
"SEND_RECEIPT_ERROR",
tableName: "StorePaymentManager",
value: "Failed to send the receipt to server: %@",
comment: ""
)
let errorFormat = NSLocalizedString("Failed to send the receipt to server: %@", comment: "")
let recoverySuggestion = NSLocalizedString(
"SEND_RECEIPT_RECOVERY_SUGGESTION",
tableName: "StorePaymentManager",
value: "Please retry by using the \"Restore purchases\" button.",
"Please retry by using the \"Restore purchases\" button.",
comment: ""
)
var errorString = String(format: errorFormat, reason)
@@ -15,19 +15,9 @@ extension UIBarButtonItem {
fileprivate var localizedTitle: String {
switch self {
case .previous:
return NSLocalizedString(
"PREVIOUS_BUTTON_TITLE",
tableName: "KeyboardNavigation",
value: "Previous",
comment: "Previous button"
)
return NSLocalizedString("Previous", comment: "")
case .next:
return NSLocalizedString(
"NEXT_BUTTON_TITLE",
tableName: "KeyboardNavigation",
value: "Next",
comment: "Next button"
)
return NSLocalizedString("Next", comment: "")
}
}
@@ -55,9 +55,8 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
style: .warning,
title: durationText,
body: NSAttributedString(string: NSLocalizedString(
"ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_BODY",
value: "You can add more time via the account view or website to continue using the VPN.",
comment: "Title for in-app notification, displayed within the last X days until account expiry."
"You can add more time via the account view or website to continue using the VPN.",
comment: ""
))
)
}
@@ -116,11 +115,6 @@ extension AccountExpiryInAppNotificationProvider {
)
else { return nil }
return String(format: NSLocalizedString(
"ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_TITLE",
tableName: "AccountExpiry",
value: "%@ left on this account",
comment: "Message for in-app notification, displayed within the last X days until account expiry."
), duration).uppercased()
return String(format: NSLocalizedString("%@ left on this account", comment: ""), duration).uppercased()
}
}
@@ -130,18 +130,8 @@ final class AccountExpirySystemNotificationProvider: NotificationProvider, Syste
extension AccountExpirySystemNotificationProvider {
private var formattedRemainingDurationTitle: String {
accountHasExpired
? NSLocalizedString(
"ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_TITLE",
tableName: "AccountExpiry",
value: "Account credit has expired",
comment: "Title for system account expiry notification, fired on account expiry."
)
: NSLocalizedString(
"ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_TITLE",
tableName: "AccountExpiry",
value: "Account credit expires soon",
comment: "Title for system account expiry notification, fired X days prior to account expiry."
)
? NSLocalizedString("Account credit has expired", comment: "")
: NSLocalizedString("Account credit expires soon", comment: "")
}
private var formattedRemainingDurationBody: String? {
@@ -159,22 +149,18 @@ extension AccountExpirySystemNotificationProvider {
private var expiredText: String {
NSLocalizedString(
"ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_BODY",
tableName: "AccountExpiry",
value: """
"""
Blocking internet: Your time on this account has expired. To continue using the internet, \
please add more time or disconnect the VPN.
""",
comment: "Message for in-app notification, displayed on account expiry while connected to VPN."
comment: ""
)
}
private var singleDayText: String {
NSLocalizedString(
"ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_BODY",
tableName: "AccountExpiry",
value: "You have one day left on this account. Please add more time to continue using the VPN.",
comment: "Message for in-app notification, displayed within the last 1 day until account expiry."
"You have one day left on this account. Please add more time to continue using the VPN.",
comment: ""
)
}
@@ -189,11 +175,9 @@ extension AccountExpirySystemNotificationProvider {
)
else { return nil }
return String(format: NSLocalizedString(
"ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_BODY",
tableName: "AccountExpiry",
value: "You have %@ left on this account.",
comment: "Message for in-app notification, displayed within the last X days until account expiry."
), duration.lowercased())
return String(
format: NSLocalizedString("You have %@ left on this account.", comment: ""),
duration.lowercased()
)
}
}
@@ -42,11 +42,7 @@ class LatestChangesNotificationProvider: NotificationProvider, InAppNotification
return InAppNotificationDescriptor(
identifier: identifier,
style: .success,
title: NSLocalizedString(
"LATEST_CHANGES_IN_APP_NOTIFICATION_TITLE",
value: "NEW VERSION INSTALLED",
comment: ""
),
title: NSLocalizedString("NEW VERSION INSTALLED", comment: ""),
body: createNotificationBody(),
button: createCloseButtonAction(),
tapAction: createTapAction()
@@ -55,11 +51,7 @@ class LatestChangesNotificationProvider: NotificationProvider, InAppNotification
private func createNotificationBody() -> NSAttributedString {
NSAttributedString(
markdownString: NSLocalizedString(
"LATEST_CHANGES_IN_APP_NOTIFICATION_BODY",
value: "**Tap here** to see what’s new.",
comment: ""
),
markdownString: NSLocalizedString("**Tap here** to see what’s new.", comment: ""),
options: MarkdownStylingOptions(
font: .preferredFont(forTextStyle: .body)
)
@@ -26,8 +26,7 @@ final class NewDeviceNotificationProvider: NotificationProvider,
private var attributedBody: NSAttributedString {
let formattedString = NSLocalizedString(
"ACCOUNT_CREATION_INAPP_NOTIFICATION_BODY",
value: "Welcome, this device is now called **%@**. For more details see the info button in Account.",
"Welcome, this device is now called **%@**. For more details see the info button in Account.",
comment: ""
)
let deviceName = storedDeviceData?.capitalizedName ?? ""
@@ -50,11 +49,7 @@ final class NewDeviceNotificationProvider: NotificationProvider,
return InAppNotificationDescriptor(
identifier: identifier,
style: .success,
title: NSLocalizedString(
"ACCOUNT_CREATION_INAPP_NOTIFICATION_TITLE",
value: "NEW DEVICE CREATED",
comment: ""
),
title: NSLocalizedString("NEW DEVICE CREATED", comment: ""),
body: attributedBody,
button: InAppNotificationAction(
image: UIImage.Buttons.closeSmall,
@@ -155,11 +155,7 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
return InAppNotificationDescriptor(
identifier: identifier,
style: .error,
title: NSLocalizedString(
"TUNNEL_BLOCKED_INAPP_NOTIFICATION_TITLE",
value: "BLOCKING INTERNET",
comment: ""
),
title: NSLocalizedString("BLOCKING INTERNET", comment: ""),
body: createNotificationBody(localizedReasonForBlockedStateError(packetTunnelError)),
tapAction: tapAction
)
@@ -167,11 +163,7 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
private func createNotificationBody(_ string: String) -> NSAttributedString {
NSAttributedString(
markdownString: NSLocalizedString(
"LATEST_CHANGES_IN_APP_NOTIFICATION_BODY",
value: string,
comment: ""
),
markdownString: string,
options: MarkdownStylingOptions(font: UIFont.preferredFont(forTextStyle: .body)),
applyEffect: { markdownType, _ in
guard case .bold = markdownType else { return [:] }
@@ -185,20 +177,12 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
if let startError = error as? StartTunnelError {
body = String(
format: NSLocalizedString(
"START_TUNNEL_ERROR_INAPP_NOTIFICATION_BODY",
value: "Failed to start the tunnel: %@.",
comment: ""
),
format: NSLocalizedString("Failed to start the tunnel: %@.", comment: ""),
startError.underlyingError?.localizedDescription ?? ""
)
} else if let stopError = error as? StopTunnelError {
body = String(
format: NSLocalizedString(
"STOP_TUNNEL_ERROR_INAPP_NOTIFICATION_BODY",
value: "Failed to stop the tunnel: %@.",
comment: ""
),
format: NSLocalizedString("Failed to stop the tunnel: %@.", comment: ""),
stopError.underlyingError?.localizedDescription ?? ""
)
} else {
@@ -208,11 +192,7 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
return InAppNotificationDescriptor(
identifier: identifier,
style: .error,
title: NSLocalizedString(
"TUNNEL_MANAGER_ERROR_INAPP_NOTIFICATION_TITLE",
value: "TUNNEL ERROR",
comment: ""
),
title: NSLocalizedString("TUNNEL ERROR", comment: ""),
body: .init(string: body)
)
}
@@ -221,15 +201,10 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
InAppNotificationDescriptor(
identifier: identifier,
style: .warning,
title: NSLocalizedString(
"TUNNEL_NO_CONNECTIVITY_INAPP_NOTIFICATION_TITLE",
value: "NETWORK ISSUES",
comment: ""
),
title: NSLocalizedString("NETWORK ISSUES", comment: ""),
body: .init(
string: NSLocalizedString(
"TUNNEL_NO_CONNECTIVITY_INAPP_NOTIFICATION_BODY",
value: """
"""
Your device is offline. The tunnel will automatically connect once \
your device is back online.
""",
@@ -243,15 +218,10 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
InAppNotificationDescriptor(
identifier: identifier,
style: .warning,
title: NSLocalizedString(
"TUNNEL_NO_NETWORK_INAPP_NOTIFICATION_TITLE",
value: "NETWORK ISSUES",
comment: ""
),
title: NSLocalizedString("NETWORK ISSUES", comment: ""),
body: .init(
string: NSLocalizedString(
"TUNNEL_NO_NETWORK_INAPP_NOTIFICATION_BODY",
value: """
"""
Your device is offline. Try connecting again when the device \
has access to Internet.
""",
@@ -262,34 +232,33 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
}
private func localizedReasonForBlockedStateError(_ error: BlockedStateReason) -> String {
let errorString: String
let errorStringKey: String
switch error {
case .outdatedSchema:
errorString = "Unable to start tunnel connection after update. Please disconnect and reconnect."
errorStringKey = "ERROR_OUTDATED_SCHEMA"
case .noRelaysSatisfyingFilterConstraints:
errorString = "No servers match your location filter. Try changing filter settings."
errorStringKey = "ERROR_NO_RELAYS_SATISFYING_FILTER_CONSTRAINTS"
case .multihopEntryEqualsExit:
errorString = "The entry and exit servers cannot be the same. Try changing one to a new server or location."
errorStringKey = "ERROR_MULTIHOP_ENTRY_EQUALS_EXIT"
case .noRelaysSatisfyingDaitaConstraints:
errorString = "No DAITA compatible servers match your location settings. Try changing location."
errorStringKey = "ERROR_NO_RELAYS_SATISFYING_DAITA_CONSTRAINTS"
case .noRelaysSatisfyingConstraints:
errorString = "No servers match your settings, try changing server or other settings."
errorStringKey = "ERROR_NO_RELAYS_SATISFYING_CONSTRAINTS"
case .noRelaysSatisfyingPortConstraints:
errorString = "The selected WireGuard port is not supported, please change it under **VPN settings**."
errorStringKey = "ERROR_NO_RELAYS_SATISFYING_PORT_CONSTRAINTS"
case .invalidAccount:
errorString = "You are logged in with an invalid account number. Please log out and try another one."
errorStringKey = "ERROR_INVALID_ACCOUNT"
case .deviceLoggedOut:
errorString = "Unable to authenticate account. Please log out and log back in."
errorStringKey = "ERROR_DEVICE_LOGGED_OUT"
default:
errorString = "Unable to start tunnel connection. Please send a problem report."
errorStringKey = "ERROR_DEFAULT"
}
return NSLocalizedString(
"BLOCKED_STATE_ERROR_TITLE",
tableName: "Main",
value: errorString,
let errorString = NSLocalizedString(
errorStringKey,
comment: ""
)
return errorString
}
}
+4 -13
View File
@@ -208,16 +208,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, @preconcurrency Setting
let presentation = AlertPresentation(
id: "settings-migration-error-alert",
title: NSLocalizedString(
"ALERT_TITLE",
tableName: "SettingsMigrationUI",
value: "Settings migration error",
comment: ""
),
title: NSLocalizedString("Settings migration error", comment: ""),
message: Self.migrationErrorReason(error),
buttons: [
AlertAction(
title: NSLocalizedString("Got it!", tableName: "SettingsMigrationUI", comment: ""),
title: NSLocalizedString("Got it!", comment: ""),
style: .default,
handler: {
completionHandler()
@@ -233,9 +228,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, @preconcurrency Setting
private static func migrationErrorReason(_ error: Error) -> String {
if error is UnsupportedSettingsVersionError {
return NSLocalizedString(
"NEWER_STORED_SETTINGS_ERROR",
tableName: "SettingsMigrationUI",
value: """
"""
The version of settings stored on device is unrecognized.\
Settings will be reset to defaults and the device will be logged out.
""",
@@ -243,9 +236,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, @preconcurrency Setting
)
} else {
return NSLocalizedString(
"INTERNAL_ERROR",
tableName: "SettingsMigrationUI",
value: """
"""
Internal error occurred. Settings will be reset to defaults and device logged out.
""",
comment: ""
@@ -17,19 +17,9 @@ enum StoreSubscription: String, CaseIterable {
var localizedTitle: String {
switch self {
case .thirtyDays:
return NSLocalizedString(
"STORE_SUBSCRIPTION_TITLE_ADD_30_DAYS",
tableName: "StoreSubscriptions",
value: "Add 30 days",
comment: ""
)
return NSLocalizedString("Add 30 days", comment: "")
case .ninetyDays:
return NSLocalizedString(
"STORE_SUBSCRIPTION_TITLE_ADD_90_DAYS",
tableName: "StoreSubscriptions",
value: "Add 90 days",
comment: ""
)
return NSLocalizedString("Add 90 days", comment: "")
}
}
}
@@ -11,23 +11,13 @@ import MullvadTypes
struct UnsetTunnelError: LocalizedError {
var errorDescription: String? {
NSLocalizedString(
"UNSET_TUNNEL_ERROR",
tableName: "TunnelManager",
value: "Tunnel is unset.",
comment: ""
)
NSLocalizedString("Tunnel is unset.", comment: "")
}
}
struct InvalidDeviceStateError: LocalizedError {
var errorDescription: String? {
NSLocalizedString(
"INVALID_DEVICE_STATE_ERROR",
tableName: "TunnelManager",
value: "Invalid device state.",
comment: ""
)
NSLocalizedString("Invalid device state.", comment: "")
}
}
@@ -35,12 +25,7 @@ struct StartTunnelError: LocalizedError, WrappingError {
private let _underlyingError: Error
var errorDescription: String? {
NSLocalizedString(
"START_TUNNEL_ERROR",
tableName: "TunnelManager",
value: "Failed to start the tunnel.",
comment: ""
)
NSLocalizedString("Failed to start the tunnel.", comment: "")
}
var underlyingError: Error? {
@@ -56,12 +41,7 @@ struct StopTunnelError: LocalizedError, WrappingError {
private let _underlyingError: Error
var errorDescription: String? {
NSLocalizedString(
"STOP_TUNNEL_ERROR",
tableName: "TunnelManager",
value: "Failed to stop the tunnel.",
comment: ""
)
NSLocalizedString("Failed to stop the tunnel.", comment: "")
}
var underlyingError: Error? {
+27 -157
View File
@@ -38,130 +38,55 @@ extension TunnelState {
switch self {
case let .connecting(_, isPostQuantum, _), let .reconnecting(_, isPostQuantum, _):
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTING",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)
NSLocalizedString("Creating quantum secure connection", comment: "")
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTING",
tableName: "Main",
value: "Creating secure connection",
comment: ""
)
NSLocalizedString("Creating secure connection", comment: "")
}
case let .negotiatingEphemeralPeer(_, _, isPostQuantum, _):
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_NEGOTIATING_KEY",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)
NSLocalizedString("Creating quantum secure connection", comment: "")
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTING",
tableName: "Main",
value: "Creating secure connection",
comment: ""
)
NSLocalizedString("Creating secure connection", comment: "")
}
case let .connected(_, isPostQuantum, _):
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTED",
tableName: "Main",
value: "Quantum secure connection",
comment: ""
)
NSLocalizedString("Quantum secure connection", comment: "")
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTED",
tableName: "Main",
value: "Connected",
comment: ""
)
NSLocalizedString("Connected", comment: "")
}
case .disconnecting(.nothing):
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTING",
tableName: "Main",
value: "Disconnecting",
comment: ""
)
NSLocalizedString("Disconnecting", comment: "")
case .disconnecting(.reconnect), .pendingReconnect:
NSLocalizedString(
"TUNNEL_STATE_PENDING_RECONNECT",
tableName: "Main",
value: "Reconnecting",
comment: ""
)
NSLocalizedString("Reconnecting", comment: "")
case .disconnected:
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTED",
tableName: "Main",
value: "Unsecured connection",
comment: ""
)
NSLocalizedString("Unsecured connection", comment: "")
case .waitingForConnectivity(.noConnection), .error:
NSLocalizedString(
"TUNNEL_STATE_WAITING_FOR_CONNECTIVITY",
tableName: "Main",
value: "Blocked connection",
comment: ""
)
NSLocalizedString("Blocked connection", comment: "")
case .waitingForConnectivity(.noNetwork):
NSLocalizedString(
"TUNNEL_STATE_NO_NETWORK",
tableName: "Main",
value: "No network",
comment: ""
)
NSLocalizedString("No network", comment: "")
}
}
var localizedTitleForSelectLocationButton: String {
switch self {
case .disconnecting(.reconnect), .pendingReconnect:
NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Select location",
comment: ""
)
NSLocalizedString("Select location", comment: "")
case .disconnected, .disconnecting(.nothing):
NSLocalizedString(
"SELECT_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Select location",
comment: ""
)
NSLocalizedString("Select location", comment: "")
case .connecting, .connected, .reconnecting, .waitingForConnectivity, .error:
NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Switch location",
comment: ""
)
NSLocalizedString("Switch location", comment: "")
case .negotiatingEphemeralPeer:
NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Switch location",
comment: ""
)
NSLocalizedString("Switch location", comment: "")
}
}
@@ -176,79 +101,38 @@ extension TunnelState {
case let .connected(tunnelInfo, isPostQuantum, _):
if isPostQuantum {
String(
format: NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTED_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Quantum secure connection. Connected to %@, %@",
comment: ""
),
format: NSLocalizedString("Quantum secure connection. Connected to %@, %@", comment: ""),
tunnelInfo.exit.location.city,
tunnelInfo.exit.location.country
)
} else {
String(
format: NSLocalizedString(
"TUNNEL_STATE_CONNECTED_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Secure connection. Connected to %@, %@",
comment: ""
),
format: NSLocalizedString("Secure connection. Connected to %@, %@", comment: ""),
tunnelInfo.exit.location.city,
tunnelInfo.exit.location.country
)
}
case .disconnected:
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTED_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Unsecured connection",
comment: ""
)
NSLocalizedString("Unsecured connection", comment: "")
case let .reconnecting(tunnelInfo, _, _):
String(
format: NSLocalizedString(
"TUNNEL_STATE_RECONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Reconnecting to %@, %@",
comment: ""
),
format: NSLocalizedString("Reconnecting to %@, %@", comment: ""),
tunnelInfo.exit.location.city,
tunnelInfo.exit.location.country
)
case .waitingForConnectivity(.noConnection), .error:
NSLocalizedString(
"TUNNEL_STATE_WAITING_FOR_CONNECTIVITY_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Blocked connection",
comment: ""
)
NSLocalizedString("Blocked connection", comment: "")
case .waitingForConnectivity(.noNetwork):
NSLocalizedString(
"TUNNEL_STATE_NO_NETWORK_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "No network",
comment: ""
)
NSLocalizedString("No network", comment: "")
case .disconnecting(.nothing):
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Disconnecting",
comment: ""
)
NSLocalizedString("Disconnecting", comment: "")
case .disconnecting(.reconnect), .pendingReconnect:
NSLocalizedString(
"TUNNEL_STATE_PENDING_RECONNECT_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Reconnecting",
comment: ""
)
NSLocalizedString("Reconnecting", comment: "")
}
}
@@ -284,16 +168,12 @@ extension TunnelState {
return if let entryName {
String(format: NSLocalizedString(
"CONNECT_PANEL_TITLE",
tableName: "Main",
value: "%@ via %@\(usingDaita ? " using DAITA" : "")",
"%@ via %@\(usingDaita ? " using DAITA" : "")",
comment: ""
), exitName, entryName)
} else {
String(format: NSLocalizedString(
"CONNECT_PANEL_TITLE",
tableName: "Main",
value: "%@\(usingDaita ? " using DAITA" : "")",
"%@\(usingDaita ? " using DAITA" : "")",
comment: ""
), exitName)
}
@@ -301,19 +181,9 @@ extension TunnelState {
func secureConnectionLabel(isPostQuantum: Bool) -> String {
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)
NSLocalizedString("Creating quantum secure connection", comment: "")
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Creating secure connection",
comment: ""
)
NSLocalizedString("Creating secure connection", comment: "")
}
}
}
@@ -12,70 +12,40 @@ class AccountContentView: UIView {
let purchaseButton: InAppPurchaseButton = {
let button = InAppPurchaseButton()
button.setAccessibilityIdentifier(.purchaseButton)
button.setTitle(NSLocalizedString(
"ADD_TIME_BUTTON_TITLE",
tableName: "Account",
value: "Add time",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Add time", comment: ""), for: .normal)
return button
}()
let storeKit2PurchaseButton: AppButton = {
let button = AppButton(style: .success)
button.setTitle(NSLocalizedString(
"BUY_SUBSCRIPTION_STOREKIT_2",
tableName: "Account",
value: "Make a purchase with StoreKit2",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Make a purchase with StoreKit2", comment: ""), for: .normal)
return button
}()
let storeKit2RefundButton: AppButton = {
let button = AppButton(style: .success)
button.setTitle(NSLocalizedString(
"BUY_SUBSCRIPTION_STOREKIT_2",
tableName: "Account",
value: "Refund last purchase with StoreKit2",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Refund last purchase with StoreKit2", comment: ""), for: .normal)
return button
}()
let redeemVoucherButton: AppButton = {
let button = AppButton(style: .success)
button.setAccessibilityIdentifier(.redeemVoucherButton)
button.setTitle(NSLocalizedString(
"REDEEM_VOUCHER_BUTTON_TITLE",
tableName: "Account",
value: "Redeem voucher",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Redeem voucher", comment: ""), for: .normal)
return button
}()
let logoutButton: AppButton = {
let button = AppButton(style: .danger)
button.setAccessibilityIdentifier(.logoutButton)
button.setTitle(NSLocalizedString(
"LOGOUT_BUTTON_TITLE",
tableName: "Account",
value: "Log out",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Log out", comment: ""), for: .normal)
return button
}()
let deleteButton: AppButton = {
let button = AppButton(style: .danger)
button.setAccessibilityIdentifier(.deleteButton)
button.setTitle(NSLocalizedString(
"DELETE_BUTTON_TITLE",
tableName: "Account",
value: "Delete account",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Delete account", comment: ""), for: .normal)
return button
}()
@@ -21,12 +21,7 @@ class AccountDeviceRow: UIView {
private let titleLabel: UILabel = {
let label = UILabel()
label.text = NSLocalizedString(
"DEVICE_NAME",
tableName: "Account",
value: "Device name",
comment: ""
)
label.text = NSLocalizedString("Device name", comment: "")
label.font = .mullvadTiny
label.adjustsFontForContentSizeCategory = true
label.textColor = UIColor(white: 1.0, alpha: 0.6)
@@ -54,12 +49,7 @@ class AccountDeviceRow: UIView {
.foregroundColor: UIColor.primaryTextColor,
.underlineStyle: NSUnderlineStyle.single.rawValue,
]
let title = NSLocalizedString(
"DEVICE_MANAGEMENT",
tableName: "Account",
value: "Manage devices",
comment: ""
)
let title = NSLocalizedString("Manage devices", comment: "")
button.attributedText = NSMutableAttributedString(
string: title,
attributes: attributes
@@ -15,12 +15,7 @@ class AccountExpiryRow: UIView {
let expiry = value
if let expiry, expiry <= Date() {
let localizedString = NSLocalizedString(
"ACCOUNT_OUT_OF_TIME_LABEL",
tableName: "Account",
value: "OUT OF TIME",
comment: ""
)
let localizedString = NSLocalizedString("OUT OF TIME", comment: "")
valueLabel.text = localizedString
accessibilityValue = localizedString
@@ -46,12 +41,7 @@ class AccountExpiryRow: UIView {
private let textLabel: UILabel = {
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = NSLocalizedString(
"ACCOUNT_EXPIRY_LABEL",
tableName: "Account",
value: "Paid until",
comment: ""
)
textLabel.text = NSLocalizedString("Paid until", comment: "")
textLabel.font = .mullvadTiny
textLabel.numberOfLines = 0
textLabel.adjustsFontForContentSizeCategory = true
@@ -26,12 +26,7 @@ class AccountNumberRow: UIView {
private let titleLabel: UILabel = {
let textLabel = UILabel()
textLabel.text = NSLocalizedString(
"ACCOUNT_TOKEN_LABEL",
tableName: "Account",
value: "Account number",
comment: ""
)
textLabel.text = NSLocalizedString("Account number", comment: "")
textLabel.font = .mullvadTiny
textLabel.adjustsFontForContentSizeCategory = true
textLabel.textColor = UIColor(white: 1.0, alpha: 0.6)
@@ -168,12 +163,7 @@ class AccountNumberRow: UIView {
if isObscured {
return NSAttributedString(
string: NSLocalizedString(
"ACCOUNT_ACCESSIBILITY_OBSCURED",
tableName: "Account",
value: "Obscured",
comment: ""
)
string: NSLocalizedString("Obscured", comment: "")
)
} else {
return NSAttributedString(
@@ -193,12 +183,7 @@ class AccountNumberRow: UIView {
selector: #selector(didTapShowHideAccount)
),
UIAccessibilityCustomAction(
name: NSLocalizedString(
"ACCOUNT_ACCESSIBILITY_COPY_TO_PASTEBOARD",
tableName: "Account",
value: "Copy to pasteboard",
comment: ""
),
name: NSLocalizedString("Copy to pasteboard", comment: ""),
target: self,
selector: #selector(didTapCopyAccountNumber)
),
@@ -207,19 +192,9 @@ class AccountNumberRow: UIView {
private var showHideAccessibilityActionName: String {
if isObscured {
return NSLocalizedString(
"ACCOUNT_ACCESSIBILITY_SHOW_ACCOUNT_NUMBER",
tableName: "Account",
value: "Show account number",
comment: ""
)
return NSLocalizedString("Show account number", comment: "")
} else {
return NSLocalizedString(
"ACCOUNT_ACCESSIBILITY_HIDE_ACCOUNT_NUMBER",
tableName: "Account",
value: "Hide account number",
comment: ""
)
return NSLocalizedString("Hide account number", comment: "")
}
}
@@ -64,12 +64,7 @@ class AccountViewController: UIViewController, @unchecked Sendable {
super.viewDidLoad()
view.backgroundColor = .secondaryColor
navigationItem.title = NSLocalizedString(
"NAVIGATION_TITLE",
tableName: "Account",
value: "Account",
comment: ""
)
navigationItem.title = NSLocalizedString("Account", comment: "")
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .done,
@@ -16,18 +16,8 @@ struct PaymentAlertPresenter {
func showAlertForRefund(completion: (@MainActor @Sendable () -> Void)? = nil) {
let presentation = AlertPresentation(
id: "payment-refund-alert",
title: NSLocalizedString(
"PAYMENT_REFUND_ALERT_TITLE",
tableName: "Payment",
value: "Refund successful",
comment: ""
),
message: NSLocalizedString(
"PAYMENT_REFUND_ALERT_MESSAGE",
tableName: "Payment",
value: "Your purchase was successfully refunded.",
comment: ""
),
title: NSLocalizedString("Refund successful", comment: ""),
message: NSLocalizedString("Your purchase was successfully refunded.", comment: ""),
buttons: [
AlertAction(
title: okButtonTextForKey("PAYMENT_REFUND_ALERT_OK_ACTION"),
@@ -121,11 +111,6 @@ struct PaymentAlertPresenter {
}
private func okButtonTextForKey(_ key: String) -> String {
NSLocalizedString(
key,
tableName: "Payment",
value: "Got it!",
comment: ""
)
NSLocalizedString("Got it!", comment: "")
}
}
@@ -64,12 +64,7 @@ class RestorePurchasesView: UIView {
}
private func makeAttributedString() -> NSAttributedString {
let text = NSLocalizedString(
"RESTORE_PURCHASES_BUTTON_TITLE",
tableName: "Account",
value: "Restore purchases",
comment: ""
)
let text = NSLocalizedString("Restore purchases", comment: "")
return NSAttributedString(string: text, attributes: [
.font: UIFont.mullvadMini,
@@ -39,12 +39,7 @@ class AccountDeletionContentView: UIView {
label.adjustsFontForContentSizeCategory = true
label.lineBreakMode = .byWordWrapping
label.textColor = .white
label.text = NSLocalizedString(
"ACCOUNT_DELETION_PAGE_TITLE",
tableName: "Account",
value: "Account deletion",
comment: ""
)
label.text = NSLocalizedString("Account deletion", comment: "")
return label
}()
@@ -68,9 +63,7 @@ class AccountDeletionContentView: UIView {
label.lineBreakMode = .byWordWrapping
label.textColor = .white
label.text = NSLocalizedString(
"TIP_TEXT",
tableName: "Account",
value: """
"""
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" \
@@ -106,24 +99,14 @@ class AccountDeletionContentView: UIView {
private let deleteButton: AppButton = {
let button = AppButton(style: .danger)
button.setAccessibilityIdentifier(.deleteButton)
button.setTitle(NSLocalizedString(
"DELETE_ACCOUNT_BUTTON_TITLE",
tableName: "Account",
value: "Delete Account",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Delete Account", comment: ""), for: .normal)
return button
}()
private let cancelButton: AppButton = {
let button = AppButton(style: .default)
button.setAccessibilityIdentifier(.cancelButton)
button.setTitle(NSLocalizedString(
"CANCEL_BUTTON_TITLE",
tableName: "Account",
value: "Cancel",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Cancel", comment: ""), for: .normal)
return button
}()
@@ -219,12 +202,7 @@ class AccountDeletionContentView: UIView {
case let .failure(error):
return error.localizedDescription
case .loading:
return NSLocalizedString(
"DELETE_ACCOUNT_STATUS_WAITING",
tableName: "Account",
value: "Deleting account...",
comment: ""
)
return NSLocalizedString("Deleting account...", comment: "")
default: return ""
}
}
@@ -324,9 +302,7 @@ class AccountDeletionContentView: UIView {
private func updateData() {
viewModel.flatMap { viewModel in
let text = NSLocalizedString(
"BODY_LABEL_TEXT",
tableName: "Account",
value: """
"""
Are you sure you want to delete account **\(viewModel.accountNumber)**?
""",
comment: ""
@@ -16,12 +16,7 @@ enum AccountDeletionError: LocalizedError {
var errorDescription: String? {
switch self {
case .invalidInput:
return NSLocalizedString(
"INVALID_ACCOUNT_NUMBER",
tableName: "Account",
value: "Last four digits of the account number are incorrect",
comment: ""
)
return NSLocalizedString("Last four digits of the account number are incorrect", comment: "")
}
}
}
@@ -21,12 +21,7 @@ class SetupAccountCompletedContentView: UIView {
label.adjustsFontForContentSizeCategory = true
label.lineBreakMode = .byWordWrapping
label.numberOfLines = .zero
label.text = NSLocalizedString(
"CREATED_ACCOUNT_CONFIRMATION_PAGE_TITLE",
tableName: "CreatedAccountConfirmation",
value: "You’re all set!!",
comment: ""
)
label.text = NSLocalizedString("You’re all set!!", comment: "")
return label
}()
@@ -34,9 +29,7 @@ class SetupAccountCompletedContentView: UIView {
let label = UILabel()
let message = NSMutableAttributedString(string: NSLocalizedString(
"CREATED_ACCOUNT_CONFIRMATION_PAGE_BODY",
tableName: "CreatedAccountConfirmation",
value: """
"""
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.
@@ -57,12 +50,7 @@ class SetupAccountCompletedContentView: UIView {
private let privacyButton: AppButton = {
let button = AppButton(style: .success)
button.setAccessibilityIdentifier(.learnAboutPrivacyButton)
let localizedString = NSLocalizedString(
"LEARN_ABOUT_PRIVACY_BUTTON",
tableName: "CreatedAccountConfirmation",
value: "Learn about privacy",
comment: ""
)
let localizedString = NSLocalizedString("Learn about privacy", comment: "")
button.setTitle(localizedString, for: .normal)
button.setImage(UIImage(named: "IconExtlink")?.imageFlippedForRightToLeftLayoutDirection(), for: .normal)
return button
@@ -71,12 +59,7 @@ class SetupAccountCompletedContentView: UIView {
private let startButton: AppButton = {
let button = AppButton(style: .success)
button.setAccessibilityIdentifier(.startUsingTheAppButton)
button.setTitle(NSLocalizedString(
"START_USING_THE_APP_BUTTON",
tableName: "CreatedAccountConfirmation",
value: "Start using the app",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Start using the app", comment: ""), for: .normal)
return button
}()
@@ -29,12 +29,7 @@ final class WelcomeContentView: UIView, Sendable {
label.adjustsFontForContentSizeCategory = true
label.lineBreakMode = .byWordWrapping
label.numberOfLines = .zero
label.text = NSLocalizedString(
"WELCOME_PAGE_TITLE",
tableName: "Welcome",
value: "Congrats!",
comment: ""
)
label.text = NSLocalizedString("Congrats!", comment: "")
return label
}()
@@ -45,12 +40,7 @@ final class WelcomeContentView: UIView, Sendable {
label.adjustsFontForContentSizeCategory = true
label.lineBreakMode = .byWordWrapping
label.numberOfLines = .zero
label.text = NSLocalizedString(
"WELCOME_PAGE_SUBTITLE",
tableName: "Welcome",
value: "Here’s your account number. Save it!",
comment: ""
)
label.text = NSLocalizedString("Here’s your account number. Save it!", comment: "")
return label
}()
@@ -107,9 +97,7 @@ final class WelcomeContentView: UIView, Sendable {
label.lineBreakMode = .byWordWrapping
label.lineBreakStrategy = []
label.text = NSLocalizedString(
"WELCOME_PAGE_DESCRIPTION",
tableName: "Welcome",
value: """
"""
To start using the app, you first need to \
add time to your account. Either buy credit \
on our website or redeem a voucher.
@@ -122,12 +110,7 @@ final class WelcomeContentView: UIView, Sendable {
private let purchaseButton: InAppPurchaseButton = {
let button = InAppPurchaseButton()
button.setAccessibilityIdentifier(.purchaseButton)
let localizedString = NSLocalizedString(
"ADD_TIME_BUTTON",
tableName: "Welcome",
value: "Add time",
comment: ""
)
let localizedString = NSLocalizedString("Add time", comment: "")
button.setTitle(localizedString, for: .normal)
return button
}()
@@ -171,12 +154,10 @@ final class WelcomeContentView: UIView, Sendable {
var viewModel: WelcomeViewModel? {
didSet {
accountNumberLabel.text = viewModel?.accountNumber
deviceNameLabel.text = String(format: NSLocalizedString(
"DEVICE_NAME_TEXT",
tableName: "Welcome",
value: "Device name: %@",
comment: ""
), viewModel?.deviceName ?? "")
deviceNameLabel.text = String(
format: NSLocalizedString("Device name: %@", comment: ""),
viewModel?.deviceName ?? ""
)
}
}
@@ -101,12 +101,7 @@ class InAppPurchaseViewController: UIViewController, StorePaymentObserver {
}
func showPurchaseOptions(for products: [SKProduct]) {
let localizedString = NSLocalizedString(
"ADD_TIME_BUTTON",
tableName: "Welcome",
value: "Add Time",
comment: ""
)
let localizedString = NSLocalizedString("Add Time", comment: "")
let sheetController = UIAlertController(
title: localizedString,
message: nil,
@@ -127,12 +122,7 @@ class InAppPurchaseViewController: UIViewController, StorePaymentObserver {
sheetController.addAction(action)
}
let cancelAction = UIAlertAction(title: NSLocalizedString(
"SHEET_CANCEL_BUTTON",
tableName: "ActionSheet",
value: "Cancel",
comment: ""
), style: .cancel) { _ in
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in
self.didFinish?()
}
cancelAction.accessibilityIdentifier = "actoin-sheet-cancel-button"
@@ -25,12 +25,7 @@ final class AccountInputGroupView: UIView {
button.setImage(UIImage.Buttons.rightArrow, for: .normal)
button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
button.setAccessibilityIdentifier(.loginTextFieldButton)
button.accessibilityLabel = NSLocalizedString(
"ACCOUNT_INPUT_LOGIN_BUTTON_ACCESSIBILITY_LABEL",
tableName: "AccountInput",
value: "Log in",
comment: ""
)
button.accessibilityLabel = NSLocalizedString("Log in", comment: "")
return button
}()
@@ -97,12 +92,7 @@ final class AccountInputGroupView: UIView {
button.translatesAutoresizingMaskIntoConstraints = false
button.contentHorizontalAlignment = .leading
button.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
button.accessibilityLabel = NSLocalizedString(
"LAST_USED_ACCOUNT_ACCESSIBILITY_LABEL",
tableName: "AccountInput",
value: "Last used account",
comment: ""
)
button.accessibilityLabel = NSLocalizedString("Last used account", comment: "")
button.configuration?.contentInsets = UIMetrics.textFieldMargins.toDirectionalInsets
button.configuration?.title = " "
button.configuration?
@@ -120,12 +110,7 @@ final class AccountInputGroupView: UIView {
let button = CustomButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
button.accessibilityLabel = NSLocalizedString(
"REMOVE_LAST_USED_ACCOUNT_ACCESSIBILITY_LABEL",
tableName: "AccountInput",
value: "Remove last used account",
comment: ""
)
button.accessibilityLabel = NSLocalizedString("Remove last used account", comment: "")
button.configuration?.image = UIImage.Buttons.closeSmall.withTintColor(.primaryColor)
button.configuration?.title = " "
return button
@@ -78,12 +78,7 @@ class LoginContentView: UIView {
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.adjustsFontForContentSizeCategory = true
textLabel.numberOfLines = 0
textLabel.text = NSLocalizedString(
"CREATE_BUTTON_HEADER_LABEL",
tableName: "Login",
value: "Don’t have an account number?",
comment: ""
)
textLabel.text = NSLocalizedString("Don’t have an account number?", comment: "")
textLabel.numberOfLines = 0
return textLabel
}()
@@ -92,12 +87,7 @@ class LoginContentView: UIView {
let button = AppButton(style: .default)
button.setAccessibilityIdentifier(.createAccountButton)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(NSLocalizedString(
"CREATE_ACCOUNT_BUTTON_LABEL",
tableName: "Login",
value: "Create account",
comment: ""
), for: .normal)
button.setTitle(NSLocalizedString("Create account", comment: ""), for: .normal)
return button
}()
Loaded 100 of 148 files, more files were not shown because too many files have changed in this diff. Show more