Skip to content

Commit

Permalink
feat: display localized product name and description
Browse files Browse the repository at this point in the history
When loading product data from indexV1, try to follow the user's locale
setting when possible so the information will be localized.

The `getBestLocale()` helper method was reused from F-droid source
code (part of `LocaleChooser.kt`). Other part wrote by myself.

Resolve: Droid-ify#337
  • Loading branch information
BLumia committed Jun 11, 2023
1 parent 2cea3d1 commit 09577b0
Showing 1 changed file with 56 additions and 5 deletions.
61 changes: 56 additions & 5 deletions app/src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.looker.droidify.index

import android.content.res.Resources
import androidx.core.os.ConfigurationCompat.getLocales
import androidx.core.os.LocaleListCompat
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.looker.core.common.extension.Json
Expand Down Expand Up @@ -46,20 +49,68 @@ object IndexV1Parser {
return this[key]?.let { callback(key, it) }
}

/**
* Gets the best localization for the given [localeList]
* from collections.
*/
private fun <T> Map<String, T>?.getBestLocale(localeList: LocaleListCompat): T? {
if (isNullOrEmpty()) return null
val firstMatch = localeList.getFirstMatch(keys.toTypedArray()) ?: return null
val tag = firstMatch.toLanguageTag()
// try first matched tag first (usually has region tag, e.g. de-DE)
return get(tag) ?: run {
// split away stuff like script and try language and region only
val langCountryTag = "${firstMatch.language}-${firstMatch.country}"
getOrStartsWith(langCountryTag) ?: run {
// split away region tag and try language only
val langTag = firstMatch.language
// try language, then English and then just take the first of the list
getOrStartsWith(langTag) ?: get("en-US") ?: get("en") ?: values.first()
}
}
}

/**
* Returns the value from the map with the given key or if that key is not contained in the map,
* tries the first map key that starts with the given key.
* If nothing matches, null is returned.
*
* This is useful when looking for a language tag like `fr_CH` and falling back to `fr`
* in a map that has `fr_FR` as a key.
*/
private fun <T> Map<String, T>.getOrStartsWith(s: String): T? = get(s) ?: run {
entries.forEach { (key, value) ->
if (key.startsWith(s)) return value
}
return null
}

private fun <T> Map<String, Localized>.find(callback: (String, Localized) -> T?): T? {
return getAndCall("en-US", callback) ?: getAndCall("en_US", callback) ?: getAndCall(
"en",
callback
)
}

private fun <T> Map<String, Localized>.findLocalized(callback: (Localized) -> T?): T? {
return getBestLocale(getLocales(Resources.getSystem().configuration))?.let { callback(it) }
}

private fun Map<String, Localized>.findString(
fallback: String,
callback: (Localized) -> String,
): String {
return (find { _, localized -> callback(localized).nullIfEmpty() } ?: fallback).trim()
}

private fun Map<String, Localized>.findLocalizedString(
fallback: String,
callback: (Localized) -> String,
): String {
// @BLumia: it's possible a key of a certain Localized object is empty, so we still need a fallback
return (findLocalized { localized -> callback(localized).trim().nullIfEmpty() } ?: findString(fallback, callback)).trim()
}

internal object DonateComparator : Comparator<Product.Donate> {
private val classes = listOf(
Product.Donate.Regular::class,
Expand Down Expand Up @@ -220,12 +271,12 @@ object IndexV1Parser {
else -> skipChildren()
}
}
val name = localizedMap.findString(nameFallback) { it.name }
val summary = localizedMap.findString(summaryFallback) { it.summary }
val name = localizedMap.findLocalizedString(nameFallback) { it.name }
val summary = localizedMap.findLocalizedString(summaryFallback) { it.summary }
val description =
localizedMap.findString(descriptionFallback) { it.description }.replace("\n", "<br/>")
val whatsNew = localizedMap.findString("") { it.whatsNew }.replace("\n", "<br/>")
val metadataIcon = localizedMap.findString("") { it.metadataIcon }.ifEmpty {
localizedMap.findLocalizedString(descriptionFallback) { it.description }.replace("\n", "<br/>")
val whatsNew = localizedMap.findLocalizedString("") { it.whatsNew }.replace("\n", "<br/>")
val metadataIcon = localizedMap.findLocalizedString("") { it.metadataIcon }.ifEmpty {
localizedMap.firstNotNullOfOrNull { it.value.metadataIcon }.orEmpty()
}
val screenshotPairs =
Expand Down

0 comments on commit 09577b0

Please sign in to comment.