feat(i18n): add languages to in-app language picker (#571)
* feat(i18n): add languages to in-app language picker * fix(i18n): locale system settings not working for Android 13 * feat(i18n): show selected language at settings page * fix(ci): ignore ExtraTranslation for linter * feat(i18n): add fallback in in-app language picker for A13+ * chore: clean up
This commit is contained in:
parent
77fd5b9746
commit
2ead25a88d
@ -61,7 +61,7 @@ android {
|
||||
}
|
||||
}
|
||||
lint {
|
||||
disable += "MissingTranslation"
|
||||
disable.addAll("MissingTranslation", "ExtraTranslation")
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
@ -21,6 +21,7 @@ import me.ash.reader.infrastructure.preference.SettingsProvider
|
||||
import me.ash.reader.ui.ext.languages
|
||||
import me.ash.reader.ui.page.common.HomeEntry
|
||||
import java.lang.reflect.Field
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@ -39,14 +40,16 @@ class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_NO_LIMITS)
|
||||
}
|
||||
Log.i("RLog", "onCreate: ${ProfileInstallerInitializer().create(this)}")
|
||||
|
||||
// Set the language
|
||||
LanguagesPreference.fromValue(languages).let {
|
||||
LanguagesPreference.setLocale(it)
|
||||
if (Build.VERSION.SDK_INT < 33) {
|
||||
LanguagesPreference.fromValue(languages).let {
|
||||
LanguagesPreference.setLocale(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/Ashinch/ReadYou/issues/312: increase cursor window size
|
||||
|
@ -2,6 +2,8 @@ package me.ash.reader.infrastructure.preference
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -14,128 +16,191 @@ import me.ash.reader.ui.ext.put
|
||||
import java.util.*
|
||||
|
||||
sealed class LanguagesPreference(val value: Int) : Preference() {
|
||||
object UseDeviceLanguages : LanguagesPreference(0)
|
||||
object English : LanguagesPreference(1)
|
||||
object ChineseSimplified : LanguagesPreference(2)
|
||||
object German : LanguagesPreference(3)
|
||||
object French : LanguagesPreference(4)
|
||||
object Czech : LanguagesPreference(5)
|
||||
object Italian : LanguagesPreference(6)
|
||||
object Hindi : LanguagesPreference(7)
|
||||
object Spanish : LanguagesPreference(8)
|
||||
object Polish : LanguagesPreference(9)
|
||||
object Russian : LanguagesPreference(10)
|
||||
object Basque : LanguagesPreference(11)
|
||||
object Indonesian : LanguagesPreference(12)
|
||||
data object UseDeviceLanguages : LanguagesPreference(0)
|
||||
data object English : LanguagesPreference(1)
|
||||
data object ChineseSimplified : LanguagesPreference(2)
|
||||
data object German : LanguagesPreference(3)
|
||||
data object French : LanguagesPreference(4)
|
||||
data object Czech : LanguagesPreference(5)
|
||||
data object Italian : LanguagesPreference(6)
|
||||
data object Hindi : LanguagesPreference(7)
|
||||
data object Spanish : LanguagesPreference(8)
|
||||
data object Polish : LanguagesPreference(9)
|
||||
data object Russian : LanguagesPreference(10)
|
||||
data object Basque : LanguagesPreference(11)
|
||||
data object Indonesian : LanguagesPreference(12)
|
||||
data object ChineseTraditional : LanguagesPreference(13)
|
||||
data object Arabic : LanguagesPreference(14)
|
||||
data object Bulgarian : LanguagesPreference(15)
|
||||
data object Catalan : LanguagesPreference(16)
|
||||
data object Danish : LanguagesPreference(17)
|
||||
data object Dutch : LanguagesPreference(18)
|
||||
data object Esperanto : LanguagesPreference(19)
|
||||
data object Filipino : LanguagesPreference(20)
|
||||
data object Hebrew : LanguagesPreference(21)
|
||||
data object Hungarian : LanguagesPreference(22)
|
||||
data object Japanese : LanguagesPreference(23)
|
||||
data object Kannada : LanguagesPreference(24)
|
||||
data object NorwegianBokmal : LanguagesPreference(25)
|
||||
data object Persian : LanguagesPreference(26)
|
||||
data object Portuguese : LanguagesPreference(27)
|
||||
data object PortugueseBrazil : LanguagesPreference(28)
|
||||
data object Romanian : LanguagesPreference(29)
|
||||
data object Serbian : LanguagesPreference(30)
|
||||
data object Slovenian : LanguagesPreference(31)
|
||||
data object Swedish : LanguagesPreference(32)
|
||||
data object Turkish : LanguagesPreference(33)
|
||||
data object Ukrainian : LanguagesPreference(34)
|
||||
data object Vietnamese : LanguagesPreference(35)
|
||||
|
||||
object ChineseTraditional : LanguagesPreference(13)
|
||||
|
||||
override fun put(context: Context, scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
context.dataStore.put(
|
||||
DataStoreKeys.Languages,
|
||||
value
|
||||
DataStoreKeys.Languages, value
|
||||
)
|
||||
scope.launch(Dispatchers.Main) { setLocale(this@LanguagesPreference) }
|
||||
}
|
||||
}
|
||||
|
||||
fun toDesc(context: Context): String =
|
||||
when (this) {
|
||||
UseDeviceLanguages -> context.getString(R.string.use_device_languages)
|
||||
English -> context.getString(R.string.english)
|
||||
ChineseSimplified -> context.getString(R.string.chinese_simplified)
|
||||
German -> context.getString(R.string.german)
|
||||
French -> context.getString(R.string.french)
|
||||
Czech -> context.getString(R.string.czech)
|
||||
Italian -> context.getString(R.string.italian)
|
||||
Hindi -> context.getString(R.string.hindi)
|
||||
Spanish -> context.getString(R.string.spanish)
|
||||
Polish -> context.getString(R.string.polish)
|
||||
Russian -> context.getString(R.string.russian)
|
||||
Basque -> context.getString(R.string.basque)
|
||||
Indonesian -> context.getString(R.string.indonesian)
|
||||
ChineseTraditional -> context.getString(R.string.chinese_traditional)
|
||||
@Composable
|
||||
fun toDesc(): String {
|
||||
return when (this) {
|
||||
ChineseTraditional -> stringResource(id = R.string.chinese_traditional)
|
||||
ChineseSimplified -> stringResource(id = R.string.chinese_simplified)
|
||||
else -> {
|
||||
this.toLocale().toDisplayName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toLocale(): Locale? =
|
||||
when (this) {
|
||||
UseDeviceLanguages -> null
|
||||
English -> Locale("en", "US")
|
||||
ChineseSimplified -> Locale("zh", "CN")
|
||||
German -> Locale("de", "DE")
|
||||
French -> Locale("fr", "FR")
|
||||
Czech -> Locale("cs", "CZ")
|
||||
Italian -> Locale("it", "IT")
|
||||
Hindi -> Locale("hi", "IN")
|
||||
Spanish -> Locale("es", "ES")
|
||||
Polish -> Locale("pl", "PL")
|
||||
Russian -> Locale("ru", "RU")
|
||||
Basque -> Locale("eu", "ES")
|
||||
Indonesian -> Locale("in", "ID")
|
||||
ChineseTraditional -> Locale("zh", "TW")
|
||||
}
|
||||
|
||||
private fun toLocaleList(): LocaleListCompat = toLocale()?.let { LocaleListCompat.create(it) }
|
||||
?: LocaleListCompat.getEmptyLocaleList()
|
||||
fun toLocale(): Locale? = when (this) {
|
||||
UseDeviceLanguages -> null
|
||||
English -> Locale("en")
|
||||
ChineseSimplified -> Locale.forLanguageTag("zh-Hans")
|
||||
German -> Locale("de")
|
||||
French -> Locale("fr")
|
||||
Czech -> Locale("cs")
|
||||
Italian -> Locale("it")
|
||||
Hindi -> Locale("hi")
|
||||
Spanish -> Locale("es")
|
||||
Polish -> Locale("pl")
|
||||
Russian -> Locale("ru")
|
||||
Basque -> Locale("eu")
|
||||
Indonesian -> Locale("in")
|
||||
ChineseTraditional -> Locale.forLanguageTag("zh-Hant")
|
||||
Arabic -> Locale("ar")
|
||||
Bulgarian -> Locale("bg")
|
||||
Catalan -> Locale("ca")
|
||||
Danish -> Locale("da")
|
||||
Dutch -> Locale("nl")
|
||||
Esperanto -> Locale("eo")
|
||||
Filipino -> Locale("fil")
|
||||
Hebrew -> Locale("he")
|
||||
Hungarian -> Locale("hu")
|
||||
Japanese -> Locale("ja")
|
||||
Kannada -> Locale("kn")
|
||||
NorwegianBokmal -> Locale("nb")
|
||||
Persian -> Locale("fa")
|
||||
Portuguese -> Locale("pt")
|
||||
PortugueseBrazil -> Locale("pt", "BR")
|
||||
Romanian -> Locale("ro")
|
||||
Serbian -> Locale("sr")
|
||||
Slovenian -> Locale("sl")
|
||||
Swedish -> Locale("sv")
|
||||
Turkish -> Locale("tr")
|
||||
Ukrainian -> Locale("uk")
|
||||
Vietnamese -> Locale("vi")
|
||||
}
|
||||
|
||||
private fun toLocaleList(): LocaleListCompat =
|
||||
toLocale()?.let { LocaleListCompat.create(it) } ?: LocaleListCompat.getEmptyLocaleList()
|
||||
|
||||
companion object {
|
||||
|
||||
val default = UseDeviceLanguages
|
||||
|
||||
val values = listOf(
|
||||
UseDeviceLanguages,
|
||||
English,
|
||||
ChineseSimplified,
|
||||
German,
|
||||
French,
|
||||
Czech,
|
||||
Italian,
|
||||
Hindi,
|
||||
Spanish,
|
||||
Polish,
|
||||
Russian,
|
||||
Basque,
|
||||
Indonesian,
|
||||
ChineseTraditional,
|
||||
UseDeviceLanguages,
|
||||
Arabic,
|
||||
Basque,
|
||||
Bulgarian,
|
||||
Catalan,
|
||||
ChineseSimplified,
|
||||
ChineseTraditional,
|
||||
Czech,
|
||||
Danish,
|
||||
Dutch,
|
||||
English,
|
||||
Esperanto,
|
||||
Filipino,
|
||||
French,
|
||||
German,
|
||||
Hebrew,
|
||||
Hindi,
|
||||
Hungarian,
|
||||
Indonesian,
|
||||
Italian,
|
||||
Japanese,
|
||||
Kannada,
|
||||
NorwegianBokmal,
|
||||
Persian,
|
||||
Polish,
|
||||
Portuguese,
|
||||
PortugueseBrazil,
|
||||
Romanian,
|
||||
Serbian,
|
||||
Slovenian,
|
||||
Spanish,
|
||||
Swedish,
|
||||
Turkish,
|
||||
Ukrainian,
|
||||
Vietnamese
|
||||
)
|
||||
|
||||
fun fromPreferences(preferences: Preferences): LanguagesPreference =
|
||||
when (preferences[DataStoreKeys.Languages.key]) {
|
||||
0 -> UseDeviceLanguages
|
||||
1 -> English
|
||||
2 -> ChineseSimplified
|
||||
3 -> German
|
||||
4 -> French
|
||||
5 -> Czech
|
||||
6 -> Italian
|
||||
7 -> Hindi
|
||||
8 -> Spanish
|
||||
9 -> Polish
|
||||
10 -> Russian
|
||||
11 -> Basque
|
||||
12 -> Indonesian
|
||||
13 -> ChineseTraditional
|
||||
else -> default
|
||||
}
|
||||
fromValue(preferences[DataStoreKeys.Languages.key] ?: 0)
|
||||
|
||||
fun fromValue(value: Int): LanguagesPreference =
|
||||
when (value) {
|
||||
0 -> UseDeviceLanguages
|
||||
1 -> English
|
||||
2 -> ChineseSimplified
|
||||
3 -> German
|
||||
4 -> French
|
||||
5 -> Czech
|
||||
6 -> Italian
|
||||
7 -> Hindi
|
||||
8 -> Spanish
|
||||
9 -> Polish
|
||||
10 -> Russian
|
||||
11 -> Basque
|
||||
12 -> Indonesian
|
||||
13 -> ChineseTraditional
|
||||
else -> default
|
||||
}
|
||||
fun fromValue(value: Int): LanguagesPreference = when (value) {
|
||||
0 -> UseDeviceLanguages
|
||||
1 -> English
|
||||
2 -> ChineseSimplified
|
||||
3 -> German
|
||||
4 -> French
|
||||
5 -> Czech
|
||||
6 -> Italian
|
||||
7 -> Hindi
|
||||
8 -> Spanish
|
||||
9 -> Polish
|
||||
10 -> Russian
|
||||
11 -> Basque
|
||||
12 -> Indonesian
|
||||
13 -> ChineseTraditional
|
||||
14 -> Arabic
|
||||
15 -> Bulgarian
|
||||
16 -> Catalan
|
||||
17 -> Danish
|
||||
18 -> Dutch
|
||||
19 -> Esperanto
|
||||
20 -> Filipino
|
||||
21 -> Hebrew
|
||||
22 -> Hungarian
|
||||
23 -> Japanese
|
||||
24 -> Kannada
|
||||
25 -> NorwegianBokmal
|
||||
26 -> Persian
|
||||
27 -> Portuguese
|
||||
28 -> PortugueseBrazil
|
||||
29 -> Romanian
|
||||
30 -> Serbian
|
||||
31 -> Slovenian
|
||||
32 -> Swedish
|
||||
33 -> Turkish
|
||||
34 -> Ukrainian
|
||||
35 -> Vietnamese
|
||||
else -> default
|
||||
}
|
||||
|
||||
fun setLocale(preference: LanguagesPreference) {
|
||||
AppCompatDelegate.setApplicationLocales(preference.toLocaleList())
|
||||
@ -143,3 +208,8 @@ sealed class LanguagesPreference(val value: Int) : Preference() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Locale?.toDisplayName(): String = this?.getDisplayName(this) ?: stringResource(
|
||||
id = R.string.use_device_languages
|
||||
)
|
||||
|
@ -17,11 +17,13 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.infrastructure.preference.LocalNewVersionNumber
|
||||
import me.ash.reader.infrastructure.preference.LocalSkipVersionNumber
|
||||
import me.ash.reader.infrastructure.preference.toDisplayName
|
||||
import me.ash.reader.ui.component.base.Banner
|
||||
import me.ash.reader.ui.component.base.DisplayText
|
||||
import me.ash.reader.ui.component.base.FeedbackIconButton
|
||||
@ -31,6 +33,7 @@ import me.ash.reader.ui.page.common.RouteName
|
||||
import me.ash.reader.ui.page.settings.tips.UpdateDialog
|
||||
import me.ash.reader.ui.page.settings.tips.UpdateViewModel
|
||||
import me.ash.reader.ui.theme.palette.onLight
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun SettingsPage(
|
||||
@ -123,7 +126,7 @@ fun SettingsPage(
|
||||
item {
|
||||
SelectableSettingGroupItem(
|
||||
title = stringResource(R.string.languages),
|
||||
desc = stringResource(R.string.languages_desc),
|
||||
desc = Locale.getDefault().toDisplayName(),
|
||||
icon = Icons.Outlined.Language,
|
||||
) {
|
||||
navController.navigate(RouteName.LANGUAGES) {
|
||||
|
@ -28,6 +28,7 @@ import me.ash.reader.ui.component.base.RYScaffold
|
||||
import me.ash.reader.ui.ext.openURL
|
||||
import me.ash.reader.ui.page.settings.SettingItem
|
||||
import me.ash.reader.ui.theme.palette.onLight
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -35,7 +36,13 @@ fun LanguagesPage(
|
||||
navController: NavHostController,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val languages = LocalLanguages.current
|
||||
val currentLocale = Locale.getDefault()
|
||||
|
||||
val languages = LocalLanguages.current.run {
|
||||
if (toLocale() == currentLocale) this
|
||||
else LanguagesPreference.default
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
RYScaffold(
|
||||
@ -64,14 +71,17 @@ fun LanguagesPage(
|
||||
)
|
||||
},
|
||||
) {
|
||||
context.openURL(context.getString(R.string.translatable_url), OpenLinkPreference.AutoPreferCustomTabs)
|
||||
context.openURL(
|
||||
context.getString(R.string.translatable_url),
|
||||
OpenLinkPreference.AutoPreferCustomTabs
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
item {
|
||||
LanguagesPreference.values.map {
|
||||
SettingItem(
|
||||
title = it.toDesc(context),
|
||||
title = it.toDesc(),
|
||||
onClick = {
|
||||
it.put(context, scope)
|
||||
},
|
||||
|
@ -102,7 +102,6 @@
|
||||
<string name="interaction">Interaction</string>
|
||||
<string name="interaction_desc">Initial page, haptic feedback</string>
|
||||
<string name="languages">Languages</string>
|
||||
<string name="languages_desc">English, Chinese, more</string>
|
||||
<string name="help_translate">Help translate</string>
|
||||
<string name="translatable_url" translatable="false">https://hosted.weblate.org/projects/readyou/</string>
|
||||
<string name="use_device_languages">Use device language</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user