Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//

import SwiftUI
import StratixModels

extension CloudLibrarySettingsView {
var streamPane: some View {
Expand All @@ -14,6 +15,17 @@ extension CloudLibrarySettingsView {
CloudLibraryPickerRow(title: "Client Profile", selection: streamBinding(\.clientProfileOSName), options: ["Auto", "Android", "Windows", "Tizen"])
CloudLibraryPickerRow(title: "Resolution", selection: streamBinding(\.preferredResolution), options: ["720p", "1080p", "1440p"])
CloudLibraryPickerRow(title: "Frame Rate", selection: streamBinding(\.preferredFPS), options: ["30", "60"])
CloudLibraryPickerRow(
title: "Preferred Game Language",
selection: Binding(
get: { streamBinding(\.preferredGameLanguage).wrappedValue.displayName },
set: { newValue in
let language = SupportedGameLanguage.allCases.first { $0.displayName == newValue } ?? .systemDefault
streamBinding(\.preferredGameLanguage).wrappedValue = language
}
),
options: SupportedGameLanguage.allCases.map(\.displayName)
)
CloudLibraryPickerRow(title: "Stats HUD Position", selection: streamBinding(\.statsHUDPosition), options: ["topRight", "topLeft", "bottomRight", "bottomLeft"])
CloudLibrarySliderRow(title: "Bitrate Cap", value: streamBinding(\.bitrateCapMbps), range: 0...100, formatter: bitrateText, step: 4)
CloudLibraryToggleRow(title: "HDR Preferred", subtitle: "Enable HDR metadata when available", isOn: streamBinding(\.hdrEnabled))
Expand Down
39 changes: 33 additions & 6 deletions Packages/StratixCore/Sources/StratixCore/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ public final class SettingsStore {
}

public struct StreamSettings: Sendable, Equatable {
public var locale: String
public var preferredGameLanguage: SupportedGameLanguage
public var locale: String {
get { preferredGameLanguage.localeCode }
set {
preferredGameLanguage = SupportedGameLanguage(rawValue: newValue) ?? .systemDefault
}
}
public var preferIPv6: Bool
public var preferredRegionID: String
public var statsHUDPosition: String
Expand Down Expand Up @@ -108,9 +114,16 @@ public final class SettingsStore {
colorRange: String = "Auto",
safeAreaPercent: Double = 100.0,
stereoAudio: Bool = false,
chatChannelEnabled: Bool = false
chatChannelEnabled: Bool = false,
preferredGameLanguage: SupportedGameLanguage? = nil
) {
self.locale = locale
if let preferredGameLanguage {
self.preferredGameLanguage = preferredGameLanguage
} else if let mappedLanguage = SupportedGameLanguage(rawValue: locale) {
self.preferredGameLanguage = mappedLanguage
} else {
self.preferredGameLanguage = .systemDefault
}
self.preferIPv6 = preferIPv6
self.preferredRegionID = preferredRegionID
self.statsHUDPosition = statsHUDPosition
Expand Down Expand Up @@ -235,6 +248,7 @@ public final class SettingsStore {
static let focusPrefetchEnabled = "guide.focus_prefetch_enabled"

static let locale = "stratix.stream.locale"
static let preferredGameLanguage = "stratix.stream.preferredGameLanguage"
static let preferIPv6 = "stratix.stream.preferIPv6"
static let preferredRegionID = "stratix.stream.preferredRegionId"
static let statsHUDPosition = "stratix.stream.statsHUDPosition"
Expand Down Expand Up @@ -522,6 +536,7 @@ public final class SettingsStore {
private func persist(_ value: StreamSettings, oldValue: StreamSettings) {
guard !isReloadingFromDefaults, value != oldValue else { return }
defaults.set(value.locale, forKey: Key.locale)
defaults.set(value.preferredGameLanguage.rawValue, forKey: Key.preferredGameLanguage)
defaults.set(value.preferIPv6, forKey: Key.preferIPv6)
defaults.set(value.preferredRegionID, forKey: Key.preferredRegionID)
defaults.set(value.statsHUDPosition, forKey: Key.statsHUDPosition)
Expand Down Expand Up @@ -600,9 +615,20 @@ public final class SettingsStore {
)
}

private nonisolated static func resolvePreferredGameLanguage(from defaults: UserDefaults) -> SupportedGameLanguage {
if let rawValue = defaults.string(forKey: Key.preferredGameLanguage),
let language = SupportedGameLanguage(rawValue: rawValue) {
return language
}

let legacyLocale = string(defaults, Key.locale, fallback: "en-US")
return SupportedGameLanguage(rawValue: legacyLocale) ?? .systemDefault
}

private nonisolated static func readStream(from defaults: UserDefaults) -> StreamSettings {
StreamSettings(
locale: string(defaults, Key.locale, fallback: "en-US"),
let preferredGameLanguage = resolvePreferredGameLanguage(from: defaults)
return StreamSettings(
locale: preferredGameLanguage.localeCode,
preferIPv6: bool(defaults, Key.preferIPv6, fallback: false),
preferredRegionID: string(defaults, Key.preferredRegionID, fallback: ""),
statsHUDPosition: string(defaults, Key.statsHUDPosition, fallback: "topRight"),
Expand All @@ -623,7 +649,8 @@ public final class SettingsStore {
colorRange: string(defaults, Key.colorRange, fallback: "Auto"),
safeAreaPercent: double(defaults, Key.safeAreaPercent, fallback: 100.0),
stereoAudio: bool(defaults, Key.stereoAudio, fallback: false),
chatChannelEnabled: bool(defaults, Key.chatChannelEnabled, fallback: false)
chatChannelEnabled: bool(defaults, Key.chatChannelEnabled, fallback: false),
preferredGameLanguage: preferredGameLanguage
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,32 @@ struct SettingsStoreTests {
#expect(suite.string(forKey: "stratix.debug.upscaling_floor_behavior") == "metalFloor")
#expect(store.diagnostics.upscalingFloorBehavior == .metalFloor)
}

@Test
func stream_preferredGameLanguageRoundTrips() {
let suiteName = "SettingsStoreTests.stream.preferredGameLanguageRoundTrips"
let suite = UserDefaults(suiteName: suiteName)!
suite.removePersistentDomain(forName: suiteName)

let store = SettingsStore(defaults: suite)
store.stream.preferredGameLanguage = .ruRU

#expect(suite.string(forKey: "stratix.stream.preferredGameLanguage") == "ru-RU")
#expect(suite.string(forKey: "stratix.stream.locale") == "ru-RU")
#expect(store.stream.preferredGameLanguage == .ruRU)
#expect(store.stream.locale == "ru-RU")
}

@Test
func stream_migratesLegacyLocaleToPreferredGameLanguage() {
let suiteName = "SettingsStoreTests.stream.migratesLegacyLocaleToPreferredGameLanguage"
let suite = UserDefaults(suiteName: suiteName)!
suite.removePersistentDomain(forName: suiteName)
suite.set("de-DE", forKey: "stratix.stream.locale")

let store = SettingsStore(defaults: suite)

#expect(store.stream.preferredGameLanguage == .deDE)
#expect(store.stream.locale == "de-DE")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// SupportedGameLanguage.swift
// StratixModels
//

import Foundation

/// Supported xCloud preferred game language values.
public enum SupportedGameLanguage: String, CaseIterable, Identifiable, Codable, Sendable {
case systemDefault = "system"
case enUS = "en-US"
case ruRU = "ru-RU"
case deDE = "de-DE"
case frFR = "fr-FR"
case esES = "es-ES"
case ptBR = "pt-BR"
case jaJP = "ja-JP"
case koKR = "ko-KR"
case zhCN = "zh-CN"
case itIT = "it-IT"
case plPL = "pl-PL"
case trTR = "tr-TR"

public var id: String { rawValue }

public var displayName: String {
switch self {
case .systemDefault: return "System Default (Account Region)"
case .enUS: return "English (United States)"
case .ruRU: return "Russian"
case .deDE: return "German"
case .frFR: return "French"
case .esES: return "Spanish (Spain)"
case .ptBR: return "Portuguese (Brazil)"
case .jaJP: return "Japanese"
case .koKR: return "Korean"
case .zhCN: return "Chinese (Simplified)"
case .itIT: return "Italian"
case .plPL: return "Polish"
case .trTR: return "Turkish"
}
}

public var localeCode: String {
self == .systemDefault ? "en-US" : rawValue
}
}
Loading