set system language as default language (#386)

This commit is contained in:
AmirHossein Abdolmotallebi 2025-01-27 08:56:14 +03:30 committed by GitHub
parent ddec1f383f
commit 30e70f8b1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 24 deletions

View File

@ -318,26 +318,36 @@ fun themeConfig(
fun languageConfig( fun languageConfig(
languageManager: LanguageManager, languageManager: LanguageManager,
scope: CoroutineScope, scope: CoroutineScope,
): EnumConfigurable<LanguageInfo> { ): EnumConfigurable<LanguageInfo?> {
val currentLanguageName = languageManager.selectedLanguage val currentLanguageName = languageManager.selectedLanguageInStorage
val allLanguages = languageManager.languageList val allLanguages = languageManager.languageList.value
return EnumConfigurable( return EnumConfigurable(
title = Res.string.settings_language.asStringSource(), title = Res.string.settings_language.asStringSource(),
description = "".asStringSource(), description = "".asStringSource(),
backedBy = createMutableStateFlowFromStateFlow( backedBy = createMutableStateFlowFromStateFlow(
flow = currentLanguageName.mapStateFlow { l -> flow = currentLanguageName.mapStateFlow { language ->
allLanguages.value.find { language?.let {
it.toLocaleString() == l allLanguages.find {
} ?: LanguageManager.DefaultLanguageInfo it.toLocaleString() == language
}
}
}, },
updater = { languageInfo -> updater = { languageInfo ->
languageManager.selectLanguage(languageInfo) languageManager.selectLanguage(languageInfo)
}, },
scope = scope, scope = scope,
), ),
possibleValues = allLanguages.value, possibleValues = listOf(null).plus(allLanguages),
describe = { describe = {
it.nativeName.asStringSource() val isAuto = it == null
val language = it ?: languageManager.systemLanguageOrDefault
val languageName = language.nativeName
if (isAuto) {
// always use english here!
"System ($languageName)".asStringSource()
} else {
languageName.asStringSource()
}
}, },
) )
} }

View File

@ -14,7 +14,7 @@ import java.io.File
@Serializable @Serializable
data class AppSettingsModel( data class AppSettingsModel(
val theme: String = "dark", val theme: String = "dark",
val language: String = "en", val language: String? = null,
val uiScale: Float? = null, val uiScale: Float? = null,
val mergeTopBarWithTitleBar: Boolean = false, val mergeTopBarWithTitleBar: Boolean = false,
val threadCount: Int = 8, val threadCount: Int = 8,
@ -95,7 +95,7 @@ data class AppSettingsModel(
override fun set(source: MapConfig, focus: AppSettingsModel): MapConfig { override fun set(source: MapConfig, focus: AppSettingsModel): MapConfig {
return source.apply { return source.apply {
put(Keys.theme, focus.theme) put(Keys.theme, focus.theme)
put(Keys.language, focus.language) putNullable(Keys.language, focus.language)
putNullable(Keys.uiScale, focus.uiScale) putNullable(Keys.uiScale, focus.uiScale)
put(Keys.mergeTopBarWithTitleBar, focus.mergeTopBarWithTitleBar) put(Keys.mergeTopBarWithTitleBar, focus.mergeTopBarWithTitleBar)
put(Keys.threadCount, focus.threadCount) put(Keys.threadCount, focus.threadCount)
@ -127,6 +127,15 @@ private val uiScaleLens: Lens<AppSettingsModel, Float?>
s.copy(uiScale = f) s.copy(uiScale = f)
} }
) )
private val languageLens: Lens<AppSettingsModel, String?>
get() = Lens(
get = {
it.language
},
set = { s, f ->
s.copy(language = f)
}
)
class AppSettingsStorage( class AppSettingsStorage(
settings: DataStore<MapConfig>, settings: DataStore<MapConfig>,
@ -134,7 +143,7 @@ class AppSettingsStorage(
ConfigBaseSettingsByMapConfig<AppSettingsModel>(settings, AppSettingsModel.ConfigLens), ConfigBaseSettingsByMapConfig<AppSettingsModel>(settings, AppSettingsModel.ConfigLens),
LanguageStorage { LanguageStorage {
var theme = from(AppSettingsModel.theme) var theme = from(AppSettingsModel.theme)
override val selectedLanguage = from(AppSettingsModel.language) override val selectedLanguage = from(languageLens)
var uiScale = from(uiScaleLens) var uiScale = from(uiScaleLens)
var mergeTopBarWithTitleBar = from(AppSettingsModel.mergeTopBarWithTitleBar) var mergeTopBarWithTitleBar = from(AppSettingsModel.mergeTopBarWithTitleBar)
val threadCount = from(AppSettingsModel.threadCount) val threadCount = from(AppSettingsModel.threadCount)
@ -152,4 +161,4 @@ class AppSettingsStorage(
val browserIntegrationPort = from(AppSettingsModel.browserIntegrationPort) val browserIntegrationPort = from(AppSettingsModel.browserIntegrationPort)
val trackDeletedFilesOnDisk = from(AppSettingsModel.trackDeletedFilesOnDisk) val trackDeletedFilesOnDisk = from(AppSettingsModel.trackDeletedFilesOnDisk)
val useBitsForSpeed = from(AppSettingsModel.useBitsForSpeed) val useBitsForSpeed = from(AppSettingsModel.useBitsForSpeed)
} }

View File

@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
import ir.amirab.util.compose.contants.FILE_PROTOCOL import ir.amirab.util.compose.contants.FILE_PROTOCOL
import ir.amirab.util.compose.contants.RESOURCE_PROTOCOL import ir.amirab.util.compose.contants.RESOURCE_PROTOCOL
import ir.amirab.util.flow.mapStateFlow import ir.amirab.util.flow.mapStateFlow
import ir.amirab.util.flow.mapTwoWayStateFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import okio.FileSystem import okio.FileSystem
@ -11,14 +12,24 @@ import okio.Path.Companion.toPath
import okio.buffer import okio.buffer
import java.io.InputStream import java.io.InputStream
import java.net.URI import java.net.URI
import java.util.Properties import java.util.*
class LanguageManager( class LanguageManager(
private val storage: LanguageStorage, private val storage: LanguageStorage,
) { ) {
private val _languageList: MutableStateFlow<List<LanguageInfo>> = MutableStateFlow(emptyList()) private val _languageList: MutableStateFlow<List<LanguageInfo>> = MutableStateFlow(emptyList())
val languageList = _languageList.asStateFlow() val languageList = _languageList.asStateFlow()
val selectedLanguage = storage.selectedLanguage val systemLanguageOrDefault: LanguageInfo by lazy {
getSystemLanguageIfWeCanUse()
}
val selectedLanguageInStorage = storage.selectedLanguage
val selectedLanguage = storage.selectedLanguage.mapStateFlow {
it ?: systemLanguageOrDefault.toLocaleString()
}
// val selectedLanguageInfo = selectedLanguage.mapStateFlow {
// bestLanguageInfo(it)
// }
val isRtl = selectedLanguage.mapStateFlow { selectedLanguage -> val isRtl = selectedLanguage.mapStateFlow { selectedLanguage ->
rtlLanguages.any { selectedLanguage.startsWith(it) } rtlLanguages.any { selectedLanguage.startsWith(it) }
} }
@ -28,11 +39,11 @@ class LanguageManager(
instance = this instance = this
} }
fun selectLanguage(languageInfo: LanguageInfo) { fun selectLanguage(languageInfo: LanguageInfo?) {
// ensure that language info is in the list! // ensure that language info is in the list!
// val languageInfo = languageList.value.find { it == languageInfo } // val languageInfo = languageList.value.find { it == languageInfo }
// selectedLanguage.value = (languageInfo ?: DefaultLanguageInfo).toLocaleString() // selectedLanguage.value = (languageInfo ?: DefaultLanguageInfo).toLocaleString()
selectedLanguage.value = languageInfo.toLocaleString() selectedLanguageInStorage.value = languageInfo?.toLocaleString()
} }
fun getMessage(key: String): String { fun getMessage(key: String): String {
@ -42,7 +53,7 @@ class LanguageManager(
} }
private fun getRequestedLanguage(): String { private fun getRequestedLanguage(): String {
return selectedLanguage.value return selectedLanguage.value ?: systemLanguageOrDefault.toLocaleString()
} }
@Volatile @Volatile
@ -74,6 +85,10 @@ class LanguageManager(
} }
} }
/**
* Find the best language info for the given locale.
* the returned language is guaranteed to be available. (at least [DefaultLanguageInfo])
*/
private fun bestLanguageInfo(locale: String): LanguageInfo { private fun bestLanguageInfo(locale: String): LanguageInfo {
return languageList.value.find { return languageList.value.find {
it.toLocaleString() == locale it.toLocaleString() == locale
@ -122,6 +137,11 @@ class LanguageManager(
} }
} }
private fun getSystemLanguageIfWeCanUse(): LanguageInfo {
val systemLocale = getSystemLocale().toString()
return bestLanguageInfo(systemLocale)
}
companion object { companion object {
lateinit var instance: LanguageManager lateinit var instance: LanguageManager
private const val LOCALES_PATH = "/com/abdownloadmanager/resources/locales" private const val LOCALES_PATH = "/com/abdownloadmanager/resources/locales"
@ -130,10 +150,8 @@ class LanguageManager(
languageCode = "en", languageCode = "en",
countryCode = "US", countryCode = "US",
) )
LanguageInfo( locale.toLanguageInfo(
locale = locale, path = "$RESOURCE_PROTOCOL:$LOCALES_PATH/${locale}.properties",
nativeName = "English",
path = URI("$RESOURCE_PROTOCOL:$LOCALES_PATH/${locale}.properties")
) )
} }
@ -204,3 +222,11 @@ data class LanguageInfo(
return locale.toString() return locale.toString()
} }
} }
private fun getSystemLocale(): MyLocale {
val javaSystemLocale = Locale.getDefault(Locale.Category.DISPLAY)
return MyLocale(
languageCode = javaSystemLocale.language,
countryCode = javaSystemLocale.country,
)
}

View File

@ -3,5 +3,6 @@ package ir.amirab.util.compose.localizationmanager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
interface LanguageStorage { interface LanguageStorage {
val selectedLanguage: MutableStateFlow<String> // null means auto
} val selectedLanguage: MutableStateFlow<String?>
}