mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-15 02:15:34 -04:00
refactor text translations, tests
This commit is contained in:
parent
a0fe43e296
commit
d90ccd3780
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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<String>): LanguageData {
|
||||
val data: LanguageData = mutableMapOf()
|
||||
val data: LanguageData = HashMap()
|
||||
|
||||
for (line in lines) {
|
||||
if (line.isBlank() || line.startsWith("#")) {
|
||||
|
@ -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<Any?> = mutableListOf()
|
||||
var splitPlaceholder: List<String> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ class LanguageList(
|
||||
private val list: MutableList<Language>,
|
||||
) : 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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ class MultiLanguageManager(
|
||||
val translators: MutableMap<String, Translator> = 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)
|
||||
}
|
||||
}
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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<out Any?>,
|
||||
val component: BaseComponent = BaseComponent(),
|
||||
var previous: TextComponent? = null,
|
||||
var builder: StringBuilder = StringBuilder(),
|
||||
var dataIndex: Int = 0,
|
||||
var escaped: Boolean = false,
|
||||
)
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 "<null>" // 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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 <null>!")
|
||||
}
|
||||
|
||||
@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 <null>!!!")
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user