diff --git a/api/build.gradle.kts b/api/build.gradle.kts
index 1e448d0c2..c5a96101e 100644
--- a/api/build.gradle.kts
+++ b/api/build.gradle.kts
@@ -29,6 +29,8 @@ dependencies {
api("net.kyori:adventure-text-serializer-legacy")
api("net.kyori:adventure-text-serializer-plain")
api("net.kyori:adventure-text-minimessage")
+ api("net.kyori:adventure-text-logger-slf4j")
+ api("net.kyori:adventure-text-serializer-ansi")
api(libs.slf4j)
api(libs.guice)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d204ff616..7733baaa7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -29,13 +29,14 @@ flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref =
jline = "org.jline:jline-terminal-jansi:3.23.0"
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
junit = "org.junit.jupiter:junit-jupiter:5.9.0"
+kyori-ansi = "net.kyori:ansi:1.0.3"
guava = "com.google.guava:guava:25.1-jre"
gson = "com.google.code.gson:gson:2.10.1"
guice = "com.google.inject:guice:6.0.0"
lmbda = "org.lanternpowered:lmbda:2.0.0"
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
-log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" }
+log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" }
log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" }
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
mockito = "org.mockito:mockito-core:5.2.0"
@@ -45,7 +46,7 @@ netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty"
netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
nightconfig = "com.electronwill.night-config:toml:3.6.6"
-slf4j = "org.slf4j:slf4j-api:1.7.30"
+slf4j = "org.slf4j:slf4j-api:2.0.7"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts
index 8d6ed5945..643d5eb78 100644
--- a/proxy/build.gradle.kts
+++ b/proxy/build.gradle.kts
@@ -93,6 +93,7 @@ dependencies {
implementation(project(":velocity-native"))
implementation(libs.bundles.log4j)
+ implementation(libs.kyori.ansi)
implementation(libs.netty.codec)
implementation(libs.netty.codec.haproxy)
implementation(libs.netty.codec.http)
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java
index f7e67cc37..aada98cf5 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java
@@ -79,6 +79,7 @@ import com.velocitypowered.proxy.tablist.VelocityTabList;
import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.DurationUtils;
+import com.velocitypowered.proxy.util.TranslatableMapper;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress;
@@ -100,9 +101,6 @@ import net.kyori.adventure.platform.facet.FacetPointers;
import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.KeybindComponent;
-import net.kyori.adventure.text.TranslatableComponent;
-import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -125,9 +123,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private static final int MAX_PLUGIN_CHANNELS = 1024;
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
- PlainTextComponentSerializer.builder().flattener(
- ComponentFlattener.basic().toBuilder().mapper(KeybindComponent.class, c -> "")
- .mapper(TranslatableComponent.class, TranslatableComponent::key).build()).build();
+ PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java
index 08bc04c07..fe360d4d6 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java
@@ -35,8 +35,7 @@ import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
-import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
-import net.kyori.adventure.translation.GlobalTranslator;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
@@ -55,6 +54,8 @@ import org.jline.reader.LineReaderBuilder;
public final class VelocityConsole extends SimpleTerminalConsole implements ConsoleCommandSource {
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
+ private static final ComponentLogger componentLogger = ComponentLogger
+ .logger(VelocityConsole.class);
private final VelocityServer server;
private PermissionFunction permissionFunction = ALWAYS_TRUE;
@@ -72,9 +73,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
@Override
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
@NonNull MessageType messageType) {
- Component translated = GlobalTranslator.render(message, ClosestLocaleMatcher.INSTANCE
- .lookupClosest(Locale.getDefault()));
- logger.info(LegacyComponentSerializer.legacySection().serialize(translated));
+ componentLogger.info(message);
}
@Override
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java
index 8e59816af..a6331dd48 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java
@@ -26,6 +26,7 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,6 +50,7 @@ class VelocityPluginModule implements Module {
binder.bind(description.getMainClass()).in(Scopes.SINGLETON);
binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId()));
+ binder.bind(ComponentLogger.class).toInstance(ComponentLogger.logger(description.getId()));
binder.bind(Path.class).annotatedWith(DataDirectory.class)
.toInstance(basePluginPath.resolve(description.getId()));
binder.bind(PluginDescription.class).toInstance(description);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/provider/ComponentLoggerProviderImpl.java b/proxy/src/main/java/com/velocitypowered/proxy/provider/ComponentLoggerProviderImpl.java
new file mode 100644
index 000000000..df29ca56f
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/provider/ComponentLoggerProviderImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * 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 .
+ */
+
+package com.velocitypowered.proxy.provider;
+
+import com.velocitypowered.proxy.util.TranslatableMapper;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
+import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider;
+import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Velocity ComponentLogger Provider.
+ */
+@SuppressWarnings("UnstableApiUsage")
+public final class ComponentLoggerProviderImpl implements ComponentLoggerProvider {
+ private static final ANSIComponentSerializer SERIALIZER = ANSIComponentSerializer.builder()
+ .flattener(TranslatableMapper.FLATTENER)
+ .build();
+
+ @Override
+ public @NotNull ComponentLogger logger(
+ final @NotNull LoggerHelper helper,
+ final @NotNull String name
+ ) {
+ return helper.delegating(LoggerFactory.getLogger(name), SERIALIZER::serialize);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java
new file mode 100644
index 000000000..c2ca2366f
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/util/TranslatableMapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 Velocity Contributors
+ *
+ * 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 .
+ */
+
+package com.velocitypowered.proxy.util;
+
+import java.util.Locale;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TranslatableComponent;
+import net.kyori.adventure.text.flattener.ComponentFlattener;
+import net.kyori.adventure.translation.GlobalTranslator;
+import net.kyori.adventure.translation.TranslationRegistry;
+import net.kyori.adventure.translation.Translator;
+import org.jetbrains.annotations.Nullable;
+
+
+/**
+ * Velocity Translation Mapper.
+ */
+public enum TranslatableMapper implements BiConsumer> {
+ INSTANCE;
+
+ public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder()
+ .complexMapper(TranslatableComponent.class, TranslatableMapper.INSTANCE)
+ .build();
+
+ @Override
+ public void accept(
+ final TranslatableComponent translatableComponent,
+ final Consumer componentConsumer
+ ) {
+ for (final Translator source : GlobalTranslator.translator().sources()) {
+ if (source instanceof TranslationRegistry
+ && ((TranslationRegistry) source).contains(translatableComponent.key())) {
+ componentConsumer.accept(GlobalTranslator.render(translatableComponent,
+ ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
+ return;
+ }
+ }
+ final @Nullable String fallback = translatableComponent.fallback();
+ if (fallback == null) {
+ return;
+ }
+ for (final Translator source : GlobalTranslator.translator().sources()) {
+ if (source instanceof TranslationRegistry
+ && ((TranslationRegistry) source).contains(fallback)) {
+ componentConsumer.accept(
+ GlobalTranslator.render(Component.translatable(fallback),
+ ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
+ return;
+ }
+ }
+ }
+}
diff --git a/proxy/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider b/proxy/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider
new file mode 100644
index 000000000..b95209776
--- /dev/null
+++ b/proxy/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider
@@ -0,0 +1 @@
+com.velocitypowered.proxy.provider.ComponentLoggerProviderImpl
\ No newline at end of file