mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Enhance string formatter, add tests to validate translations
This commit is contained in:
42
.github/workflows/Check.yml
vendored
Normal file
42
.github/workflows/Check.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check_wrapper:
|
||||
name: Validate Gradle Wrapper
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
run_tests:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '15'
|
||||
architecture: x64
|
||||
|
||||
- name: Setup Cl
|
||||
run: ./scripts/SetupClUnix.sh
|
||||
|
||||
- name: Run Tests
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
arguments: check
|
||||
wrapper-cache-enabled: true
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
@@ -20,7 +20,7 @@ version = "1.1.1"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
google()
|
||||
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ dependencies {
|
||||
implementation("io.github.kerubistan.kroki:kroki-coroutines:1.21")
|
||||
|
||||
// Testing
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
testImplementation(kotlin("test-junit"))
|
||||
testImplementation(compose("org.jetbrains.compose.ui:ui-test-junit4"))
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
||||
}
|
||||
|
||||
|
||||
@@ -46,18 +46,50 @@ class XmlResourceBundle internal constructor(internal val lookup: Map<String, An
|
||||
}
|
||||
|
||||
fun getStringA(key: String): String {
|
||||
return getString(key).replace("\\n", "\n")
|
||||
return getString(key)
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\t", "\t")
|
||||
.replace("\\?", "?")
|
||||
.replace("\\@", "@")
|
||||
}
|
||||
|
||||
fun getString(key: String, vararg replacements: String): String {
|
||||
fun getString(key: String, vararg replacements: Any): String {
|
||||
val stringBuilder = StringBuilder()
|
||||
var string = getStringA(key)
|
||||
replacements.forEachIndexed { index, s ->
|
||||
string = string.replace(
|
||||
"%" + (index + 1).toString() + '$' + "s",
|
||||
s
|
||||
)
|
||||
while (true) {
|
||||
val index = string.indexOf('%')
|
||||
if (index < 0) {
|
||||
stringBuilder.append(string)
|
||||
break
|
||||
} else {
|
||||
stringBuilder.append(string.substring(0, index))
|
||||
val stringConfig = string.substring(index, index + 4)
|
||||
val item = replacements[stringConfig[1].digitToInt() - 1]
|
||||
when (stringConfig[3]) {
|
||||
's' -> {
|
||||
require(item is String) { "Expected String, got ${item::class.java.simpleName}" }
|
||||
stringBuilder.append(item)
|
||||
}
|
||||
return string
|
||||
'd' -> {
|
||||
when (item) {
|
||||
is Int -> stringBuilder.append(item)
|
||||
is Long -> stringBuilder.append(item)
|
||||
else -> throw IllegalArgumentException("Expected Int or Long, got ${item::class.java.simpleName}")
|
||||
}
|
||||
}
|
||||
'f' -> {
|
||||
when (item) {
|
||||
is Float -> stringBuilder.append(item)
|
||||
is Double -> stringBuilder.append(item)
|
||||
else -> throw IllegalArgumentException("Expected Float or Double, got ${item::class.java.simpleName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
stringBuilder.append(item)
|
||||
string = string.substring((index + 4).coerceAtMost(string.length), string.length)
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -68,29 +100,6 @@ class XmlResourceBundle internal constructor(internal val lookup: Map<String, An
|
||||
}
|
||||
}
|
||||
|
||||
fun forTag(tag: String): XmlResourceBundle {
|
||||
val classLoader = this::class.java.classLoader
|
||||
val rootBundle = classLoader.getResourceAsStream("values/values/strings.xml")!!
|
||||
.use { XmlResourceBundle(it) }
|
||||
|
||||
val languageBundle = classLoader.getResourceAsStream("values/values-${tag.substringBefore('-')}/strings.xml")
|
||||
?.use { XmlResourceBundle(it) }
|
||||
|
||||
val languageTagBundle = if (tag.contains('-')) {
|
||||
classLoader.getResourceAsStream("values/values-$tag/strings.xml")
|
||||
?.use { XmlResourceBundle(it) }
|
||||
} else null
|
||||
|
||||
var resultBundle = rootBundle
|
||||
if (languageBundle != null) {
|
||||
resultBundle += languageBundle
|
||||
}
|
||||
if (languageTagBundle != null) {
|
||||
resultBundle += languageTagBundle
|
||||
}
|
||||
return resultBundle
|
||||
}
|
||||
|
||||
fun forLocale(locale: Locale): XmlResourceBundle {
|
||||
val classLoader = this::class.java.classLoader
|
||||
val rootBundle = classLoader.getResourceAsStream("values/values/strings.xml")!!
|
||||
|
||||
@@ -22,7 +22,7 @@ fun stringResource(key: String): String {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun stringResource(key: String, vararg replacements: String): String {
|
||||
fun stringResource(key: String, vararg replacements: Any): String {
|
||||
val resources = LocalResources.current
|
||||
return remember(key, replacements) { resources.getString(key, *replacements) }
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ fun DownloadsExtraInfo() {
|
||||
val list by vm.downloadQueue.collectAsState()
|
||||
if (list.isNotEmpty()) {
|
||||
Text(
|
||||
stringResource("downloads_remaining", list.size.toString()),
|
||||
stringResource("downloads_remaining", list.size),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
|
||||
)
|
||||
|
||||
@@ -109,7 +109,7 @@ fun ChapterItem(
|
||||
if (length > 0) append(" • ")
|
||||
append(
|
||||
AnnotatedString(
|
||||
stringResource("page_progress", (chapter.lastPageRead + 1).toString()),
|
||||
stringResource("page_progress", (chapter.lastPageRead + 1)),
|
||||
SpanStyle(color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -168,9 +168,9 @@ fun SettingsReaderScreen(navController: BackStack<Route>) {
|
||||
)
|
||||
val maxSize by it.maxSize.collectAsState()
|
||||
val (maxSizeTitle, maxSizeSubtitle) = if (direction == Direction.Up || direction == Direction.Down) {
|
||||
stringResource("max_width") to stringResource("max_width_sub", maxSize.toString())
|
||||
stringResource("max_width") to stringResource("max_width_sub", maxSize)
|
||||
} else {
|
||||
stringResource("max_height") to stringResource("max_height_sub", maxSize.toString())
|
||||
stringResource("max_height") to stringResource("max_height_sub", maxSize)
|
||||
}
|
||||
ChoicePreference(
|
||||
it.maxSize,
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<string name="categories_delete_confirm">آیا میخواهید رده %1$s را پاک کنید؟</string>
|
||||
|
||||
<!-- Downloads Menu -->
|
||||
<string name="downloads_remaining">%1$s تغییر نام</string>
|
||||
<string name="downloads_remaining">%1$d تغییر نام</string>
|
||||
|
||||
<!-- Extensions Menu -->
|
||||
<string name="enabled_languages">زبانهای فعال</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="default_category">پیشفرض</string>
|
||||
|
||||
<!-- Manga Menu -->
|
||||
<string name="page_progress">صفحه %1$s</string>
|
||||
<string name="page_progress">صفحه %1$d</string>
|
||||
|
||||
<!-- Sources Menu -->
|
||||
<string name="sources_home">خانه</string>
|
||||
@@ -145,9 +145,9 @@
|
||||
<string name="force_fit_height">هم اندازه کردن طول</string>
|
||||
<string name="force_fit_height_sub">اگر طول پنجره از عکس بزرگ تر است، عکس هم اندازه میشود</string>
|
||||
<string name="max_width">حداکثر عرض</string>
|
||||
<string name="max_width_sub">طولی که عکس باید از آن کوچکتر باشد که مقدار فعلی آن %1$sdp است. میشود برای بزرگ تر کردن عکسها استفاده شود</string>
|
||||
<string name="max_width_sub">طولی که عکس باید از آن کوچکتر باشد که مقدار فعلی آن %1$ddp است. میشود برای بزرگ تر کردن عکسها استفاده شود</string>
|
||||
<string name="max_height">حداکثر طول</string>
|
||||
<string name="max_height_sub">طولی که عکس باید از آن کوچکتر باشد که مقدار فعلی آن %1$sdp است. میشود برای بزرگ تر کردن عکسها استفاده شود.</string>
|
||||
<string name="max_height_sub">طولی که عکس باید از آن کوچکتر باشد که مقدار فعلی آن %1$ddp است. میشود برای بزرگ تر کردن عکسها استفاده شود.</string>
|
||||
<string name="max_size_unrestricted">آزاد</string>
|
||||
<string name="image_scale">مقیاس عکس</string>
|
||||
<string name="scale_fit_screen">هم اندازه صفحه</string>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<string name="categories_delete_confirm">Do you wish to delete the category %1$s?</string>
|
||||
|
||||
<!-- Downloads Menu -->
|
||||
<string name="downloads_remaining">%1$s remaining</string>
|
||||
<string name="downloads_remaining">%1$d remaining</string>
|
||||
|
||||
<!-- Extensions Menu -->
|
||||
<string name="enabled_languages">Enabled languages</string>
|
||||
@@ -56,7 +56,7 @@
|
||||
<string name="default_category">Default</string>
|
||||
|
||||
<!-- Manga Menu -->
|
||||
<string name="page_progress">Page %1$s</string>
|
||||
<string name="page_progress">Page %1$d</string>
|
||||
|
||||
<!-- Sources Menu -->
|
||||
<string name="sources_home">Home</string>
|
||||
@@ -144,9 +144,9 @@
|
||||
<string name="force_fit_height">Force fit height</string>
|
||||
<string name="force_fit_height_sub">When the window's height is over the images height, scale the image to the window</string>
|
||||
<string name="max_width">Max width</string>
|
||||
<string name="max_width_sub">Width to restrict a image from going over, currently %1$sdp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_width_sub">Width to restrict a image from going over, currently %1$ddp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_height">Max height</string>
|
||||
<string name="max_height_sub">Height to restrict a image from going over, currently %1$sdp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_height_sub">Height to restrict a image from going over, currently %1$ddp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_size_unrestricted">Unrestricted</string>
|
||||
<string name="image_scale">Image Scale</string>
|
||||
<string name="scale_fit_screen">Fit Screen</string>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<string name="categories_delete_confirm">Você deseja deletar a categoria %1$s?</string>
|
||||
|
||||
<!-- Downloads Menu -->
|
||||
<string name="downloads_remaining">%1$s Remanescente</string>
|
||||
<string name="downloads_remaining">%1$d Remanescente</string>
|
||||
|
||||
<!-- Extensions Menu -->
|
||||
<string name="enabled_languages">Idiomas habilitados</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="default_category">Padrão</string>
|
||||
|
||||
<!-- Manga Menu -->
|
||||
<string name="page_progress">Página %1$s</string>
|
||||
<string name="page_progress">Página %1$d</string>
|
||||
|
||||
<!-- Sources Menu -->
|
||||
<string name="sources_home">Iníco</string>
|
||||
@@ -145,9 +145,9 @@
|
||||
<string name="force_fit_height">Forçar o ajuste de altura</string>
|
||||
<string name="force_fit_height_sub">Quando a altura da janela estiver acima da altura da imagem, dimensione a imagem para a janela</string>
|
||||
<string name="max_width">Largura máxima</string>
|
||||
<string name="max_width_sub">Largura para impedir que uma imagem ultrapasse, atualmente %1$sdp. Funciona com a configuração acima para dimensionar as imagens, mas restringindo-as a uma certa quantidade</string>
|
||||
<string name="max_width_sub">Largura para impedir que uma imagem ultrapasse, atualmente %1$ddp. Funciona com a configuração acima para dimensionar as imagens, mas restringindo-as a uma certa quantidade</string>
|
||||
<string name="max_height">Altura máxima</string>
|
||||
<string name="max_height_sub">Altura para impedir que uma imagem ultrapasse, atualmente %1$sdp. Funciona com a configuração acima para dimensionar as imagens, mas restringindo-as a uma certa quantidade</string>
|
||||
<string name="max_height_sub">Altura para impedir que uma imagem ultrapasse, atualmente %1$ddp. Funciona com a configuração acima para dimensionar as imagens, mas restringindo-as a uma certa quantidade</string>
|
||||
<string name="max_size_unrestricted">Irrestrito</string>
|
||||
<string name="image_scale">Ecala da imagem</string>
|
||||
<string name="scale_fit_screen">Tela de ajuste</string>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<string name="categories_delete_confirm">Vill du radera kategorin? %1$s?</string>
|
||||
|
||||
<!-- Downloads Menu -->
|
||||
<string name="downloads_remaining">%1$s kvar</string>
|
||||
<string name="downloads_remaining">%1$d kvar</string>
|
||||
|
||||
<!-- Extensions Menu -->
|
||||
<string name="enabled_languages">Aktiverade språk</string>
|
||||
@@ -56,7 +56,7 @@
|
||||
<string name="default_category">Standard</string>
|
||||
|
||||
<!-- Manga Menu -->
|
||||
<string name="page_progress">Sida %1$s</string>
|
||||
<string name="page_progress">Sida %1$d</string>
|
||||
|
||||
<!-- Sources Menu -->
|
||||
<string name="sources_home">Hem</string>
|
||||
@@ -144,9 +144,9 @@
|
||||
<string name="force_fit_height">Tvinga höjdpassning</string>
|
||||
<string name="force_fit_height_sub">När fönstrets höjd är större än bildens höjd, skala bilden till fönstret</string>
|
||||
<string name="max_width">Max bredd</string>
|
||||
<string name="max_width_sub">Bredd för att begränsa en bild från att gå över, för närvarande% %1$sdp. Fungerar med ovanstående inställning för att skala upp bilder men begränsa dem till en viss mängd</string>
|
||||
<string name="max_width_sub">Bredd för att begränsa en bild från att gå över, för närvarande %1$ddp. Fungerar med ovanstående inställning för att skala upp bilder men begränsa dem till en viss mängd</string>
|
||||
<string name="max_height">Max höjd</string>
|
||||
<string name="max_height_sub">Höjd för att begränsa en bild från att gå över, för närvarande% %1$sdp. Fungerar med ovanstående inställning för att skala upp bilder men begränsa dem till en viss mängd</string>
|
||||
<string name="max_height_sub">Höjd för att begränsa en bild från att gå över, för närvarande %1$ddp. Fungerar med ovanstående inställning för att skala upp bilder men begränsa dem till en viss mängd</string>
|
||||
<string name="max_size_unrestricted">Obegränsad</string>
|
||||
<string name="image_scale">Bildskala</string>
|
||||
<string name="scale_fit_screen">Passa skärmen</string>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<string name="categories_delete_confirm">%1$s-ஐ அழிக்க வரும்புகிறீர்களா?</string>
|
||||
|
||||
<!-- Downloads Menu -->
|
||||
<string name="downloads_remaining">%1$s மீதி</string> <!--Translation;%1$s remaining; or "பதிவிறக்கம் மீதி"-download remaining, if needed to be specific-->
|
||||
<string name="downloads_remaining">%1$d மீதி</string> <!--Translation;%1$s remaining; or "பதிவிறக்கம் மீதி"-download remaining, if needed to be specific-->
|
||||
|
||||
<!-- Extensions Menu -->
|
||||
<string name="enabled_languages">இயக்கப்பட்ட மொழிகள்</string>
|
||||
@@ -56,7 +56,7 @@
|
||||
<string name="default_category">இயல்புநிலை</string> <!--This means default-->
|
||||
|
||||
<!-- Manga Menu -->
|
||||
<string name="page_progress">பக்கம் %1$s</string>
|
||||
<string name="page_progress">பக்கம் %1$d</string>
|
||||
|
||||
<!-- Sources Menu -->
|
||||
<string name="sources_home">முகப்பு</string>
|
||||
@@ -144,9 +144,9 @@
|
||||
<string name="force_fit_height">பொருத்தமான உயரம் கட்டாயபடுத்தல்</string>
|
||||
<string name="force_fit_height_sub">சாளரத்தின் உயரம் படங்களின் உயரத்திற்கு மேல் இருக்கும்போது, படத்தை சாளரத்திற்கு அளவிடவும்</string>
|
||||
<string name="max_width">அதிகபட்ச அகலம்</string>
|
||||
<string name="max_width_sub">படத்து அளவு மேலே செல்வதைத் தடுக்கும் அகலம், இப்பொழுது %1$sdp. இது மேல் அமைவுடன் வேலைசையும் ஆனால் ஒரு அளவுக்கு தான் கட்டுப்படுத்தும்</string>
|
||||
<string name="max_width_sub">படத்து அளவு மேலே செல்வதைத் தடுக்கும் அகலம், இப்பொழுது %1$ddp. இது மேல் அமைவுடன் வேலைசையும் ஆனால் ஒரு அளவுக்கு தான் கட்டுப்படுத்தும்</string>
|
||||
<string name="max_height">அதிகபட்ச உயரம்</string>
|
||||
<string name="max_height_sub">படத்து அளவு மேலே செல்வதைத் தடுக்கும் உயரம், இப்பொழுது %1$sdp. இது மேல் அமைவுடன் வேலைசையும் ஆனால் ஒரு அளவுக்கு தான் கட்டுப்படுத்தும்</string>
|
||||
<string name="max_height_sub">படத்து அளவு மேலே செல்வதைத் தடுக்கும் உயரம், இப்பொழுது %1$ddp. இது மேல் அமைவுடன் வேலைசையும் ஆனால் ஒரு அளவுக்கு தான் கட்டுப்படுத்தும்</string>
|
||||
<string name="max_size_unrestricted">கட்டுப்பாடற்ற</string>
|
||||
<string name="image_scale">பட அளவு</string>
|
||||
<string name="scale_fit_screen">திரைக்கு பொருது</string>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<string name="categories_delete_confirm">Do you wish to delete the category %1$s?</string>
|
||||
|
||||
<!-- Downloads Menu -->
|
||||
<string name="downloads_remaining">%1$s remaining</string>
|
||||
<string name="downloads_remaining">%1$d remaining</string>
|
||||
|
||||
<!-- Extensions Menu -->
|
||||
<string name="enabled_languages">Enabled languages</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="default_category">Default</string>
|
||||
|
||||
<!-- Manga Menu -->
|
||||
<string name="page_progress">Page %1$s</string>
|
||||
<string name="page_progress">Page %1$d</string>
|
||||
|
||||
<!-- Sources Menu -->
|
||||
<string name="sources_home">Home</string>
|
||||
@@ -145,9 +145,9 @@
|
||||
<string name="force_fit_height">Force fit height</string>
|
||||
<string name="force_fit_height_sub">When the window's height is over the images height, scale the image to the window</string>
|
||||
<string name="max_width">Max width</string>
|
||||
<string name="max_width_sub">Width to restrict a image from going over, currently %1$sdp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_width_sub">Width to restrict a image from going over, currently %1$ddp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_height">Max height</string>
|
||||
<string name="max_height_sub">Height to restrict a image from going over, currently %1$sdp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_height_sub">Height to restrict a image from going over, currently %1$ddp. Works with the above setting to scale images up but restrict them to a certain amount</string>
|
||||
<string name="max_size_unrestricted">Unrestricted</string>
|
||||
<string name="image_scale">Image Scale</string>
|
||||
<string name="scale_fit_screen">Fit Screen</string>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package ca.gosyer.data.server
|
||||
|
||||
import kotlin.test.Test
|
||||
|
||||
class ExtensionInteractionTest {
|
||||
|
||||
@Test
|
||||
fun `Install a extension`() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ca.gosyer.data.translation
|
||||
|
||||
import org.junit.Before
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import kotlin.test.Test
|
||||
|
||||
class XmlResourceBundleTest {
|
||||
|
||||
lateinit var rootBundle: XmlResourceBundle
|
||||
lateinit var bundles: List<Pair<String, XmlResourceBundle>>
|
||||
|
||||
@Before
|
||||
fun `load root bundle`() {
|
||||
rootBundle = XmlResourceBundle.forLocale(Locale.ENGLISH)
|
||||
val url = XmlResourceBundleTest::class.java.getResource("/values/")
|
||||
requireNotNull(url)
|
||||
bundles = requireNotNull(
|
||||
File(url.toURI()).listFiles()?.mapNotNull {
|
||||
if (it.name == "values") return@mapNotNull null
|
||||
it.name.substringAfter("values-") to XmlResourceBundle.forLocale(Locale.forLanguageTag(it.name.substringAfter("values-")))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test each language parameters`() {
|
||||
rootBundle.lookup.entries.forEach { (key, value) ->
|
||||
if (value !is String || !value.contains('%')) return@forEach
|
||||
val testValues: Array<Any> = value.split('%').drop(1).map {
|
||||
when (val char = it[2]) {
|
||||
's' -> "Test string"
|
||||
'd' -> 69
|
||||
'f' -> 6.9
|
||||
else -> {
|
||||
throw IllegalArgumentException("Unknown value key $char in $key")
|
||||
}
|
||||
}
|
||||
}.toTypedArray()
|
||||
bundles.forEach { (lang, bundle) ->
|
||||
try {
|
||||
bundle.getString(key, *testValues)
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Broken translation in $lang with $key", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user