fix: localising desktop

This commit is contained in:
tapframe 2026-05-13 00:03:01 +05:30
parent b30ad31b37
commit b1b5a82fce
5 changed files with 56 additions and 7 deletions

View file

@ -100,7 +100,9 @@ actual object ThemeSettingsStorage {
actual fun replaceFromSyncPayload(payload: JsonObject) {
preferences?.edit()?.apply {
profileScopedSyncKeys.forEach { remove(ProfileScopedKey.of(it)) }
globalSyncKeys.forEach { remove(it) }
globalSyncKeys
.filter(payload::containsKey)
.forEach { remove(it) }
}?.apply()
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)

View file

@ -55,6 +55,7 @@ import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.put
private const val PUSH_DEBOUNCE_MS = 1500L
private const val APP_LANGUAGE_SYNC_KEY = "selected_app_language"
object ProfileSettingsSync {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@ -74,6 +75,9 @@ object ProfileSettingsSync {
@Volatile
private var skipNextPushSignature: String? = null
@Volatile
private var pendingLocalAppLanguageCode: String? = null
private var observeJob: Job? = null
fun startObserving() {
@ -121,7 +125,7 @@ object ProfileSettingsSync {
return@withLock false
}
applyRemoteBlob(remoteBlob)
applyRemoteBlob(remoteBlob.withPendingLocalAppLanguage())
skipNextPushSignature = currentObservedStateSignature()
} finally {
isApplyingRemoteBlob = false
@ -140,21 +144,31 @@ object ProfileSettingsSync {
suspend fun pushCurrentProfileToRemote() {
ensureRepositoriesLoaded()
val authState = AuthRepository.state.value
if (authState !is AuthState.Authenticated || authState.isAnonymous) return
syncMutex.withLock {
runCatching {
pushToRemoteLocked(ProfileRepository.activeProfileId, exportSettingsBlob())
if (pendingLocalAppLanguageCode == ThemeSettingsRepository.selectedAppLanguage.value.code) {
pendingLocalAppLanguageCode = null
}
}.onFailure { error ->
log.e(error) { "pushCurrentProfileToRemote() — FAILED" }
}
}
}
fun markAppLanguageChanged() {
pendingLocalAppLanguageCode = ThemeSettingsRepository.selectedAppLanguage.value.code
}
@OptIn(FlowPreview::class)
private fun observeLocalChangesAndPush() {
val signatureFlows = listOf(
ThemeSettingsRepository.selectedTheme.map { "theme" },
ThemeSettingsRepository.amoledEnabled.map { "amoled" },
ThemeSettingsRepository.liquidGlassNativeTabBarEnabled.map { "liquid_glass_tab_bar" },
ThemeSettingsRepository.selectedAppLanguage.map { "app_language" },
PosterCardStyleRepository.uiState.map { "poster_card_style" },
PlayerSettingsRepository.uiState.map { "player" },
TmdbSettingsRepository.uiState.map { "tmdb" },
@ -275,6 +289,7 @@ object ProfileSettingsSync {
"theme=${ThemeSettingsRepository.selectedTheme.value.name}",
"amoled=${ThemeSettingsRepository.amoledEnabled.value}",
"liquid_glass_tab_bar=${ThemeSettingsRepository.liquidGlassNativeTabBarEnabled.value}",
"app_language=${ThemeSettingsRepository.selectedAppLanguage.value.code}",
"poster_card_style=${PosterCardStyleRepository.uiState.value}",
"player=${PlayerSettingsRepository.uiState.value}",
"tmdb=${TmdbSettingsRepository.uiState.value}",
@ -286,6 +301,18 @@ object ProfileSettingsSync {
"trakt_comments=${TraktCommentsSettings.enabled.value}",
"episode_release_alerts=${EpisodeReleaseNotificationsRepository.uiState.value.isEnabled}",
).joinToString(separator = "||")
private fun MobileProfileSettingsBlob.withPendingLocalAppLanguage(): MobileProfileSettingsBlob {
val languageCode = pendingLocalAppLanguageCode ?: return this
return copy(
features = features.copy(
themeSettings = buildJsonObject {
features.themeSettings.forEach { (key, value) -> put(key, value) }
put(APP_LANGUAGE_SYNC_KEY, encodeSyncString(languageCode))
},
),
)
}
}
@Serializable

View file

@ -47,6 +47,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nuvio.app.core.sync.ProfileSettingsSync
import com.nuvio.app.core.ui.AppTheme
import com.nuvio.app.core.ui.LocalNuvioBottomNavigationOverlayPadding
import com.nuvio.app.core.ui.NuvioScreen
@ -119,6 +120,16 @@ fun SettingsScreen(
}.collectAsStateWithLifecycle()
val liquidGlassNativeTabBarSupported = remember { isLiquidGlassNativeTabBarSupported() }
val selectedAppLanguage by remember { ThemeSettingsRepository.selectedAppLanguage }.collectAsStateWithLifecycle()
val settingsSyncScope = rememberCoroutineScope()
val onAppLanguageSelected: (AppLanguage) -> Unit = remember(settingsSyncScope) {
{ language ->
ThemeSettingsRepository.setAppLanguage(language)
ProfileSettingsSync.markAppLanguageChanged()
settingsSyncScope.launch {
ProfileSettingsSync.pushCurrentProfileToRemote()
}
}
}
val tmdbSettings by remember {
TmdbSettingsRepository.ensureLoaded()
TmdbSettingsRepository.uiState
@ -228,7 +239,7 @@ fun SettingsScreen(
liquidGlassNativeTabBarEnabled = liquidGlassNativeTabBarEnabled,
onLiquidGlassNativeTabBarToggle = ThemeSettingsRepository::setLiquidGlassNativeTabBar,
selectedAppLanguage = selectedAppLanguage,
onAppLanguageSelected = ThemeSettingsRepository::setAppLanguage,
onAppLanguageSelected = onAppLanguageSelected,
episodeReleaseNotificationsUiState = episodeReleaseNotificationsUiState,
tmdbSettings = tmdbSettings,
mdbListSettings = mdbListSettings,
@ -275,7 +286,7 @@ fun SettingsScreen(
liquidGlassNativeTabBarEnabled = liquidGlassNativeTabBarEnabled,
onLiquidGlassNativeTabBarToggle = ThemeSettingsRepository::setLiquidGlassNativeTabBar,
selectedAppLanguage = selectedAppLanguage,
onAppLanguageSelected = ThemeSettingsRepository::setAppLanguage,
onAppLanguageSelected = onAppLanguageSelected,
episodeReleaseNotificationsUiState = episodeReleaseNotificationsUiState,
tmdbSettings = tmdbSettings,
mdbListSettings = mdbListSettings,

View file

@ -18,6 +18,7 @@ import nuvio.composeapp.generated.resources.mdblist_logo
import nuvio.composeapp.generated.resources.rating_tmdb
import nuvio.composeapp.generated.resources.trakt_tv_favicon
import org.jetbrains.compose.resources.painterResource
import java.util.Locale
internal actual object ThemeSettingsStorage {
private const val preferencesName = "nuvio_theme_settings"
@ -65,7 +66,13 @@ internal actual object ThemeSettingsStorage {
DesktopPreferences.putString(preferencesName, selectedAppLanguageKey, languageCode)
}
actual fun applySelectedAppLanguage(languageCode: String) = Unit
actual fun applySelectedAppLanguage(languageCode: String) {
val normalizedCode = languageCode
.trim()
.takeIf { it.isNotBlank() }
?: AppLanguage.ENGLISH.code
Locale.setDefault(Locale.forLanguageTag(normalizedCode))
}
actual fun exportToSyncPayload(): JsonObject = buildJsonObject {
loadSelectedTheme()?.let { put(selectedThemeKey, encodeSyncString(it)) }
@ -76,7 +83,9 @@ internal actual object ThemeSettingsStorage {
actual fun replaceFromSyncPayload(payload: JsonObject) {
profileScopedSyncKeys.forEach { DesktopPreferences.remove(preferencesName, ProfileScopedKey.of(it)) }
globalSyncKeys.forEach { DesktopPreferences.remove(preferencesName, it) }
globalSyncKeys
.filter(payload::containsKey)
.forEach { DesktopPreferences.remove(preferencesName, it) }
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)
payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled)

View file

@ -95,7 +95,7 @@ actual object ThemeSettingsStorage {
profileScopedSyncKeys.forEach { key ->
NSUserDefaults.standardUserDefaults.removeObjectForKey(ProfileScopedKey.of(key))
}
globalSyncKeys.forEach { key ->
globalSyncKeys.filter(payload::containsKey).forEach { key ->
NSUserDefaults.standardUserDefaults.removeObjectForKey(key)
}