From d90ccd3780be7f52242abc98abe3550be1c12dc9 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Sun, 19 Mar 2023 15:25:04 +0100 Subject: [PATCH] refactor text translations, tests --- .../data/chat/message/FormattedChatMessage.kt | 2 +- .../minosoft/data/language/LanguageUtil.kt | 4 +- .../minosoft/data/language/lang/Language.kt | 56 +----- .../data/language/lang/LanguageList.kt | 4 +- .../data/language/manager/LanguageManager.kt | 4 +- .../language/manager/MultiLanguageManager.kt | 4 +- .../placeholder/PlaceholderIteratorOptions.kt | 30 +++ .../language/placeholder/PlaceholderUtil.kt | 121 +++++++++++++ .../data/language/translate/Translator.kt | 12 +- .../minosoft/data/text/ChatComponent.kt | 12 +- .../protocol/protocol/buffers/InByteBuffer.kt | 2 +- .../assets/minosoft/language/en_us.lang | 12 +- .../assets/minosoft/language/es_es.lang | 10 +- .../data/language/lang/LanguageTest.kt | 171 ++++++++++++++++++ .../minosoft/data/text/ChatComponentTest.kt | 2 +- 15 files changed, 359 insertions(+), 87 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderIteratorOptions.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderUtil.kt create mode 100644 src/test/java/de/bixilon/minosoft/data/language/lang/LanguageTest.kt diff --git a/src/main/java/de/bixilon/minosoft/data/chat/message/FormattedChatMessage.kt b/src/main/java/de/bixilon/minosoft/data/chat/message/FormattedChatMessage.kt index 775e5d203..a89a4387c 100644 --- a/src/main/java/de/bixilon/minosoft/data/chat/message/FormattedChatMessage.kt +++ b/src/main/java/de/bixilon/minosoft/data/chat/message/FormattedChatMessage.kt @@ -31,7 +31,7 @@ FormattedChatMessage( init { // ToDo: parent (formatting) val data = type.chat.formatParameters(parameters) - text = connection.language.forceTranslate(type.chat.translationKey.toResourceLocation(), restrictedMode = true, fallback = type.chat.translationKey, data = data) + text = connection.language.forceTranslate(type.chat.translationKey.toResourceLocation(), restricted = true, fallback = type.chat.translationKey, data = data) text.setFallbackColor(ChatUtil.DEFAULT_CHAT_COLOR) } } diff --git a/src/main/java/de/bixilon/minosoft/data/language/LanguageUtil.kt b/src/main/java/de/bixilon/minosoft/data/language/LanguageUtil.kt index 9d392e1b9..691229ff3 100644 --- a/src/main/java/de/bixilon/minosoft/data/language/LanguageUtil.kt +++ b/src/main/java/de/bixilon/minosoft/data/language/LanguageUtil.kt @@ -46,7 +46,7 @@ object LanguageUtil { } fun loadJsonLanguage(json: JsonObject): LanguageData { - val data: LanguageData = mutableMapOf() + val data: LanguageData = HashMap() for ((key, value) in json) { val path = ResourceLocation.of(key).path @@ -57,7 +57,7 @@ object LanguageUtil { } fun loadLanguage(lines: Sequence): LanguageData { - val data: LanguageData = mutableMapOf() + val data: LanguageData = HashMap() for (line in lines) { if (line.isBlank() || line.startsWith("#")) { diff --git a/src/main/java/de/bixilon/minosoft/data/language/lang/Language.kt b/src/main/java/de/bixilon/minosoft/data/language/lang/Language.kt index 94554b99c..89480a1e8 100644 --- a/src/main/java/de/bixilon/minosoft/data/language/lang/Language.kt +++ b/src/main/java/de/bixilon/minosoft/data/language/lang/Language.kt @@ -12,9 +12,9 @@ */ package de.bixilon.minosoft.data.language.lang +import de.bixilon.minosoft.data.language.placeholder.PlaceholderUtil import de.bixilon.minosoft.data.language.translate.Translator import de.bixilon.minosoft.data.registries.identified.ResourceLocation -import de.bixilon.minosoft.data.text.BaseComponent import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.TextComponent @@ -23,62 +23,12 @@ class Language( private val data: LanguageData, ) : Translator { - override fun translate(key: ResourceLocation?, parent: TextComponent?, restrictedMode: Boolean, vararg data: Any?): ChatComponent? { + override fun translate(key: ResourceLocation?, parent: TextComponent?, restricted: Boolean, vararg data: Any?): ChatComponent? { val placeholder = this.data[key?.path] ?: return null - return Companion.translate(placeholder, parent, this, restrictedMode, *data) + return PlaceholderUtil.format(placeholder, parent, restricted, *data) } override fun toString(): String { return name } - - companion object { - private val FORMATTER_ORDER_REGEX = "%(\\w+)\\\$[sd]".toRegex() // %1$s fell from a high place - private val FORMATTER_SPLIT_REGEX = "%[ds]".toRegex() // %s fell from a high place - - - fun translate(placeholder: String, parent: TextComponent? = null, translator: Translator? = null, restrictedMode: Boolean = false, vararg data: Any?): ChatComponent { - - val ret = BaseComponent() - - val arguments: MutableList = mutableListOf() - var splitPlaceholder: List = emptyList() - - // Bring arguments in correct oder - FORMATTER_ORDER_REGEX.findAll(placeholder).toList().let { - if (it.isEmpty()) { - // this is not the correct formatter - return@let - } - splitPlaceholder = placeholder.split(FORMATTER_ORDER_REGEX) - for (matchResult in it) { - // 2 groups: Full, index. We don't care about the full value, just skip it - val dataIndex = matchResult.groupValues[1].toInt() - 1 - if (dataIndex < 0 || dataIndex > data.size) { - arguments += null - continue - } - arguments += data[dataIndex] - } - } - - // check if other splitter already did the job for us - if (splitPlaceholder.isEmpty()) { - placeholder.split(FORMATTER_SPLIT_REGEX).let { - splitPlaceholder = it - arguments.addAll(data.toList()) - } - } - - // create base component - for ((index, part) in splitPlaceholder.withIndex()) { - ret += ChatComponent.of(part, translator, parent, restrictedMode) - if (index < data.size) { - ret += ChatComponent.of(arguments[index], translator, parent, restrictedMode) - } - } - - return ret - } - } } diff --git a/src/main/java/de/bixilon/minosoft/data/language/lang/LanguageList.kt b/src/main/java/de/bixilon/minosoft/data/language/lang/LanguageList.kt index 124cbfeb8..7ad0a62b8 100644 --- a/src/main/java/de/bixilon/minosoft/data/language/lang/LanguageList.kt +++ b/src/main/java/de/bixilon/minosoft/data/language/lang/LanguageList.kt @@ -22,9 +22,9 @@ class LanguageList( private val list: MutableList, ) : Translator { - override fun translate(key: ResourceLocation?, parent: TextComponent?, restrictedMode: Boolean, vararg data: Any?): ChatComponent? { + override fun translate(key: ResourceLocation?, parent: TextComponent?, restricted: Boolean, vararg data: Any?): ChatComponent? { for (language in list) { - return language.translate(key, parent, restrictedMode, data) ?: continue + return language.translate(key, parent, restricted, data) ?: continue } return null } diff --git a/src/main/java/de/bixilon/minosoft/data/language/manager/LanguageManager.kt b/src/main/java/de/bixilon/minosoft/data/language/manager/LanguageManager.kt index 3444e9411..f3c7fc8ff 100644 --- a/src/main/java/de/bixilon/minosoft/data/language/manager/LanguageManager.kt +++ b/src/main/java/de/bixilon/minosoft/data/language/manager/LanguageManager.kt @@ -24,9 +24,9 @@ class LanguageManager( ) : Translator { - override fun translate(key: ResourceLocation?, parent: TextComponent?, restrictedMode: Boolean, vararg data: Any?): ChatComponent? { + override fun translate(key: ResourceLocation?, parent: TextComponent?, restricted: Boolean, vararg data: Any?): ChatComponent? { for (language in languages) { - return language.translate(key, parent, restrictedMode, *data) ?: continue + return language.translate(key, parent, restricted, *data) ?: continue } return null } diff --git a/src/main/java/de/bixilon/minosoft/data/language/manager/MultiLanguageManager.kt b/src/main/java/de/bixilon/minosoft/data/language/manager/MultiLanguageManager.kt index 4a1e034b9..0cded5317 100644 --- a/src/main/java/de/bixilon/minosoft/data/language/manager/MultiLanguageManager.kt +++ b/src/main/java/de/bixilon/minosoft/data/language/manager/MultiLanguageManager.kt @@ -22,9 +22,9 @@ class MultiLanguageManager( val translators: MutableMap = mutableMapOf(), ) : Translator { - override fun translate(key: ResourceLocation?, parent: TextComponent?, restrictedMode: Boolean, vararg data: Any?): ChatComponent? { + override fun translate(key: ResourceLocation?, parent: TextComponent?, restricted: Boolean, vararg data: Any?): ChatComponent? { if (key == null) return null - return translators[key.namespace]?.translate(key, parent, restrictedMode, *data) + return translators[key.namespace]?.translate(key, parent, restricted, *data) } } diff --git a/src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderIteratorOptions.kt b/src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderIteratorOptions.kt new file mode 100644 index 000000000..f97dab277 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderIteratorOptions.kt @@ -0,0 +1,30 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.language.placeholder + +import de.bixilon.minosoft.data.text.BaseComponent +import de.bixilon.minosoft.data.text.TextComponent +import java.util.* + +class PlaceholderIteratorOptions( + val iterator: PrimitiveIterator.OfInt, + val parent: TextComponent?, + val restricted: Boolean, + val data: Array, + val component: BaseComponent = BaseComponent(), + var previous: TextComponent? = null, + var builder: StringBuilder = StringBuilder(), + var dataIndex: Int = 0, + var escaped: Boolean = false, +) diff --git a/src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderUtil.kt b/src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderUtil.kt new file mode 100644 index 000000000..67d25cac8 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/language/placeholder/PlaceholderUtil.kt @@ -0,0 +1,121 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.language.placeholder + +import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.data.text.EmptyComponent +import de.bixilon.minosoft.data.text.TextComponent + +object PlaceholderUtil { + private val DIGIT_RANGE = '0'.code..'9'.code + private const val ESCAPE = '%'.code + private const val INDEX = '$'.code + private const val STRING = 's'.code + private const val DIGIT = 'd'.code + + private fun PlaceholderIteratorOptions.processEscape() { + if (escaped) { + builder.appendCodePoint(ESCAPE) + } else { + escaped = true + } + } + + private fun PlaceholderIteratorOptions.push() { + if (builder.isEmpty()) return + val text = ChatComponent.of(builder.toString(), parent = previous ?: parent, restricted = restricted) + if (text is TextComponent) { + previous = text + } + component += text + builder.clear() + } + + private fun PlaceholderIteratorOptions.appendArgument(index: Int) { + val value = if (index >= 0 && index < data.size) data[index] else "" // TODO (kutil 1.21): replace with ArrayUtil::isIndex + component += ChatComponent.of(value, parent = previous ?: parent, restricted = restricted) + } + + private fun PlaceholderIteratorOptions.processOrdered() { + push() + appendArgument(dataIndex++) + } + + private fun PlaceholderIteratorOptions.processIndexed(char: Int) { + if (char !in DIGIT_RANGE) { + return processChar(char) + } + val indexBuilder = StringBuilder() + + indexBuilder.appendCodePoint(char) + + var trailing = 0 + while (iterator.hasNext()) { + val digit = iterator.nextInt() + if (digit in DIGIT_RANGE) { + indexBuilder.appendCodePoint(digit) + } else { + trailing = digit + break + } + } + if (trailing != INDEX) { + indexBuilder.append(trailing) + } + if (!iterator.hasNext()) { + builder.append(indexBuilder) + return + } + val type = iterator.nextInt() + if (trailing != INDEX || (type != STRING && type != DIGIT)) { + builder.append(indexBuilder) + return + } + + push() + appendArgument(Integer.parseInt(indexBuilder.toString())) + } + + private fun PlaceholderIteratorOptions.processChar() = processChar(iterator.nextInt()) + private fun PlaceholderIteratorOptions.processChar(char: Int) { + if (char == ESCAPE) { + return processEscape() + } + if (!escaped) { + builder.appendCodePoint(char) + return + } + escaped = false + if (char != STRING && char != DIGIT) { + return processIndexed(char) + } + + return processOrdered() + } + + + fun format(placeholder: String, parent: TextComponent? = null, restricted: Boolean = false, vararg data: Any?): ChatComponent { + if (data.isEmpty()) return ChatComponent.of(placeholder, parent = parent, restricted = restricted) + + + val options = PlaceholderIteratorOptions(placeholder.codePoints().iterator(), parent, restricted, data) + + while (options.iterator.hasNext()) { + options.processChar() + } + options.push() + + return options.component.trim() ?: EmptyComponent + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/language/translate/Translator.kt b/src/main/java/de/bixilon/minosoft/data/language/translate/Translator.kt index 3028de6c5..3c9dd362b 100644 --- a/src/main/java/de/bixilon/minosoft/data/language/translate/Translator.kt +++ b/src/main/java/de/bixilon/minosoft/data/language/translate/Translator.kt @@ -14,7 +14,7 @@ package de.bixilon.minosoft.data.language.translate import de.bixilon.minosoft.data.language.LanguageUtil -import de.bixilon.minosoft.data.language.lang.Language +import de.bixilon.minosoft.data.language.placeholder.PlaceholderUtil import de.bixilon.minosoft.data.registries.identified.ResourceLocation import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.TextComponent @@ -25,16 +25,16 @@ interface Translator { return forceTranslate(key, null, false, null, *data) } - fun forceTranslate(key: ResourceLocation?, parent: TextComponent? = null, restrictedMode: Boolean = false, fallback: String? = null, vararg data: Any?): ChatComponent { - translate(key, parent, restrictedMode, *data)?.let { return it } + fun forceTranslate(key: ResourceLocation?, parent: TextComponent? = null, restricted: Boolean = false, fallback: String? = null, vararg data: Any?): ChatComponent { + translate(key, parent, restricted, *data)?.let { return it } if (fallback != null) { - return Language.translate(fallback, parent, null, restrictedMode) + return PlaceholderUtil.format(fallback, parent, restricted, *data) } - return LanguageUtil.getFallbackTranslation(key, parent, restrictedMode, data) + return LanguageUtil.getFallbackTranslation(key, parent, restricted, *data) } fun translate(key: ResourceLocation?, parent: TextComponent? = null, vararg data: Any?): ChatComponent? = translate(key, parent, false, *data) - fun translate(key: ResourceLocation?, parent: TextComponent? = null, restrictedMode: Boolean = false, vararg data: Any?): ChatComponent? + fun translate(key: ResourceLocation?, parent: TextComponent? = null, restricted: Boolean = false, vararg data: Any?): ChatComponent? fun translate(translatable: Any?): ChatComponent { diff --git a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt index b73e422da..112e4b1df 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt @@ -93,7 +93,7 @@ interface ChatComponent { val EMPTY = EmptyComponent @JvmOverloads - fun of(raw: Any? = null, translator: Translator? = null, parent: TextComponent? = null, ignoreJson: Boolean = false, restrictedMode: Boolean = false): ChatComponent { + fun of(raw: Any? = null, translator: Translator? = null, parent: TextComponent? = null, ignoreJson: Boolean = false, restricted: Boolean = false): ChatComponent { if (raw == null) { return EMPTY } @@ -101,15 +101,15 @@ interface ChatComponent { return raw } if (raw is Translatable && raw !is ResourceLocation) { - return (translator ?: Minosoft.LANGUAGE_MANAGER).forceTranslate(raw.translationKey, parent, restrictedMode = restrictedMode) + return (translator ?: Minosoft.LANGUAGE_MANAGER).forceTranslate(raw.translationKey, parent, restricted = restricted) } when (raw) { - is Map<*, *> -> return BaseComponent(translator, parent, raw.unsafeCast(), restrictedMode).trim() ?: EmptyComponent + is Map<*, *> -> return BaseComponent(translator, parent, raw.unsafeCast(), restricted).trim() ?: EmptyComponent is List<*> -> { val component = BaseComponent() for (part in raw) { - component += of(part, translator, parent, restrictedMode = restrictedMode).trim() ?: continue + component += of(part, translator, parent, restricted = restricted).trim() ?: continue } return component.trim() ?: EmptyComponent } @@ -126,7 +126,7 @@ interface ChatComponent { if (codePoint == '{'.code || codePoint == '['.code) { try { val read: Any = Jackson.MAPPER.readValue(string, Any::class.java) - return of(read, translator, parent, ignoreJson = true, restrictedMode).trim() ?: EmptyComponent + return of(read, translator, parent, ignoreJson = true, restricted).trim() ?: EmptyComponent } catch (ignored: JacksonException) { break } @@ -135,7 +135,7 @@ interface ChatComponent { } } - return LegacyComponentReader.parse(parent, string, restrictedMode).trim() ?: EmptyComponent + return LegacyComponentReader.parse(parent, string, restricted).trim() ?: EmptyComponent } fun String.chat(): ChatComponent { diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt index 82eb7bf08..90261f0b6 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/buffers/InByteBuffer.kt @@ -56,7 +56,7 @@ open class InByteBuffer : de.bixilon.kutil.buffer.bytes.`in`.InByteBuffer { } open fun readChatComponent(): ChatComponent { - return ChatComponent.of(readString(), restrictedMode = true) + return ChatComponent.of(readString(), restricted = true) } fun readDirection(): Directions { diff --git a/src/main/resources/assets/minosoft/language/en_us.lang b/src/main/resources/assets/minosoft/language/en_us.lang index 7c073299c..8aea6cb50 100644 --- a/src/main/resources/assets/minosoft/language/en_us.lang +++ b/src/main/resources/assets/minosoft/language/en_us.lang @@ -20,7 +20,7 @@ minosoft:server_info.remote_brand=Remote brand minosoft:server_info.active_connections=Connected minosoft:server_info.players_online=Players online minosoft:server_info.ping=Latency -minosoft:server_info.delete.dialog.description=Do you really want to delete the server %1$s (%2$s)? +minosoft:server_info.delete.dialog.description=Do you really want to delete the server %0$s (%1$s)? minosoft:connection.dialog.verify_assets.title=Verifying assets... - Minosoft @@ -107,7 +107,7 @@ minosoft:main.account.checking_dialog.title=Checking account... - Minosoft minosoft:main.account.checking_dialog.header=Checking account... Please wait -minosoft:main.account.card.connection_count=%1$s connections +minosoft:main.account.card.connection_count=%0$s connections minosoft:main.account.account_info.id=Id minosoft:main.account.account_info.state=State @@ -135,20 +135,20 @@ minosoft:main.account.add.mojang.cancel_button=Cancel minosoft:main.account.add.microsoft.please_wait.device_code=Obtaining device code...Please wait! minosoft:main.account.add.microsoft.title=Add microsoft account - Minosoft -minosoft:main.account.add.microsoft.header=Please use a web browser to open the page %1$s and enter the following code in order to proceed with the login +minosoft:main.account.add.microsoft.header=Please use a web browser to open the page %0$s and enter the following code in order to proceed with the login minosoft:main.account.add.microsoft.cancel=Cancel minosoft:connection.kick.title=Kicked from server minosoft:connection.kick.header=You got kicked -minosoft:connection.kick.description=You got kicked from %1$s (connected with: %2$s) +minosoft:connection.kick.description=You got kicked from %0$s (connected with: %1$s) minosoft:connection.kick.reconnect_button=Reconnect minosoft:connection.kick.close_button=Close minosoft:connection.login_kick.title=Kicked from server minosoft:connection.login_kick.header=You got kicked -minosoft:connection.login_kick.description=You got kicked while logging in from %1$s (connected with: %2$s) +minosoft:connection.login_kick.description=You got kicked while logging in from %0$s (connected with: %0$s) -minosoft:error.title=%1$s - Minosoft +minosoft:error.title=%0$s - Minosoft minosoft:error.header=An error occurred! minosoft:error.description=An error in minosoft occurred. You can continue like before, but the behavior might not be the expected one. If this error persists, feel free to open an issue here: https://gitlab.bixilon.de/bixilon/minosoft/-/issues/ minosoft:error.fatal_crash=Fatal crash diff --git a/src/main/resources/assets/minosoft/language/es_es.lang b/src/main/resources/assets/minosoft/language/es_es.lang index 9ec9fd8ca..8a2204abc 100644 --- a/src/main/resources/assets/minosoft/language/es_es.lang +++ b/src/main/resources/assets/minosoft/language/es_es.lang @@ -19,7 +19,7 @@ minosoft:server_info.active_connections=Conexiones activas minosoft:server_info.players_online=Jugadores conectados minosoft:server_info.ping=Latencia -minosoft:server_info.delete.dialog.description=Estas seguro de que quieres eliminar el servidor: %1$s (%2$s)? +minosoft:server_info.delete.dialog.description=Estas seguro de que quieres eliminar el servidor: %0$s (%1$s)? minosoft:connection.status.state.waiting=Esperando... @@ -68,7 +68,7 @@ minosoft:main.account.type.microsoft=Microsoft minosoft:main.account.type.offline=Offline -minosoft:main.account.card.connection_count=%1$s conexiones +minosoft:main.account.card.connection_count=%0$s conexiones minosoft:main.account.account_info.id=Id minosoft:main.account.account_info.email=E-Mail @@ -97,15 +97,15 @@ minosoft:main.account.add.microsoft.title=Añadir cuenta de Microsoft minosoft:connection.kick.title=Expulsado del servidor minosoft:connection.kick.header=Te han expulsado -minosoft:connection.kick.description=Te han expulsado de %1$s (conectado con: %2$s) +minosoft:connection.kick.description=Te han expulsado de %0$s (conectado con: %1$s) minosoft:connection.kick.reconnect_button=Reconectarse minosoft:connection.kick.close_button=Cerrar minosoft:connection.login_kick.title=Expulsado del servidor minosoft:connection.login_kick.header=Te han expulsado -minosoft:connection.login_kick.description=Te han expulsado mientras iniciabas sesion de %1$s (conectado con: %2$s) +minosoft:connection.login_kick.description=Te han expulsado mientras iniciabas sesion de %0$s (conectado con: %1$s) -minosoft:error.title=%1$s - Minosoft +minosoft:error.title=%0$s - Minosoft minosoft:error.header=¡Ha ocurrido un error! minosoft:error.description=Ha ocurrido un error en Minosoft. Puedes continuar como antes, pero el comportamiento puede ser inesperado. Si el error continua, sientete libre de abrir un ticket aquí: https://gitlab.bixilon.de/bixilon/minosoft/-/issues/ minosoft:error.fatal_crash=Cierre inesperado diff --git a/src/test/java/de/bixilon/minosoft/data/language/lang/LanguageTest.kt b/src/test/java/de/bixilon/minosoft/data/language/lang/LanguageTest.kt new file mode 100644 index 000000000..01cafeb78 --- /dev/null +++ b/src/test/java/de/bixilon/minosoft/data/language/lang/LanguageTest.kt @@ -0,0 +1,171 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.language.lang + +import de.bixilon.minosoft.data.language.translate.Translator +import de.bixilon.minosoft.data.text.BaseComponent +import de.bixilon.minosoft.data.text.TextComponent +import de.bixilon.minosoft.data.text.formatting.color.ChatColors +import de.bixilon.minosoft.util.KUtil.toResourceLocation +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test + +class LanguageTest { + + private fun create(placeholder: String): Translator { + val data: LanguageData = mutableMapOf( + KEY.path to placeholder, + ) + + return Language("test", data) + } + + @Test + fun none() { + val language = create("Hello world!") + assertEquals(language.translate(KEY)?.message, "Hello world!") + } + + @Test + fun args() { + val language = create("%s %s") + assertEquals(language.translate(KEY, data = arrayOf("hello", "world"))?.message, "hello world") + } + + @Test + fun numberArgs() { + val language = create("%s %d") + assertEquals(language.translate(KEY, data = arrayOf("hello", "world"))?.message, "hello world") + } + + @Test + fun textArgs() { + val language = create("Hi %s, my name is %s and I like %s!") + assertEquals(language.translate(KEY, data = arrayOf("Gustaf", "Moritz", "sleeping"))?.message, "Hi Gustaf, my name is Moritz and I like sleeping!") + } + + @Test + fun ordered() { + val language = create("Hi %2\$s, my name is %1\$s and I like %0\$s!") + assertEquals(language.translate(KEY, data = arrayOf("sleeping", "Moritz", "Gustaf"))?.message, "Hi Gustaf, my name is Moritz and I like sleeping!") + } + + @Test + fun invalid() { + val language = create("hi %") + assertEquals(language.translate(KEY)?.message, "hi %") + } + + @Test + fun invalid2() { + val language = create("hi % s") + assertEquals(language.translate(KEY)?.message, "hi % s") + } + + @Test + fun invalid3() { + val language = create("hi %2$ s") + assertEquals(language.translate(KEY)?.message, "hi %2$ s") + } + + @Test + fun escape() { + val language = create("%%s %%%s %%%%s %%%%%s") + assertEquals(language.translate(KEY)?.message, "%%s %%%s %%%%s %%%%%s") + } + + @Test + fun complex() { + val language = create("Prefix, %s%2\$s again %s and %1\$s lastly %s and also %1\$s again!") + assertEquals(language.translate(KEY, data = arrayOf("aaa", "bbb", "ccc"))?.message, "Prefix, aaaccc again bbb and bbb lastly ccc and also bbb again!") + } + + @Test + fun formatting() { + val language = create("§eHi %s, welcome!") + assertEquals(language.translate(KEY, data = arrayOf("§aMoritz"))?.legacyText, "§eHi §r§aMoritz§r§e, welcome!§r") + } + + @Test + fun formatting2() { + val language = create("§eHi %s, welcome!") + assertEquals(language.translate(KEY, data = arrayOf("§aMoritz")), BaseComponent(TextComponent("Hi ").color(ChatColors.YELLOW), TextComponent("Moritz").color(ChatColors.GREEN), TextComponent(", welcome!").color(ChatColors.YELLOW))) + } + + @Test + fun parent() { + val language = create("Hi %s, welcome!") + assertEquals(language.translate(KEY, parent = TextComponent("").color(ChatColors.YELLOW), data = arrayOf("§aMoritz"))?.legacyText, "§eHi §r§aMoritz§r§e, welcome!§r") + } + + @Test + fun unavailableEmpty() { + val language = Language("test", mutableMapOf()) + assertNull(language.translate(KEY)?.message) + } + + @Test + fun unavailableForceEmpty() { + val language = Language("test", mutableMapOf()) + assertEquals(language.forceTranslate(KEY).message, "minecraft:key") + } + + @Test + fun unavailableData() { + val language = Language("test", mutableMapOf()) + assertEquals(language.forceTranslate(KEY, data = arrayOf("data2")).message, "minecraft:key->[data2]") + } + + @Test + fun fallbackData() { + val language = Language("test", mutableMapOf()) + assertEquals(language.forceTranslate(KEY, fallback = "falling back %s!", data = arrayOf("data2")).message, "falling back data2!") + } + + @Test + fun trailingData() { + val language = create("Hi %s!") + assertEquals(language.translate(KEY, data = arrayOf("Moritz", "trail me off"))?.message, "Hi Moritz!") + } + + @Test + fun missingData() { + val language = create("Hi %s %s!") + assertEquals(language.translate(KEY, data = arrayOf("Moritz"))?.message, "Hi Moritz !") + } + + @Test + fun tailingIndex() { + val language = create("Hi %0\$s!!!") + assertEquals(language.translate(KEY, data = arrayOf(null, "not me"))?.message, "Hi !!!") + } + + @Test + fun invalidIndex() { + val language = create("Hi %213\$s!!!") + assertEquals(language.translate(KEY, data = arrayOf("i am index one"))?.message, "Hi !!!") + } + + @Test + fun recursion() { + val language = create("Hi %s!") + assertEquals(language.translate(KEY, data = arrayOf("hah %0\$s"))?.message, "Hi hah %0\$s!") + } + + + companion object { + val KEY = "key".toResourceLocation() + } +} diff --git a/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt b/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt index 77e448bbe..7e60b9df1 100644 --- a/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt +++ b/src/test/java/de/bixilon/minosoft/data/text/ChatComponentTest.kt @@ -108,7 +108,7 @@ internal class ChatComponentTest { TextComponent("Test ").color(ChatColors.RED), TextComponent("file:/home/moritz").color(ChatColors.GREEN), ) - val actual = ChatComponent.of("§cTest §afile:/home/moritz", restrictedMode = true) + val actual = ChatComponent.of("§cTest §afile:/home/moritz", restricted = true) assertEquals(expected, actual) }