summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/Localizations/AppLanguage.swift145
-rw-r--r--ios/Localizations/da.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/de.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/en.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/es.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/fi.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/fr.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/it.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/ja.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/ko.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/my.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/nb.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/nl.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/pl.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/pt-PT.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/ru.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/sv.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/th.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/tr.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/zh-Hans.lproj/Localizable.strings7
-rw-r--r--ios/Localizations/zh-Hant.lproj/Localizable.strings7
-rw-r--r--ios/MullvadTypes/PersistentAccessMethod.swift4
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj135
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift12
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift3
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift19
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift41
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift23
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift25
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsViewModel.swift5
31 files changed, 513 insertions, 40 deletions
diff --git a/ios/Localizations/AppLanguage.swift b/ios/Localizations/AppLanguage.swift
new file mode 100644
index 0000000000..b299938235
--- /dev/null
+++ b/ios/Localizations/AppLanguage.swift
@@ -0,0 +1,145 @@
+//
+// AppLanguage.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2025-07-16.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/**
+ * TODO:
+ * Edit the "Localization Cleanup (Release Build)" build script phase after
+ * multi-language support is completed and released.
+ *
+ * Note:
+ * - Localization is not available for the Staging configuration, which is used by `UITest`.
+ * - When the functionality is finished, the script should:
+ * • Remove bilingual content only for Staging.
+ * • Eliminate the Debug configuration check.
+ */
+enum AppLanguage: String, CaseIterable, Identifiable {
+ case english = "en"
+ case danish = "da"
+ case german = "de"
+ case spanish = "es"
+ case finnish = "fi"
+ case french = "fr"
+ case italian = "it"
+ case japanese = "ja"
+ case korean = "ko"
+ case burmese = "my"
+ case norwegianBokmal = "nb"
+ case dutch = "nl"
+ case polish = "pl"
+ case portuguese = "pt"
+ case russian = "ru"
+ case swedish = "sv"
+ case thai = "th"
+ case turkish = "tr"
+ case chineseSimplified = "zh-Hans" // Maps to zh-CN
+ case chineseTraditional = "zh-Hant" // Maps to zh-TW
+
+ var id: String { rawValue }
+
+ var displayName: String {
+ switch self {
+ case .english: "English"
+ case .danish: "Dansk"
+ case .german: "Deutsch"
+ case .spanish: "Español"
+ case .finnish: "Suomi"
+ case .french: "Français"
+ case .italian: "Italiano"
+ case .japanese: "日本語"
+ case .korean: "한국어"
+ case .burmese: "မြန်မာ"
+ case .norwegianBokmal: "Norsk Bokmål"
+ case .dutch: "Nederlands"
+ case .polish: "Polski"
+ case .portuguese: "Português"
+ case .russian: "Русский"
+ case .swedish: "Svenska"
+ case .thai: "ไทย"
+ case .turkish: "Türkçe"
+ case .chineseSimplified: "简体中文"
+ case .chineseTraditional: "繁體中文"
+ }
+ }
+
+ var countryCodeForFlag: String {
+ switch self {
+ case .english: "us" // English → US flag (or "gb" for UK)
+ case .danish: "dk"
+ case .german: "de"
+ case .spanish: "es"
+ case .finnish: "fi"
+ case .french: "fr"
+ case .italian: "it"
+ case .japanese: "jp"
+ case .korean: "kr"
+ case .burmese: "mm"
+ case .norwegianBokmal: "no"
+ case .dutch: "nl"
+ case .polish: "pl"
+ case .portuguese: "pt"
+ case .russian: "ru"
+ case .swedish: "se"
+ case .thai: "th"
+ case .turkish: "tr"
+ case .chineseSimplified: "cn"
+ case .chineseTraditional: "tw"
+ }
+ }
+
+ static var allSorted: [AppLanguage] {
+ AppLanguage.allCases
+ .sorted { $0.displayName.localizedCaseInsensitiveCompare($1.displayName) == .orderedAscending }
+ }
+
+ static func from(_ code: String) -> AppLanguage {
+ AppLanguage(rawValue: code) ?? .english
+ }
+
+ var flagEmoji: String {
+ let base: UInt32 = 127397
+ var flagString = ""
+ for scalar in countryCodeForFlag.uppercased().unicodeScalars {
+ guard let scalarValue = UnicodeScalar(base + scalar.value) else { return "" }
+ flagString.unicodeScalars.append(scalarValue)
+ }
+ return flagString
+ }
+
+ static var currentLanguage: AppLanguage {
+ let defaultCode = AppLanguage.english.rawValue
+ let fullCode = Locale.preferredLanguages.first ?? defaultCode
+
+ if #available(iOS 16, *) {
+ let locale = Locale(identifier: fullCode)
+ if let script = locale.language.script?.identifier {
+ switch script {
+ case "Hans":
+ return .chineseSimplified
+ case "Hant":
+ return .chineseTraditional
+ default:
+ break
+ }
+ }
+ } else {
+ if fullCode.contains("Hans") {
+ return .chineseSimplified
+ } else if fullCode.contains("Hant") {
+ return .chineseTraditional
+ }
+ }
+
+ // Otherwise, try to get languageCode (e.g., "en", "fr")
+ let locale = Locale(identifier: fullCode)
+ let langCode = locale.languageCode ?? defaultCode
+
+ return AppLanguage.from(langCode)
+ }
+}
diff --git a/ios/Localizations/da.lproj/Localizable.strings b/ios/Localizations/da.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/da.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/de.lproj/Localizable.strings b/ios/Localizations/de.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/de.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/en.lproj/Localizable.strings b/ios/Localizations/en.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/en.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/es.lproj/Localizable.strings b/ios/Localizations/es.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/es.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/fi.lproj/Localizable.strings b/ios/Localizations/fi.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/fi.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/fr.lproj/Localizable.strings b/ios/Localizations/fr.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/fr.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/it.lproj/Localizable.strings b/ios/Localizations/it.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/it.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/ja.lproj/Localizable.strings b/ios/Localizations/ja.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/ja.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/ko.lproj/Localizable.strings b/ios/Localizations/ko.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/ko.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/my.lproj/Localizable.strings b/ios/Localizations/my.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/my.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/nb.lproj/Localizable.strings b/ios/Localizations/nb.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/nb.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/nl.lproj/Localizable.strings b/ios/Localizations/nl.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/nl.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/pl.lproj/Localizable.strings b/ios/Localizations/pl.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/pl.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/pt-PT.lproj/Localizable.strings b/ios/Localizations/pt-PT.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/pt-PT.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/ru.lproj/Localizable.strings b/ios/Localizations/ru.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/ru.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/sv.lproj/Localizable.strings b/ios/Localizations/sv.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/sv.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/th.lproj/Localizable.strings b/ios/Localizations/th.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/th.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/tr.lproj/Localizable.strings b/ios/Localizations/tr.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/tr.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/zh-Hans.lproj/Localizable.strings b/ios/Localizations/zh-Hans.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/zh-Hans.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/Localizations/zh-Hant.lproj/Localizable.strings b/ios/Localizations/zh-Hant.lproj/Localizable.strings
new file mode 100644
index 0000000000..f40aa19b9a
--- /dev/null
+++ b/ios/Localizations/zh-Hant.lproj/Localizable.strings
@@ -0,0 +1,7 @@
+/*
+ Localizable.strings
+ MullvadVPN
+
+ Created by Mojgan on 2025-07-16.
+ Copyright © 2025 Mullvad VPN AB. All rights reserved.
+*/
diff --git a/ios/MullvadTypes/PersistentAccessMethod.swift b/ios/MullvadTypes/PersistentAccessMethod.swift
index 9a3310d06f..ff816405b6 100644
--- a/ios/MullvadTypes/PersistentAccessMethod.swift
+++ b/ios/MullvadTypes/PersistentAccessMethod.swift
@@ -82,12 +82,12 @@ public enum PersistentProxyConfiguration: Codable, Equatable, Sendable {
extension PersistentProxyConfiguration {
/// Socks autentication method.
- public enum SocksAuthentication: Codable, Equatable {
+ public enum SocksAuthentication: Codable, Equatable, Sendable {
case noAuthentication
case authentication(UserCredential)
}
- public struct UserCredential: Codable, Equatable {
+ public struct UserCredential: Codable, Equatable, Sendable {
public let username: String
public let password: String
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index a4bf13fb88..b0bd9e504a 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -980,6 +980,16 @@
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 */; };
@@ -2442,6 +2452,27 @@
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>"; };
@@ -3917,12 +3948,16 @@
children = (
58F3C0A824A50C0E003E76BE /* Assets */,
58ECD29023F178FD004298B6 /* Configurations */,
+ 584F991F2902CBDD001F858D /* Frameworks */,
+ F05DCE9D2E38C563009A9B85 /* Localizations */,
01EF6F2D2B6A51B100125696 /* mullvad-api.h */,
8556EB512B9A1C6900D26DD4 /* MullvadApi.swift */,
58D223F4294C8FF00029F5F8 /* MullvadLogging */,
F0ACE3092BE4E478006D5333 /* MullvadMockData */,
06799ABD28F98E1D00ACD94E /* MullvadREST */,
58FBFBE7291622580020E046 /* MullvadRESTTests */,
+ A992DA1E2C24709F00DE7CE5 /* MullvadRustRuntime */,
+ A9D9A4C12C36D53C004088DD /* MullvadRustRuntimeTests */,
58B2FDD42AA71D2A003EB5C6 /* MullvadSettings */,
581943F228F8014500B0CB5E /* MullvadTypes */,
58CE5E62224146200008646E /* MullvadVPN */,
@@ -3933,15 +3968,12 @@
58CE5E7A224146470008646E /* PacketTunnel */,
58C7A4372A863F450060C66F /* PacketTunnelCore */,
58C7A4432A863F490060C66F /* PacketTunnelCoreTests */,
+ 58CE5E61224146200008646E /* Products */,
7A88DCCF2A8FABBE00D2FF0E /* Routing */,
7A88DCDD2A8FABBE00D2FF0E /* RoutingTests */,
589A454A28DDF59B00565204 /* Shared */,
7A83C3FC2A55B39500DFB83A /* TestPlans */,
58695A9E2A4ADA9200328DB3 /* TunnelObfuscationTests */,
- A992DA1E2C24709F00DE7CE5 /* MullvadRustRuntime */,
- A9D9A4C12C36D53C004088DD /* MullvadRustRuntimeTests */,
- 58CE5E61224146200008646E /* Products */,
- 584F991F2902CBDD001F858D /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -4659,6 +4691,15 @@
path = PostQuantum;
sourceTree = "<group>";
};
+ F05DCE9D2E38C563009A9B85 /* Localizations */ = {
+ isa = PBXGroup;
+ children = (
+ F05DCE872E38C563009A9B85 /* AppLanguage.swift */,
+ F05DCE9C2E38C563009A9B85 /* Localizable.strings */,
+ );
+ path = Localizations;
+ sourceTree = "<group>";
+ };
F06045F02B2324DA00B2D37A /* ApiHandlers */ = {
isa = PBXGroup;
children = (
@@ -5195,6 +5236,7 @@
58CE5E5E224146200008646E /* Resources */,
58CE5E85224146470008646E /* Embed Foundation Extensions */,
06799AD628F98E1D00ACD94E /* Embed Frameworks */,
+ F0DABA422E27D20900EB4E21 /* Localization Cleanup (Release Build) */,
);
buildRules = (
);
@@ -5557,7 +5599,26 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
+ th,
Base,
+ fr,
+ de,
+ es,
+ it,
+ "pt-PT",
+ nl,
+ sv,
+ da,
+ nb,
+ ja,
+ ko,
+ "zh-Hans",
+ "zh-Hant",
+ ru,
+ pl,
+ tr,
+ fi,
+ my,
);
mainGroup = 58CE5E57224146200008646E;
packageReferences = (
@@ -5597,6 +5658,7 @@
buildActionMask = 2147483647;
files = (
062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */,
+ F05DCEA22E38C5F1009A9B85 /* Localizable.strings in Resources */,
7A95B67B2D5F758300687524 /* relays.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -5619,6 +5681,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ F05DCEA52E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5636,6 +5699,7 @@
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 */,
58CE5E6B224146210008646E /* Assets.xcassets in Resources */,
@@ -5647,6 +5711,7 @@
buildActionMask = 2147483647;
files = (
A9BA08322BA32FB6005A7A2D /* PrivacyInfo.xcprivacy in Resources */,
+ F05DCEA02E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5654,6 +5719,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ F05DCEA12E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5661,6 +5727,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ F05DCEA32E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5668,6 +5735,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ F05DCEA42E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5682,6 +5750,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ F05DCEA62E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5717,6 +5786,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ F05DCEA72E38C5F1009A9B85 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5834,6 +5904,24 @@
shellPath = /bin/sh;
shellScript = "exec > $PROJECT_DIR/relays-prebuild.log 2>&1\n\n$PROJECT_DIR/relays-prebuild.sh\n";
};
+ F0DABA422E27D20900EB4E21 /* Localization Cleanup (Release Build) */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Localization Cleanup (Release Build)";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# Run the following steps if the build configuration is NOT Debug\n# OR if the configuration is Staging (used for UITests).\nif [ \"$CONFIGURATION\" != \"Debug\" ] || [ \"$CONFIGURATION\" = \"Staging\" ]; then\n echo \"Removing non-English localizations for Release or Staging build\"\n find \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" -type d -name \"*.lproj\" ! -name \"en.lproj\" -exec rm -r {} +\nfi\n";
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -6435,6 +6523,7 @@
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 */,
@@ -7240,6 +7329,36 @@
};
/* 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;
@@ -7597,6 +7716,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -7661,6 +7781,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -8307,6 +8428,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -8381,6 +8503,10 @@
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F;
ENABLE_BITCODE = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "DEBUG=1",
+ );
INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -9163,6 +9289,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index 7d0196560c..b7113fb11e 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -104,6 +104,7 @@ public enum AccessibilityIdentifier: Equatable {
case daitaCell
case daitaFilterPill
case obfuscationFilterPill
+ case languageCell
// Labels
case accountPageDeviceNameLabel
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
index dae1b67605..898a518411 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
@@ -38,6 +38,9 @@ enum SettingsNavigationRoute: Equatable {
/// DAITA route.
case daita
+
+ /// Language route.
+ case language
}
/// Top-level settings coordinator.
@@ -141,6 +144,15 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
presentChild(safariCoordinator, animated: animated, completion: completion)
+ case .language:
+ logger.debug("Show App's settings for \(route)")
+
+ if let url = URL(string: UIApplication.openSettingsURLString) {
+ if UIApplication.shared.canOpenURL(url) {
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
+ }
+ }
+
default:
// Ignore navigation if the route is already presented.
guard currentRoute != route else {
diff --git a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
index ea2ef51e32..bef1dd4f7e 100644
--- a/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/SettingsViewControllerFactory.swift
@@ -57,6 +57,9 @@ struct SettingsViewControllerFactory {
case .faq:
// Handled separately and presented as a modal.
.failed
+ case .language:
+ // Handled separately and presented settings.
+ .failed
case .vpnSettings:
makeVPNSettingsViewCoordinator()
case .problemReport:
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
index e837f65cd4..1e76a42e9f 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
@@ -149,6 +149,25 @@ final class SettingsCellFactory: @preconcurrency CellFactoryProtocol, Sendable {
cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.disclosureType = .chevron
+ case .language:
+ guard let cell = cell as? SettingsCell else { return }
+
+ cell.titleLabel.text = NSLocalizedString(
+ "LANGUAGE_CELL_LABEL",
+ tableName: "Settings",
+ value: "Language",
+ comment: ""
+ )
+
+ cell.detailTitleLabel.text = NSLocalizedString(
+ "LANGUAGE_CELL_DETAIL_LABEL",
+ tableName: "Settings",
+ value: viewModel.currentLanguage,
+ comment: ""
+ )
+
+ cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
+ cell.disclosureType = .chevron
}
}
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
index 138fabf450..74c09d704a 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
@@ -46,6 +46,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
case apiAccess
case version
case problemReport
+ case language
}
enum Item: String {
@@ -56,23 +57,26 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
case apiAccess
case daita
case multihop
+ case language
var accessibilityIdentifier: AccessibilityIdentifier {
switch self {
case .vpnSettings:
- return .vpnSettingsCell
+ .vpnSettingsCell
case .changelog:
- return .versionCell
+ .versionCell
case .problemReport:
- return .problemReportCell
+ .problemReportCell
case .faq:
- return .faqCell
+ .faqCell
case .apiAccess:
- return .apiAccessCell
+ .apiAccessCell
case .daita:
- return .daitaCell
+ .daitaCell
case .multihop:
- return .multihopCell
+ .multihopCell
+ case .language:
+ .languageCell
}
}
@@ -109,14 +113,14 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
registerClasses()
updateDataSnapshot()
- interactor.didUpdateDeviceState = { [weak self] _ in
+ interactor.didUpdateSettings = { [weak self] in
self?.updateDataSnapshot()
}
storedAccountData = interactor.deviceState.accountData
}
- func reload(from tunnelSettings: LatestTunnelSettings) {
- settingsCellFactory.viewModel = SettingsViewModel(from: tunnelSettings)
+ func reload() {
+ settingsCellFactory.viewModel = SettingsViewModel(from: interactor.tunnelSettings)
var snapshot = snapshot()
snapshot.reconfigureItems(snapshot.itemIdentifiers)
@@ -135,9 +139,15 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
- tableView.dequeueReusableHeaderFooterView(
- withIdentifier: HeaderFooterReuseIdentifier.spacer.rawValue
- )
+ guard let section = sectionIdentifier(for: section) else { return nil }
+ return switch section {
+ case .language:
+ nil
+ default:
+ tableView.dequeueReusableHeaderFooterView(
+ withIdentifier: HeaderFooterReuseIdentifier.spacer.rawValue
+ )
+ }
}
// MARK: - Private
@@ -163,6 +173,11 @@ final class SettingsDataSource: UITableViewDiffableDataSource<SettingsDataSource
], toSection: .vpnSettings)
}
+ #if DEBUG
+ snapshot.appendSections([.language])
+ snapshot.appendItems([.language], toSection: .language)
+ #endif
+
snapshot.appendSections([.apiAccess])
snapshot.appendItems([.apiAccess], toSection: .apiAccess)
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift b/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift
index de2585737c..7509bf5215 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsInteractor.swift
@@ -13,28 +13,27 @@ import MullvadSettings
final class SettingsInteractor {
private let tunnelManager: TunnelManager
private var tunnelObserver: TunnelObserver?
+ var didUpdateSettings: (() -> Void)?
- var didUpdateDeviceState: ((DeviceState) -> Void)?
- var didUpdateTunnelSettings: ((LatestTunnelSettings) -> Void)?
-
- var tunnelSettings: LatestTunnelSettings {
- tunnelManager.settings
- }
-
- var deviceState: DeviceState {
- tunnelManager.deviceState
- }
+ private(set) var tunnelSettings: LatestTunnelSettings
+ private(set) var deviceState: DeviceState
init(tunnelManager: TunnelManager) {
self.tunnelManager = tunnelManager
+ self.tunnelSettings = tunnelManager.settings
+ self.deviceState = tunnelManager.deviceState
let tunnelObserver =
TunnelBlockObserver(
didUpdateDeviceState: { [weak self] _, deviceState, _ in
- self?.didUpdateDeviceState?(deviceState)
+ guard let self = self else { return }
+ self.deviceState = deviceState
+ self.didUpdateSettings?()
},
didUpdateTunnelSettings: { [weak self] _, settings in
- self?.didUpdateTunnelSettings?(settings)
+ guard let self = self else { return }
+ self.tunnelSettings = settings
+ self.didUpdateSettings?()
}
)
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
index 784fcfa000..783cac1ee1 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
@@ -70,10 +70,15 @@ class SettingsViewController: UITableViewController {
dataSource = SettingsDataSource(tableView: tableView, interactor: interactor)
dataSource?.delegate = self
- interactor.didUpdateTunnelSettings = { [weak self] newSettings in
- self?.dataSource?.reload(from: newSettings)
+ interactor.didUpdateSettings = { [weak self] in
+ self?.dataSource?.reload()
}
}
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ dataSource?.reload()
+ }
}
extension SettingsViewController: @preconcurrency SettingsDataSourceDelegate {
@@ -108,19 +113,21 @@ extension SettingsDataSource.Item {
var navigationRoute: SettingsNavigationRoute? {
switch self {
case .vpnSettings:
- return .vpnSettings
+ .vpnSettings
case .changelog:
- return .changelog
+ .changelog
case .problemReport:
- return .problemReport
+ .problemReport
case .faq:
- return .faq
+ .faq
case .apiAccess:
- return .apiAccess
+ .apiAccess
case .daita:
- return .daita
+ .daita
case .multihop:
- return .multihop
+ .multihop
+ case .language:
+ .language
}
}
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewModel.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewModel.swift
index fc698caa50..77abcf9f05 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsViewModel.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewModel.swift
@@ -12,6 +12,11 @@ struct SettingsViewModel {
private(set) var daitaSettings: DAITASettings
private(set) var multihopState: MultihopState
+ var currentLanguage: String {
+ let currentLanguage = AppLanguage.currentLanguage
+ return "\(currentLanguage.flagEmoji) \(currentLanguage.displayName)"
+ }
+
init(from tunnelSettings: LatestTunnelSettings = LatestTunnelSettings()) {
daitaSettings = tunnelSettings.daita
multihopState = tunnelSettings.tunnelMultihopState