Merge pull request #348 from Inhishonor/language-supported-function

This commit is contained in:
Stypox 2025-10-08 18:06:46 +02:00 committed by GitHub
commit 68ea25e7c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 93 additions and 73 deletions

View File

@ -71,7 +71,7 @@ class LocaleManager @Inject constructor(
private fun getSentencesLocale(language: Language): LocaleUtils.LocaleResolutionResult {
return try {
LocaleUtils.resolveSupportedLocale(
LocaleUtils.resolveSupportedLocaleOrThrow(
getAvailableLocalesFromLanguage(language),
Sentences.languages
)

View File

@ -21,7 +21,6 @@ package org.stypox.dicio.io.input.vosk
import android.content.Context
import android.util.Log
import androidx.core.os.LocaleListCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -110,15 +109,7 @@ class VoskInputDevice(
private fun init(locale: Locale): VoskState {
// choose the model url based on the locale
val modelUrl = try {
val localeResolutionResult = LocaleUtils.resolveSupportedLocale(
LocaleListCompat.create(locale),
MODEL_URLS.keys
)
MODEL_URLS[localeResolutionResult.supportedLocaleString]
} catch (_: LocaleUtils.UnsupportedLocaleException) {
null
}
val modelUrl = LocaleUtils.resolveValueForSupportedLocale(locale, MODEL_URLS)
// the model url may change if the user changes app language, or in case of model updates
val modelUrlChanged = try {

View File

@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EmojiEmotions
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.core.os.LocaleListCompat
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillInfo
@ -25,16 +24,8 @@ object JokeInfo : SkillInfo("Joke") {
rememberVectorPainter(Icons.Default.EmojiEmotions)
override fun isAvailable(ctx: SkillContext): Boolean {
val hasSupportedLocale = try {
LocaleUtils.resolveSupportedLocale(
LocaleListCompat.create(ctx.locale),
JokeSkill.JOKE_SUPPORTED_LOCALES
)
true
} catch (ignored: LocaleUtils.UnsupportedLocaleException) {
false
}
return (Sentences.Joke[ctx.sentencesLanguage] != null) && hasSupportedLocale
return (Sentences.Joke[ctx.sentencesLanguage] != null) &&
LocaleUtils.isLocaleSupported(ctx.locale, JokeSkill.JOKE_SUPPORTED_LOCALES)
}
override fun build(ctx: SkillContext): Skill<*> {

View File

@ -1,6 +1,5 @@
package org.stypox.dicio.skills.joke
import androidx.core.os.LocaleListCompat
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillInfo
import org.dicio.skill.skill.SkillOutput
@ -14,15 +13,9 @@ import org.stypox.dicio.util.LocaleUtils
class JokeSkill(correspondingSkillInfo: SkillInfo, data: StandardRecognizerData<Joke>)
: StandardRecognizerSkill<Joke>(correspondingSkillInfo, data) {
override suspend fun generateOutput(ctx: SkillContext, inputData: Joke): SkillOutput {
var resolvedLocale: LocaleUtils.LocaleResolutionResult? = null
try {
resolvedLocale = LocaleUtils.resolveSupportedLocale(
LocaleListCompat.create(ctx.locale),
JOKE_SUPPORTED_LOCALES
)
} catch (ignored: LocaleUtils.UnsupportedLocaleException) {
}
val locale = resolvedLocale?.supportedLocaleString ?: ""
// we can use !! because the JokeInfo would have declared this skill unavailable
// if the current locale was not among the supported ones
val locale = LocaleUtils.resolveSupportedLocale(ctx.locale, JOKE_SUPPORTED_LOCALES)!!
if (locale == "en") {
val joke: JSONObject = ConnectionUtils.getPageJson(RANDOM_JOKE_URL_EN)

View File

@ -1,7 +1,6 @@
package org.stypox.dicio.skills.search
import androidx.core.net.toUri
import androidx.core.os.LocaleListCompat
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillInfo
import org.dicio.skill.skill.SkillOutput
@ -32,19 +31,20 @@ private val DUCK_DUCK_GO_SUPPORTED_LOCALES = listOf(
"it-it", "jp-jp", "kr-kr", "lv-lv", "lt-lt", "my-en", "mx-es", "nl-nl", "nz-en",
"no-no", "pk-en", "pe-es", "ph-en", "pl-pl", "pt-pt", "ro-ro", "ru-ru", "xa-ar",
"sg-en", "sk-sk", "sl-sl", "za-en", "es-ca", "es-es", "se-sv", "ch-de", "ch-fr",
"tw-tz", "th-en", "tr-tr", "us-en", "us-es", "ua-uk", "uk-en", "vn-en"
"tw-tz", "th-en", "tr-tr", /*"us-en",*/ "us-es", "ua-uk", "uk-en", "vn-en"
)
internal fun searchOnDuckDuckGo(ctx: SkillContext, query: String): List<SearchOutput.Data> {
// find the locale supported by DuckDuckGo that matches the user locale the most
var resolvedLocale: LocaleUtils.LocaleResolutionResult? = null
try {
resolvedLocale = LocaleUtils.resolveSupportedLocale(
LocaleListCompat.create(ctx.locale), DUCK_DUCK_GO_SUPPORTED_LOCALES
)
} catch (_: LocaleUtils.UnsupportedLocaleException) {
}
val locale = resolvedLocale?.supportedLocaleString ?: ""
val locale = LocaleUtils.resolveValueForSupportedLocale(
ctx.locale,
DUCK_DUCK_GO_SUPPORTED_LOCALES.associateBy {
// DuckDuckGo locale names have first the country and then the language, but the locale
// selection function assumes the opposite
it.split("-").reversed().joinToString(separator = "_")
}
// default to English when no locale is supported
) ?: "us-en"
// make request using headers
val html: String = ConnectionUtils.getPage(

View File

@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Language
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.core.os.LocaleListCompat
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillInfo
@ -25,16 +24,8 @@ object TranslationInfo : SkillInfo("translation") {
rememberVectorPainter(Icons.Default.Language)
override fun isAvailable(ctx: SkillContext): Boolean {
val hasSupportedLocale = try {
LocaleUtils.resolveSupportedLocale(
LocaleListCompat.create(ctx.locale),
TranslationSkill.TRANSLATE_SUPPORTED_LOCALES
)
true
} catch (ignored: LocaleUtils.UnsupportedLocaleException) {
false
}
return (Sentences.Translation[ctx.sentencesLanguage] != null) && hasSupportedLocale
return (Sentences.Translation[ctx.sentencesLanguage] != null) &&
LocaleUtils.isLocaleSupported(ctx.locale, TranslationSkill.TRANSLATE_SUPPORTED_LOCALES)
}
override fun build(ctx: SkillContext): Skill<*> {

View File

@ -1,6 +1,5 @@
package org.stypox.dicio.skills.translation
import androidx.core.os.LocaleListCompat
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillInfo
import org.dicio.skill.skill.SkillOutput
@ -18,19 +17,6 @@ import org.stypox.dicio.util.getLocaleByLanguageName
class TranslationSkill(correspondingSkillInfo: SkillInfo, data: StandardRecognizerData<Translation>)
: StandardRecognizerSkill<Translation>(correspondingSkillInfo, data) {
private fun determineCurrentLocale(ctx: SkillContext): String {
var resolvedLocale: LocaleUtils.LocaleResolutionResult? = null
try {
resolvedLocale = LocaleUtils.resolveSupportedLocale(
LocaleListCompat.create(ctx.locale),
TRANSLATE_SUPPORTED_LOCALES
)
} catch (_: LocaleUtils.UnsupportedLocaleException) {
}
val locale = resolvedLocale?.supportedLocaleString ?: ""
return locale
}
private fun findCodeInSupportedLocales(language: LocaleAndTranslation): String? {
return TRANSLATE_SUPPORTED_LOCALES.firstOrNull { it.lowercase() == language.locale }
?: TRANSLATE_SUPPORTED_LOCALES.firstOrNull { language.locale.startsWith(it.lowercase()) }
@ -40,7 +26,12 @@ class TranslationSkill(correspondingSkillInfo: SkillInfo, data: StandardRecogniz
// TODO: Add more servers in like Libre Translate or DeepL
override suspend fun generateOutput(ctx: SkillContext, inputData: Translation): SkillOutput {
val input = when (inputData) { is Translate -> inputData }
val currentLocale = determineCurrentLocale(ctx)
// we can use !! because the TranslationInfo would have declared this skill unavailable
// if the current locale was not among the supported ones
val currentLocale = LocaleUtils.resolveSupportedLocale(
ctx.locale, TRANSLATE_SUPPORTED_LOCALES)!!
// we can use !! here because `CldrLanguages` is generated by the `unicode-cldr-plugin`
// based on the locales supported by the app, and `ctx.locale` is surely such a locale
val cldr = CldrLanguages[ctx.locale.language]!!
// extract the query from the input

View File

@ -4,6 +4,7 @@ import androidx.core.os.LocaleListCompat
import java.util.Locale
object LocaleUtils {
/**
* Basically implements Android locale resolution (not sure if exactly the same, probably,
* though), so it tries, in this order:<br></br>
@ -23,7 +24,7 @@ object LocaleUtils {
* @throws UnsupportedLocaleException if the locale resolution failed.
*/
@Throws(UnsupportedLocaleException::class)
fun resolveSupportedLocale(
fun resolveSupportedLocaleOrThrow(
availableLocales: LocaleListCompat,
supportedLocales: Collection<String>
): LocaleResolutionResult {
@ -48,7 +49,7 @@ object LocaleUtils {
}
/**
* @see resolveSupportedLocale
* @see resolveSupportedLocaleOrThrow
*/
@Throws(UnsupportedLocaleException::class)
fun resolveLocaleString(
@ -95,12 +96,74 @@ object LocaleUtils {
.toTypedArray()
return if (languageCountryArr.size == 1) {
Locale(languageCountryArr[0])
Locale.Builder()
.setLanguage(languageCountryArr[0])
.build()
} else {
Locale(languageCountryArr[0], languageCountryArr[1])
Locale.Builder()
.setLanguage(languageCountryArr[0])
.setRegion(languageCountryArr[1])
.build()
}
}
/**
* Like [resolveSupportedLocaleOrThrow], but returns null instead of throwing an exception.
*/
fun resolveSupportedLocale(
availableLocales: LocaleListCompat,
supportedLocales: Collection<String>
): LocaleResolutionResult? {
return try {
resolveSupportedLocaleOrThrow(availableLocales, supportedLocales)
} catch (_: UnsupportedLocaleException) {
null
}
}
/**
* Uses [resolveSupportedLocaleOrThrow] to find a supported locale string in [supportedLocales]
* matching [currentLocale], and returns it. This is NOT meant to be used for locale resolution
* when the app starts, but only to select the correct item from a list using the app's current
* locale (that has already been determined, hence the parameter name [currentLocale]).
*/
fun resolveSupportedLocale(
currentLocale: Locale,
supportedLocales: Collection<String>
): String? {
return resolveSupportedLocale(
availableLocales = LocaleListCompat.create(currentLocale),
supportedLocales = supportedLocales
)?.supportedLocaleString
}
/**
* Uses [resolveSupportedLocale] to find a supported locale string matching [currentLocale] in
* the keys of [supportedLocalesAndValues], and returns the corresponding value.
*/
fun <T> resolveValueForSupportedLocale(
currentLocale: Locale,
supportedLocalesAndValues: Map<String, T>
): T? {
return resolveSupportedLocale(
availableLocales = LocaleListCompat.create(currentLocale),
supportedLocales = supportedLocalesAndValues.keys
)?.let {
supportedLocalesAndValues[it.supportedLocaleString]
}
}
/**
* Returns whether the [currentLocale] matches with any of the [supportedLocales] using
* [resolveSupportedLocale].
*/
fun isLocaleSupported(currentLocale: Locale, supportedLocales: List<String>): Boolean {
return resolveSupportedLocale(
LocaleListCompat.create(currentLocale),
supportedLocales
) != null
}
class UnsupportedLocaleException : Exception {
constructor(locale: Locale) : super("Unsupported locale: $locale")
constructor() : super("No locales provided")

View File

@ -33,7 +33,7 @@ private fun assertLocaleNotFound(locale: String, vararg supportedLocales: String
val localeString: String
try {
localeString = getLocaleString(locale, *supportedLocales)
} catch (e: UnsupportedLocaleException) {
} catch (_: UnsupportedLocaleException) {
return
}
error("The locale \"$locale\" should not have been found: $localeString")