better translation system

This commit is contained in:
alexcrea 2026-08-13 13:10:22 +02:00
parent c636767078
commit cb6040df10
Signed by: alexcrea
GPG key ID: E59DF23EE2A2266C
11 changed files with 257 additions and 83 deletions

View file

@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.dependency.util
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import org.bukkit.inventory.ItemStack
import org.bukkit.command.CommandSender
import org.bukkit.inventory.meta.ItemMeta
// Mostly made for paper, spigot and folia support
@ -100,4 +100,19 @@ object PlatformUtil {
}
}
/**
* Try to send paper component to the player
*
* @param component The used component
* @return true if sent, else otherwise
*/
fun CommandSender.sendPaperMessage(component: Component): Boolean {
if(isPaper) {
this.sendMessage(component)
return true
}
return false
}
}

View file

@ -16,7 +16,8 @@ import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant
import xyz.alexcrea.cuanvil.lang.Lang
import xyz.alexcrea.cuanvil.lang.Lang.translate
import xyz.alexcrea.cuanvil.lang.MsgError
import xyz.alexcrea.cuanvil.lang.MsgWarning
import xyz.alexcrea.cuanvil.listener.AnvilCloseListener
import xyz.alexcrea.cuanvil.listener.AnvilResultListener
import xyz.alexcrea.cuanvil.listener.ChatEventListener
@ -111,12 +112,12 @@ open class CustomAnvil : JavaPlugin() {
/**
* Error Logging handler
*/
@JvmStatic fun logError(message: String, throwable: Throwable? = null, track: Boolean = true) {
instance.logger.log(Level.SEVERE, message, throwable)
addToLogQueue(message)
@JvmStatic fun logError(message: String, throwable: Throwable? = null, track: Boolean = true, level: Level = Level.SEVERE) {
instance.logger.log(level, message, throwable)
addToLogQueue("Error: $message")
if(track && throwable != null) {
MetricsUtil.trackError(throwable)
if(track) {
MetricsUtil.trackError(message, throwable)
}
}
}
@ -168,7 +169,7 @@ open class CustomAnvil : JavaPlugin() {
try {
legacyCheck()
} catch (e: Exception) {
logError("error.load.legacy.failed".translate(), e)
MsgError.LOAD_LEGACY_FAILED.log(e)
if(trySafeStart()) return
}
@ -177,7 +178,7 @@ open class CustomAnvil : JavaPlugin() {
try {
CustomAnvilCommand(this)
} catch (e: Exception) {
logError("error.load.command-register".translate(), e)
MsgError.LOAD_COMMAND_REGISTER.log(e)
if(trySafeStart()) return
}
@ -186,7 +187,7 @@ open class CustomAnvil : JavaPlugin() {
try {
DependencyManager.loadDependency()
} catch (e: Exception) {
logError("error.load.compatibility".translate(), e)
MsgError.LOAD_COMPATIBILITY.log(e)
if(tryDirtyStart()) return
}
@ -194,7 +195,7 @@ open class CustomAnvil : JavaPlugin() {
try {
registerListeners()
} catch (e: Exception) {
logError("error.load.listeners".translate(), e)
MsgError.LOAD_LISTENERS.log(e)
if(tryDirtyStart()) return
}
@ -215,18 +216,15 @@ open class CustomAnvil : JavaPlugin() {
val potentialPlugin = Bukkit.getPluginManager().getPlugin("UnsafeEnchantsPlus")
if (potentialPlugin != null) {
Bukkit.getPluginManager().disablePlugin(potentialPlugin)
logger.warning("warning.load.legacy.old-name.1".translate())
logger.warning("warning.load.legacy.old-name.2".translate())
MsgWarning.LOAD_LEGACY_OLD_NAME.log()
}
val isPaper = PlatformUtil.isPaper
if(!isPaper) {
logger.warning("warning.load.legacy.spigot.1".translate())
logger.warning("warning.load.legacy.spigot.2".translate())
if(MinecraftVersionUtil.isTooNewForSpigot) {
logger.warning("warning.load.legacy.spigot-old.1".translate())
logger.warning("warning.load.legacy.spigot-old.1".translate())
}
MsgWarning.LOAD_LEGACY_SPIGOT.log()
if(MinecraftVersionUtil.isTooNewForSpigot)
MsgWarning.LOAD_LEGACY_SPIGOT_OLD.log()
}
val loader = if(isPaper) "paper" else "spigot"
@ -238,13 +236,13 @@ open class CustomAnvil : JavaPlugin() {
UpdateUtils.currentMinecraftVersion().toString())
.setFeatured(featured)
.setOnError {
logger.log(Level.WARNING, "error.load.update.check-fail".translate(), it)
MsgError.LOAD_UPDATE_CHECK_FAIL.log(it, level = Level.WARNING, track = false)
}
.checkVersion { latestVer: String? ->
CustomAnvil.latestVer = latestVer
if(latestVer == null || version.contains(latestVer)) return@checkVersion
logger.warning("warning.load.update.available".translate(Pair("version", latestVer)))
MsgWarning.LOAD_UPDATE_AVAILABLE.log(latestVer)
}
}
@ -263,7 +261,7 @@ open class CustomAnvil : JavaPlugin() {
try {
loadEnchantmentSystem()
} catch (e: Exception) {
logError("error.load.enchant-system".translate(), e)
MsgError.LOAD_ENCHANT_SYSTEM.log(e)
tryDirtyStart()
}
}
@ -278,7 +276,7 @@ open class CustomAnvil : JavaPlugin() {
// Load config
if (!ConfigHolder.loadNonDefaultConfig()) {
logError("error.load.non-default-config".translate())
MsgError.LOAD_NON_DEFAULT_CONFIG.log()
server.pluginManager.disablePlugin(this)
return
}
@ -332,16 +330,15 @@ open class CustomAnvil : JavaPlugin() {
try {
val configReader = FileReader(resourceFile)
yamlConfig.load(configReader)
} catch (_: Exception) {
} catch (e: Exception) {
MsgError.RELOAD_FAIL.log(e, resourceFile.path)
if (hardFailSafe) {
// This is important and may impact gameplay if it does not load.
// Failsafe is to stop the plugin
logError("error.reload.resource.fail".translate(Pair("path", resourceFile.path)))
logError("error.reload.resource.hard-fail".translate())
MsgError.RELOAD_HARD_FAIL.log()
Bukkit.getPluginManager().disablePlugin(this)
} else {
logError("error.reload.resource.fail".translate(Pair("path", resourceFile.path)))
}
return null
}
return yamlConfig

View file

@ -122,8 +122,7 @@ class DiagnosticExecutor : CASubCommand {
if (sender is HumanEntity) {
if (hasError)
sender.spigot()
.sendMessage(TextComponent(ChatColor.RED.toString() + "There was an error running the diagnostic"))
sender.sendMessage(ChatColor.RED.toString() + "There was an error running the diagnostic")
val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic data")
message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString())

View file

@ -18,60 +18,13 @@ object Lang {
if(default != lang) default.reload()
}
fun String.translate(): String {
val value = lang.get(this)
fun getTranslated(key: String): String {
val value = lang.get(key)
if(value != null) return value
CustomAnvil.log("Missing language data for ${lang.name} using default")
return default.get(this) ?: this
}
fun String.translate(vararg params: Pair<String, Any>): String {
return translate(
params.asSequence()
.map { Pair(it.first, it.second.toString()) }
.toMap()
)
}
fun String.translate(vararg params: Pair<Char, Any>): String {
return translate(
params.asSequence()
.map { Pair(it.first.toString(), it.second.toString()) }
.toMap()
)
}
fun String.translate(vararg params: Pair<String, String>): String {
return translate(params.toMap())
}
fun String.translate(vararg params: Pair<Char, String>): String {
return translate(
params.asSequence()
.map { Pair(it.first.toString(), it.second) }
.toMap()
)
}
fun String.translate(params: Map<String, String>): String {
val builder = StringBuilder(translate())
// replace all placeholder thingy %key -> value
for((key, replacement) in params) {
var current = 0
while(true) {
current = builder.indexOf('%', current) + 1
if(current < 0 || current + key.length > builder.length) break // may be able to be removed if bound checked in startsWith ?
if(!builder.startsWith(key, current, false)) continue
builder.replace(current - 1, current + key.length, replacement)
current = current - 1 + replacement.length
}
}
return builder.toString()
return default.get(key) ?: key
}
/*

View file

@ -7,7 +7,6 @@ import java.io.File
import java.io.InputStreamReader
import java.util.logging.Level
class Language(private val id: String, private val default: Boolean = false) {
private val resourcePath: String

View file

@ -0,0 +1,128 @@
package xyz.alexcrea.cuanvil.lang
import io.delilaheve.CustomAnvil
import net.kyori.adventure.text.Component
import org.bukkit.command.CommandSender
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.sendPaperMessage
import xyz.alexcrea.cuanvil.util.MiniMessageUtil
import java.util.logging.Level
import kotlin.math.min
interface MessageLike {
fun log(vararg params: Any)
fun send(destination: CommandSender, vararg params: Any)
}
enum class MessageType {
DEFAULT,
WARNING,
ERROR,
}
open class Message(val key: String, vararg val params: String) : MessageLike {
protected fun replaceParameters(stb: StringBuilder, vararg values: Any) {
// replace all placeholder thingy %key -> value
for(i in 0 until min(params.size, values.size)) {
val key = params[i]
val replacement = values[i].toString()
var current = 0
while(true) {
current = stb.indexOf('%', current) + 1
if(current < 0 || current + key.length > stb.length) break // may be able to be removed if bound checked in startsWith ?
if(!stb.startsWith(key, current, false)) continue
stb.replace(current - 1, current + key.length, replacement)
current = current - 1 + replacement.length
}
}
}
fun unformatted(vararg params: Any): String {
val translated = Lang.getTranslated(key)
if(params.isEmpty()) return translated
val stb = StringBuilder(translated)
replaceParameters(stb, *params)
return stb.toString()
}
fun formatted(vararg params: Any): Component {
val unformatted = unformatted(*params)
return MiniMessageUtil.mm.deserialize(unformatted)
}
override fun log(vararg params: Any) {
val text = unformatted(*params)
CustomAnvil.instance.logger.info(text)
}
override fun send(destination: CommandSender, vararg params: Any) {
val message = formatted(*params)
if(!destination.sendPaperMessage(message))
destination.sendMessage(MiniMessageUtil.legacy_mm.serialize(message))
}
}
class WarningMessage(key: String, vararg params: String) : Message("warning.$key", *params) {
override fun log(vararg params: Any) {
val text = unformatted(*params)
CustomAnvil.instance.logger.warning(text)
}
}
class ErrorMessage(key: String, vararg params: String) : Message("error.$key", *params) {
override fun log(vararg params: Any) {
val text = unformatted(*params)
CustomAnvil.logError(text)
}
fun log(e: Throwable, vararg params: Any, level: Level = Level.SEVERE, track: Boolean = true) {
val text = unformatted(*params)
CustomAnvil.logError(text, e, track, level)
}
}
class MultiLineMessage(type: MessageType, baseKey: String, count: Int, vararg params: String) : MessageLike {
private val messages = ArrayList<MessageLike>()
init {
for(i in 1 until count + 1) {
val message = createNew(type, "$baseKey.$i", *params)
messages.add(message)
}
}
private fun createNew(type: MessageType, key: String, vararg params: String): MessageLike {
return when(type) {
MessageType.DEFAULT -> Message(key, *params)
MessageType.WARNING -> WarningMessage(key, *params)
MessageType.ERROR -> ErrorMessage(key, *params)
}
}
override fun log(vararg params: Any) {
for(message in messages) {
message.log(*params)
}
}
override fun send(destination: CommandSender, vararg params: Any) {
for(message in messages) {
message.send(destination, *params)
}
}
}

View file

@ -0,0 +1,4 @@
package xyz.alexcrea.cuanvil.lang
object Msg {
}

View file

@ -0,0 +1,26 @@
package xyz.alexcrea.cuanvil.lang
object MsgError {
/*
* -----------------
* Load and reload
* -----------------
*/
val LOAD_UPDATE_CHECK_FAIL = ErrorMessage("load.update.check-fail")
val LOAD_LEGACY_FAILED = ErrorMessage("load.legacy.failed")
val LOAD_COMMAND_REGISTER = ErrorMessage("load.command-register")
val LOAD_COMPATIBILITY = ErrorMessage("load.compatibility")
val LOAD_LISTENERS = ErrorMessage("load.listeners")
val LOAD_ENCHANT_SYSTEM = ErrorMessage("load.enchant-system")
val LOAD_NON_DEFAULT_CONFIG = ErrorMessage("load.non-default-config")
val RELOAD_FAIL = ErrorMessage("reload.resource.fail", "path")
val RELOAD_HARD_FAIL = ErrorMessage("reload.resource.hardfail")
/*
* ----------
* Commands
* ----------
*/
}

View file

@ -0,0 +1,17 @@
package xyz.alexcrea.cuanvil.lang
object MsgWarning {
/*
* ---------------
* Load and reload
* ---------------
*/
val LOAD_UPDATE_AVAILABLE = WarningMessage("load.update.available", "version")
val LOAD_LEGACY_OLD_NAME = MultiLineMessage(MessageType.WARNING, "load.legacy.old-name", 2)
val LOAD_LEGACY_SPIGOT = MultiLineMessage(MessageType.WARNING, "load.legacy.spigot", 2)
val LOAD_LEGACY_SPIGOT_OLD = MultiLineMessage(MessageType.WARNING, "load.legacy.spigot", 2)
}

View file

@ -74,8 +74,8 @@ object MetricsUtil {
lastError = e
}
fun trackError(message: String) {
ERROR_TRACKER?.trackError(message)
fun trackError(message: String, cause: Throwable? = null) {
trackError(RuntimeException(message, cause))
}
}

View file

@ -1,4 +1,5 @@
name: English
last-updated: 2.1.0
warning:
load:
@ -27,3 +28,38 @@ error:
resource:
fail: "Resource %path Could not be loaded or reloaded."
hard-fail: "Disabling plugin."
command:
shared:
no-diag-permission: "<red>You do not have permission to diagnostic this server"
# I try to avoid using & for color but this is for a bungee text component
hover-copy: "§7Click to copy"
warning:
missing-subcmd: "Need to specify a subcommand. for example %example1 or %example2"
root:
warning:
unknown-sub: "<red>Invalid subcommand. run <yellow>`%command help` <red>to see available commands"
error:
generic: "<red>Error running this command"
debug:
description: "Used to toggle debug logs and retrieve them"
log-cleared: "Log Cleared"
toggled: "Debug toggled to %type"
# I try to avoid using & for color but this is for a bungee text component
copy: "§aClick to copy log data"
data:
header: "Debug Log data:"
line-count: "Found %count lines"
warning:
unspecified-type: "Need to specify which type of debug to toggle: \"default\" or \"verbose\""
no-log: "No log to show ? make sure you tried with debug log toggled (%command)"
diagnostic:
description: "Basic diagnostic of this plugin"
had-error: "<red>There was an error running the diagnostic"
# I try to avoid using & for color but this is for a bungee text component
copy: "§aClick to copy diagnostic data"
config:
description: "Used to edit the configuration of the plugin"
warning:
legacy-name: "<red>/ca gui has been moved to /ca config"