mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-15 18:34:56 -04:00
refactor text translations, tests
This commit is contained in:
parent
a0fe43e296
commit
d90ccd3780
@ -31,7 +31,7 @@ FormattedChatMessage(
|
|||||||
init {
|
init {
|
||||||
// ToDo: parent (formatting)
|
// ToDo: parent (formatting)
|
||||||
val data = type.chat.formatParameters(parameters)
|
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)
|
text.setFallbackColor(ChatUtil.DEFAULT_CHAT_COLOR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ object LanguageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadJsonLanguage(json: JsonObject): LanguageData {
|
fun loadJsonLanguage(json: JsonObject): LanguageData {
|
||||||
val data: LanguageData = mutableMapOf()
|
val data: LanguageData = HashMap()
|
||||||
|
|
||||||
for ((key, value) in json) {
|
for ((key, value) in json) {
|
||||||
val path = ResourceLocation.of(key).path
|
val path = ResourceLocation.of(key).path
|
||||||
@ -57,7 +57,7 @@ object LanguageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadLanguage(lines: Sequence<String>): LanguageData {
|
fun loadLanguage(lines: Sequence<String>): LanguageData {
|
||||||
val data: LanguageData = mutableMapOf()
|
val data: LanguageData = HashMap()
|
||||||
|
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
if (line.isBlank() || line.startsWith("#")) {
|
if (line.isBlank() || line.startsWith("#")) {
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
*/
|
*/
|
||||||
package de.bixilon.minosoft.data.language.lang
|
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.language.translate.Translator
|
||||||
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
|
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.ChatComponent
|
||||||
import de.bixilon.minosoft.data.text.TextComponent
|
import de.bixilon.minosoft.data.text.TextComponent
|
||||||
|
|
||||||
@ -23,62 +23,12 @@ class Language(
|
|||||||
private val data: LanguageData,
|
private val data: LanguageData,
|
||||||
) : Translator {
|
) : 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
|
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 {
|
override fun toString(): String {
|
||||||
return name
|
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>,
|
private val list: MutableList<Language>,
|
||||||
) : Translator {
|
) : 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) {
|
for (language in list) {
|
||||||
return language.translate(key, parent, restrictedMode, data) ?: continue
|
return language.translate(key, parent, restricted, data) ?: continue
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ class LanguageManager(
|
|||||||
) : Translator {
|
) : 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) {
|
for (language in languages) {
|
||||||
return language.translate(key, parent, restrictedMode, *data) ?: continue
|
return language.translate(key, parent, restricted, *data) ?: continue
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,9 @@ class MultiLanguageManager(
|
|||||||
val translators: MutableMap<String, Translator> = mutableMapOf(),
|
val translators: MutableMap<String, Translator> = mutableMapOf(),
|
||||||
) : Translator {
|
) : 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
|
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
|
package de.bixilon.minosoft.data.language.translate
|
||||||
|
|
||||||
import de.bixilon.minosoft.data.language.LanguageUtil
|
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.registries.identified.ResourceLocation
|
||||||
import de.bixilon.minosoft.data.text.ChatComponent
|
import de.bixilon.minosoft.data.text.ChatComponent
|
||||||
import de.bixilon.minosoft.data.text.TextComponent
|
import de.bixilon.minosoft.data.text.TextComponent
|
||||||
@ -25,16 +25,16 @@ interface Translator {
|
|||||||
return forceTranslate(key, null, false, null, *data)
|
return forceTranslate(key, null, false, null, *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceTranslate(key: ResourceLocation?, parent: TextComponent? = null, restrictedMode: Boolean = false, fallback: String? = null, vararg data: Any?): ChatComponent {
|
fun forceTranslate(key: ResourceLocation?, parent: TextComponent? = null, restricted: Boolean = false, fallback: String? = null, vararg data: Any?): ChatComponent {
|
||||||
translate(key, parent, restrictedMode, *data)?.let { return it }
|
translate(key, parent, restricted, *data)?.let { return it }
|
||||||
if (fallback != null) {
|
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, 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 {
|
fun translate(translatable: Any?): ChatComponent {
|
||||||
|
@ -93,7 +93,7 @@ interface ChatComponent {
|
|||||||
val EMPTY = EmptyComponent
|
val EMPTY = EmptyComponent
|
||||||
|
|
||||||
@JvmOverloads
|
@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) {
|
if (raw == null) {
|
||||||
return EMPTY
|
return EMPTY
|
||||||
}
|
}
|
||||||
@ -101,15 +101,15 @@ interface ChatComponent {
|
|||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
if (raw is Translatable && raw !is ResourceLocation) {
|
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) {
|
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<*> -> {
|
is List<*> -> {
|
||||||
val component = BaseComponent()
|
val component = BaseComponent()
|
||||||
for (part in raw) {
|
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
|
return component.trim() ?: EmptyComponent
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ interface ChatComponent {
|
|||||||
if (codePoint == '{'.code || codePoint == '['.code) {
|
if (codePoint == '{'.code || codePoint == '['.code) {
|
||||||
try {
|
try {
|
||||||
val read: Any = Jackson.MAPPER.readValue(string, Any::class.java)
|
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) {
|
} catch (ignored: JacksonException) {
|
||||||
break
|
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 {
|
fun String.chat(): ChatComponent {
|
||||||
|
@ -56,7 +56,7 @@ open class InByteBuffer : de.bixilon.kutil.buffer.bytes.`in`.InByteBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun readChatComponent(): ChatComponent {
|
open fun readChatComponent(): ChatComponent {
|
||||||
return ChatComponent.of(readString(), restrictedMode = true)
|
return ChatComponent.of(readString(), restricted = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readDirection(): Directions {
|
fun readDirection(): Directions {
|
||||||
|
@ -20,7 +20,7 @@ minosoft:server_info.remote_brand=Remote brand
|
|||||||
minosoft:server_info.active_connections=Connected
|
minosoft:server_info.active_connections=Connected
|
||||||
minosoft:server_info.players_online=Players online
|
minosoft:server_info.players_online=Players online
|
||||||
minosoft:server_info.ping=Latency
|
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
|
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.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.id=Id
|
||||||
minosoft:main.account.account_info.state=State
|
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.please_wait.device_code=Obtaining device code...Please wait!
|
||||||
|
|
||||||
minosoft:main.account.add.microsoft.title=Add microsoft account - Minosoft
|
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:main.account.add.microsoft.cancel=Cancel
|
||||||
|
|
||||||
minosoft:connection.kick.title=Kicked from server
|
minosoft:connection.kick.title=Kicked from server
|
||||||
minosoft:connection.kick.header=You got kicked
|
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.reconnect_button=Reconnect
|
||||||
minosoft:connection.kick.close_button=Close
|
minosoft:connection.kick.close_button=Close
|
||||||
|
|
||||||
minosoft:connection.login_kick.title=Kicked from server
|
minosoft:connection.login_kick.title=Kicked from server
|
||||||
minosoft:connection.login_kick.header=You got kicked
|
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.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.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
|
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.players_online=Jugadores conectados
|
||||||
minosoft:server_info.ping=Latencia
|
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...
|
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.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.id=Id
|
||||||
minosoft:main.account.account_info.email=E-Mail
|
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.title=Expulsado del servidor
|
||||||
minosoft:connection.kick.header=Te han expulsado
|
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.reconnect_button=Reconectarse
|
||||||
minosoft:connection.kick.close_button=Cerrar
|
minosoft:connection.kick.close_button=Cerrar
|
||||||
|
|
||||||
minosoft:connection.login_kick.title=Expulsado del servidor
|
minosoft:connection.login_kick.title=Expulsado del servidor
|
||||||
minosoft:connection.login_kick.header=Te han expulsado
|
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.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.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
|
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("Test ").color(ChatColors.RED),
|
||||||
TextComponent("file:/home/moritz").color(ChatColors.GREEN),
|
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)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user