diff --git a/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/asset/TranslatedDisplayableAssetExt.kt b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/asset/TranslatedDisplayableAssetExt.kt new file mode 100644 index 0000000..6720774 --- /dev/null +++ b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/asset/TranslatedDisplayableAssetExt.kt @@ -0,0 +1,43 @@ +package de.sipgate.dachlatten.compose.asset + +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import de.sipgate.dachlatten.asset.AssetUrl +import de.sipgate.dachlatten.asset.DisplayableAsset +import de.sipgate.dachlatten.asset.TranslatedDisplayableAsset +import de.sipgate.dachlatten.compose.text.get +import java.util.Locale + +@Composable +fun TranslatedDisplayableAsset.resolve( + fallbackLocale: Locale? = null, + useDarkMode: Boolean = isSystemInDarkTheme() +): AssetUrl { + val configuration = LocalConfiguration.current + val displayableAsset = configuration.getAssetsForLocales(this, fallbackLocale) + ?: throw Resources.NotFoundException("Could not find multilang string") + + return displayableAsset.resolve(useDarkMode) +} + +private fun Configuration.getAssetsForLocales( + translations: TranslatedDisplayableAsset, + fallbackLocale: Locale? +): DisplayableAsset? { + val resolvedLocale = resolveLocale(translations.keys) + return resolvedLocale?.let { locale -> translations[locale] } + ?: fallbackLocale?.let { fallback -> translations[fallback] } +} + +private fun Configuration.resolveLocale(supportedLanguages: Set): Locale? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + locales.getFirstMatch(supportedLanguages.toTypedArray()) + } else { + @Suppress("DEPRECATION") + locale + } +} diff --git a/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/text/UiTextExt.kt b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/text/UiTextExt.kt index a28a25e..8452b69 100644 --- a/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/text/UiTextExt.kt +++ b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/text/UiTextExt.kt @@ -28,7 +28,7 @@ private fun Configuration.getStringForLocales(translations: TranslatedText, fall ?: fallbackLocale?.let { fallback -> translations[fallback] } } -operator fun TranslatedText.get(locale: Locale): String? = +operator fun Map.get(locale: Locale): T? = this[locale.language.lowercase()] ?: this[locale.language.uppercase()] diff --git a/dachlatten-compose/src/test/kotlin/de/sipgate/dachlatten/compose/asset/TranslatedDisplayableAssetTest.kt b/dachlatten-compose/src/test/kotlin/de/sipgate/dachlatten/compose/asset/TranslatedDisplayableAssetTest.kt new file mode 100644 index 0000000..d553b9c --- /dev/null +++ b/dachlatten-compose/src/test/kotlin/de/sipgate/dachlatten/compose/asset/TranslatedDisplayableAssetTest.kt @@ -0,0 +1,112 @@ +package de.sipgate.dachlatten.compose.asset + +import androidx.compose.ui.test.junit4.createComposeRule +import de.sipgate.dachlatten.asset.DisplayableAsset +import de.sipgate.dachlatten.asset.TranslatedDisplayableAsset +import java.util.Locale +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +class TranslatedDisplayableAssetTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private val englishDarkModeVariantUrl = "dark-mode-variant-of-asset-en" + private val germanDarkModeVariantUrl = "dark-mode-variant-of-asset-de" + private val englishLightModeVariantUrl = "light-mode-variant-of-asset-en" + private val germanLightModeVariantUrl = "light-mode-variant-of-asset-de" + + private val translatedImage: TranslatedDisplayableAsset = mapOf( + "en" to DisplayableAsset( + dark = englishDarkModeVariantUrl, + light = englishLightModeVariantUrl, + ), + "de" to DisplayableAsset( + dark = germanDarkModeVariantUrl, + light = germanLightModeVariantUrl, + ) + ) + + private val englishLightOnly = mapOf( + "en" to DisplayableAsset( + light = englishLightModeVariantUrl, + ) + ) + + @Test + @Config(qualifiers = "de-night") + fun urlResolvesToGermanDarkVariantInDarkModeAndDeviceSetToGerman() { + expectResolvedComposeUrl(germanDarkModeVariantUrl, translatedImage) + } + + @Test + @Config(qualifiers = "en-night") + fun urlResolvesToEnglishDarkVariantInDarkModeAndDeviceSetToEnglish() { + expectResolvedComposeUrl(englishDarkModeVariantUrl, translatedImage) + } + + @Test + @Config(qualifiers = "en-notnight") + fun urlResolvesToEnglishLightVariantInLightModeAndDeviceSetToEnglish() { + expectResolvedComposeUrl(englishLightModeVariantUrl, translatedImage) + } + + @Test + @Config(qualifiers = "en-notnight") + fun urlResolvesToEnglishDarkVariantInLightModeAndDeviceSetToEnglishAndDarkModeExplicitlyRequested() { + expectResolvedComposeUrl(englishDarkModeVariantUrl, translatedImage, useDarkMode = true) + } + + @Test + @Config(qualifiers = "en-night") + fun urlResolvesToEnglishLightVariantInDarkModeAndDeviceSetToEnglishWhenDarkModeResourcesAreNotAvailable() { + expectResolvedComposeUrl(englishLightModeVariantUrl, englishLightOnly) + } + + @Test + @Config(qualifiers = "en-night") + fun urlResolvesToEnglishLightVariantInDarkModeAndDeviceSetToEnglishWhenDarkModeResourcesAreNotAvailableAndDarkModeExplicitlyRequested() { + expectResolvedComposeUrl(englishLightModeVariantUrl, englishLightOnly, useDarkMode = true) + } + + @Test + @Config(qualifiers = "fr-notnight") + fun urlResolvesToGermanLightVariantInLightModeAndDeviceSetToEnglishWhenTheResourcesAreNotAvailable() { + val germanLightVariantOnly = mapOf( + "de" to DisplayableAsset( + light = germanLightModeVariantUrl + ) + ) + expectResolvedComposeUrl(germanLightModeVariantUrl, germanLightVariantOnly, fallbackLocale = Locale.GERMAN) + } + + @Test + @Config(qualifiers = "fr-night") + fun urlResolvesToGermanLightVariantInDarkModeAndDeviceSetToEnglishWhenTheResourcesAreNotAvailable() { + val germanLightVariantOnly = mapOf( + "de" to DisplayableAsset( + light = germanLightModeVariantUrl + ) + ) + expectResolvedComposeUrl(germanLightModeVariantUrl, germanLightVariantOnly, fallbackLocale = Locale.GERMAN) + } + + private fun expectResolvedComposeUrl(expected: String, image: TranslatedDisplayableAsset, + fallbackLocale: Locale? = null, + useDarkMode: Boolean? = null) { + composeTestRule.setContent { + val resolvedString = if (useDarkMode != null) { + image.resolve(useDarkMode = useDarkMode, fallbackLocale = fallbackLocale) + } else { + image.resolve(fallbackLocale = fallbackLocale) + } + assertEquals(expected, resolvedString) + } + } +} diff --git a/dachlatten-primitives/src/main/kotlin/de/sipgate/dachlatten/asset/TranslatedDisplayableAsset.kt b/dachlatten-primitives/src/main/kotlin/de/sipgate/dachlatten/asset/TranslatedDisplayableAsset.kt new file mode 100644 index 0000000..fb64f46 --- /dev/null +++ b/dachlatten-primitives/src/main/kotlin/de/sipgate/dachlatten/asset/TranslatedDisplayableAsset.kt @@ -0,0 +1,3 @@ +package de.sipgate.dachlatten.asset + +typealias TranslatedDisplayableAsset = Map